Compare commits
137 Commits
v0.124.7
...
html_tag_s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c57eaae0d9 | ||
|
|
ec82fe7749 | ||
|
|
14a479b504 | ||
|
|
8cf36ae603 | ||
|
|
079c31fcac | ||
|
|
bbc4ed9cab | ||
|
|
8536ba54c3 | ||
|
|
a0c8debd35 | ||
|
|
009384f948 | ||
|
|
72009de309 | ||
|
|
bdf59b8f06 | ||
|
|
e01a606616 | ||
|
|
510f4328f2 | ||
|
|
4b7bd03db7 | ||
|
|
935938a27c | ||
|
|
d4584a10b6 | ||
|
|
3b2e315ead | ||
|
|
43163a0154 | ||
|
|
cd8ede542b | ||
|
|
7d0c515be9 | ||
|
|
011ae8536c | ||
|
|
fcd0571ab4 | ||
|
|
f27d59896f | ||
|
|
a44fc24445 | ||
|
|
f54bb32a7f | ||
|
|
d8902ca5d6 | ||
|
|
b575fbb449 | ||
|
|
d8276b0f0d | ||
|
|
841e010fa4 | ||
|
|
ffdda588b4 | ||
|
|
6c5dfd1061 | ||
|
|
6ef32374d6 | ||
|
|
b29946130e | ||
|
|
368fec2822 | ||
|
|
934af6ad45 | ||
|
|
053b6cc715 | ||
|
|
37a12a366f | ||
|
|
93f3c6984b | ||
|
|
633e31a47f | ||
|
|
882cd6e52f | ||
|
|
83f493b387 | ||
|
|
dbe1f48f95 | ||
|
|
401798d9b8 | ||
|
|
35bec9803a | ||
|
|
d4973846c0 | ||
|
|
16d826a3f4 | ||
|
|
c29ea9bdbc | ||
|
|
cf3b875922 | ||
|
|
3ddf2f27d3 | ||
|
|
d1b58601a1 | ||
|
|
356d197422 | ||
|
|
d895388809 | ||
|
|
76196694b7 | ||
|
|
ba4e1699ae | ||
|
|
58463b2e97 | ||
|
|
03b0764df4 | ||
|
|
c59aab5090 | ||
|
|
2e616f8388 | ||
|
|
dc7e14f888 | ||
|
|
7599933f30 | ||
|
|
622cae19eb | ||
|
|
e06ff5f507 | ||
|
|
9b44ba9382 | ||
|
|
aef299be3d | ||
|
|
885ae2d863 | ||
|
|
cab8b5a9a3 | ||
|
|
d993dd3b2c | ||
|
|
351e6a5de2 | ||
|
|
41949d7b6c | ||
|
|
0fbd0d6649 | ||
|
|
69c7d0e549 | ||
|
|
d3a38c69cd | ||
|
|
7c514d044f | ||
|
|
a11ebe01ff | ||
|
|
c5bb032224 | ||
|
|
58fd84308d | ||
|
|
008d99d206 | ||
|
|
3bc7cd66b7 | ||
|
|
b0872b5b57 | ||
|
|
602fd58929 | ||
|
|
ed3bb68206 | ||
|
|
522176d414 | ||
|
|
f19ab464c7 | ||
|
|
6d91224882 | ||
|
|
351c8c9a36 | ||
|
|
c90065e6ea | ||
|
|
0f584cb353 | ||
|
|
7cf0696c89 | ||
|
|
576f8d3ef3 | ||
|
|
42ac9880c6 | ||
|
|
65318cb6ac | ||
|
|
71557f3eb3 | ||
|
|
a588f674db | ||
|
|
50dd38bd02 | ||
|
|
caa156ab13 | ||
|
|
a82f4857f4 | ||
|
|
0de8672044 | ||
|
|
cc8e3c2286 | ||
|
|
347f68887f | ||
|
|
a475d8640f | ||
|
|
991c9ec441 | ||
|
|
250df707bf | ||
|
|
ba6b319046 | ||
|
|
bd94a0e921 | ||
|
|
40bbd0031d | ||
|
|
946f4a312a | ||
|
|
af06063d31 | ||
|
|
5c4f3c0cea | ||
|
|
c6826a61a0 | ||
|
|
fa2c92d190 | ||
|
|
20b10fdca9 | ||
|
|
4f40d3c801 | ||
|
|
b716035d02 | ||
|
|
94bc216bbd | ||
|
|
95d5ea7edc | ||
|
|
aff858bd00 | ||
|
|
583d85cf66 | ||
|
|
36586b77ec | ||
|
|
587788b9a0 | ||
|
|
6f36527bc6 | ||
|
|
aa34e306f7 | ||
|
|
e5d971f4c7 | ||
|
|
6be77ef2e5 | ||
|
|
b7f7febf93 | ||
|
|
38c3a93f0c | ||
|
|
5d9db13a8f | ||
|
|
54423efb1c | ||
|
|
2a77b1c52f | ||
|
|
7f062902d6 | ||
|
|
f930969411 | ||
|
|
266bb62813 | ||
|
|
6e897d9969 | ||
|
|
d90b052162 | ||
|
|
49a53e7654 | ||
|
|
3220986fc9 | ||
|
|
9fcda5a5ac | ||
|
|
5e43290aa1 |
38
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
@@ -2,23 +2,23 @@ name: Feature Request
|
||||
description: "Tip: open this issue template from within Zed with the `request feature` command palette action"
|
||||
labels: ["admin read", "triage", "enhancement"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Check for existing issues
|
||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||
options:
|
||||
- label: Completed
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Check for existing issues
|
||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the feature
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the feature
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: |
|
||||
If applicable, add mockups / screenshots to help present your vision of the feature
|
||||
description: Drag images into the text input below
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: |
|
||||
If applicable, add mockups / screenshots to help present your vision of the feature
|
||||
description: Drag images into the text input below
|
||||
validations:
|
||||
required: false
|
||||
|
||||
40
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Bug Report
|
||||
description: |
|
||||
Use this template for **non-crash-related** bug reports.
|
||||
Tip: open this issue template from within Zed with the `file bug report` command palette action.
|
||||
labels: ["admin read", "triage", "defect"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Check for existing issues
|
||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug / provide steps to reproduce it
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
|
||||
description: Drag issues into the text input below
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: |
|
||||
If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
description: Drag Zed.log into the text input below
|
||||
validations:
|
||||
required: false
|
||||
47
.github/ISSUE_TEMPLATE/1_language_support.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: Language Support
|
||||
description: Request language support
|
||||
title: "<name_of_language> support"
|
||||
labels:
|
||||
[
|
||||
"admin read",
|
||||
"triage",
|
||||
"enhancement",
|
||||
"language",
|
||||
"unsupported language",
|
||||
"potential extension",
|
||||
]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Check for existing issues
|
||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Language
|
||||
description: What language do you want support for?
|
||||
placeholder: HTML
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Tree Sitter parser link
|
||||
description: If applicable, provide a link to the appropriate tree sitter parser. Look here first - https://tree-sitter.github.io/tree-sitter/#available-parsers
|
||||
placeholder: https://github.com/tree-sitter/tree-sitter-html
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Language server link
|
||||
description: If applicable, provide a link to the appropriate language server. Look here first - https://microsoft.github.io/language-server-protocol/implementors/servers/
|
||||
placeholder: https://github.com/Microsoft/vscode/tree/main/extensions/html-language-features/server
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Misc notes
|
||||
description: Provide any additional things the team should consider when adding support for this language
|
||||
validations:
|
||||
required: false
|
||||
38
.github/ISSUE_TEMPLATE/2_bug_report.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Bug Report
|
||||
description: "Tip: open this issue template from within Zed with the `file bug report` command palette action"
|
||||
labels: ["admin read", "triage", "defect"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Check for existing issues
|
||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug / provide steps to reproduce it
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
|
||||
description: Drag issues into the text input below
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: |
|
||||
If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
description: Drag Zed.log into the text input below
|
||||
validations:
|
||||
required: false
|
||||
39
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Crash Report
|
||||
description: |
|
||||
Use this template for crash reports.
|
||||
labels: ["admin read", "triage", "defect", "panic / crash"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Check for existing issues
|
||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug / provide steps to reproduce it
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
|
||||
description: Drag issues into the text input below
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: |
|
||||
If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
description: Drag Zed.log into the text input below
|
||||
validations:
|
||||
required: false
|
||||
27
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,13 +1,16 @@
|
||||
contact_links:
|
||||
- name: Theme Request
|
||||
url: https://github.com/zed-industries/extensions/issues/new/choose
|
||||
about: Request a theme in the extensions repository
|
||||
- name: Top-Ranking Issues
|
||||
url: https://github.com/zed-industries/zed/issues/5393
|
||||
about: See an overview of the most popular Zed issues
|
||||
- name: Platform Support
|
||||
url: https://github.com/zed-industries/zed/issues/5391
|
||||
about: A quick note on platform support
|
||||
- name: Positive Feedback
|
||||
url: https://github.com/zed-industries/zed/discussions/5397
|
||||
about: A central location for kind words about Zed
|
||||
- name: Language Request
|
||||
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=language&projects=&template=1_language_request.yml&title=%3Cname_of_language%3E
|
||||
about: Request a language in the extensions repository
|
||||
- name: Theme Request
|
||||
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=theme&projects=&template=0_theme_request.yml&title=%3Cname_of_theme%3E+theme
|
||||
about: Request a theme in the extensions repository
|
||||
- name: Top-Ranking Issues
|
||||
url: https://github.com/zed-industries/zed/issues/5393
|
||||
about: See an overview of the most popular Zed issues
|
||||
- name: Platform Support
|
||||
url: https://github.com/zed-industries/zed/issues/5391
|
||||
about: A quick note on platform support
|
||||
- name: Positive Feedback
|
||||
url: https://github.com/zed-industries/zed/discussions/5397
|
||||
about: A central location for kind words about Zed
|
||||
|
||||
10
.github/workflows/ci.yml
vendored
@@ -64,6 +64,8 @@ jobs:
|
||||
fi
|
||||
|
||||
- uses: bufbuild/buf-setup-action@v1
|
||||
with:
|
||||
version: v1.29.0
|
||||
- uses: bufbuild/buf-breaking-action@v1
|
||||
with:
|
||||
input: "crates/rpc/proto/"
|
||||
@@ -148,10 +150,10 @@ jobs:
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/rust-toolchain.toml') }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
# todo!(windows): Actually run clippy
|
||||
#- name: cargo clippy
|
||||
# shell: bash -euxo pipefail {0}
|
||||
# run: script/clippy
|
||||
|
||||
- name: cargo clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clippy
|
||||
|
||||
- name: Build Zed
|
||||
run: cargo build -p zed
|
||||
|
||||
962
Cargo.lock
generated
38
Cargo.toml
@@ -16,6 +16,7 @@ members = [
|
||||
"crates/collab_ui",
|
||||
"crates/collections",
|
||||
"crates/command_palette",
|
||||
"crates/command_palette_hooks",
|
||||
"crates/copilot",
|
||||
"crates/copilot_ui",
|
||||
"crates/db",
|
||||
@@ -38,6 +39,7 @@ members = [
|
||||
"crates/language",
|
||||
"crates/language_selector",
|
||||
"crates/language_tools",
|
||||
"crates/languages",
|
||||
"crates/live_kit_client",
|
||||
"crates/live_kit_server",
|
||||
"crates/lsp",
|
||||
@@ -49,10 +51,9 @@ members = [
|
||||
"crates/notifications",
|
||||
"crates/outline",
|
||||
"crates/picker",
|
||||
"crates/plugin",
|
||||
"crates/plugin_macros",
|
||||
"crates/prettier",
|
||||
"crates/project",
|
||||
"crates/project_core",
|
||||
"crates/project_panel",
|
||||
"crates/project_symbols",
|
||||
"crates/quick_action_bar",
|
||||
@@ -81,6 +82,7 @@ members = [
|
||||
"crates/theme_importer",
|
||||
"crates/theme_selector",
|
||||
"crates/telemetry_events",
|
||||
"crates/time_format",
|
||||
"crates/ui",
|
||||
"crates/util",
|
||||
"crates/vcs_menu",
|
||||
@@ -111,6 +113,7 @@ collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections" }
|
||||
color = { path = "crates/color" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||
copilot = { path = "crates/copilot" }
|
||||
copilot_ui = { path = "crates/copilot_ui" }
|
||||
db = { path = "crates/db" }
|
||||
@@ -133,6 +136,7 @@ journal = { path = "crates/journal" }
|
||||
language = { path = "crates/language" }
|
||||
language_selector = { path = "crates/language_selector" }
|
||||
language_tools = { path = "crates/language_tools" }
|
||||
languages = { path = "crates/languages" }
|
||||
live_kit_client = { path = "crates/live_kit_client" }
|
||||
live_kit_server = { path = "crates/live_kit_server" }
|
||||
lsp = { path = "crates/lsp" }
|
||||
@@ -148,6 +152,7 @@ plugin = { path = "crates/plugin" }
|
||||
plugin_macros = { path = "crates/plugin_macros" }
|
||||
prettier = { path = "crates/prettier" }
|
||||
project = { path = "crates/project" }
|
||||
project_core = { path = "crates/project_core" }
|
||||
project_panel = { path = "crates/project_panel" }
|
||||
project_symbols = { path = "crates/project_symbols" }
|
||||
quick_action_bar = { path = "crates/quick_action_bar" }
|
||||
@@ -173,7 +178,8 @@ text = { path = "crates/text" }
|
||||
theme = { path = "crates/theme" }
|
||||
theme_importer = { path = "crates/theme_importer" }
|
||||
theme_selector = { path = "crates/theme_selector" }
|
||||
telemetry_events = { path ="crates/telemetry_events" }
|
||||
telemetry_events = { path = "crates/telemetry_events" }
|
||||
time_format = { path = "crates/time_format" }
|
||||
ui = { path = "crates/ui" }
|
||||
util = { path = "crates/util" }
|
||||
vcs_menu = { path = "crates/vcs_menu" }
|
||||
@@ -193,24 +199,29 @@ blade-rwh = { package = "raw-window-handle", version = "0.5" }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clickhouse = { version = "0.11.6" }
|
||||
ctor = "0.2.6"
|
||||
core-foundation = { version = "0.9.3" }
|
||||
core-foundation-sys = "0.8.6"
|
||||
derive_more = "0.99.17"
|
||||
env_logger = "0.9"
|
||||
futures = "0.3"
|
||||
git2 = { version = "0.15", default-features = false }
|
||||
globset = "0.4"
|
||||
hex = "0.4.3"
|
||||
ignore = "0.4.22"
|
||||
indoc = "1"
|
||||
# We explicitly disable a http2 support in isahc.
|
||||
# We explicitly disable http2 support in isahc.
|
||||
isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] }
|
||||
itertools = "0.11.0"
|
||||
lazy_static = "1.4.0"
|
||||
linkify = "0.10.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
ordered-float = "2.1.1"
|
||||
parking_lot = "0.11.1"
|
||||
profiling = "1"
|
||||
postage = { version = "0.5", features = ["futures-traits"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
prost = "0.8"
|
||||
pulldown-cmark = { version = "0.9.2", default-features = false }
|
||||
pulldown-cmark = { version = "0.10.0", default-features = false }
|
||||
rand = "0.8.5"
|
||||
refineable = { path = "./crates/refineable" }
|
||||
regex = "1.5"
|
||||
@@ -224,6 +235,7 @@ serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
||||
serde_json_lenient = { version = "0.1", features = ["preserve_order", "raw_value"] }
|
||||
serde_repr = "0.1"
|
||||
sha2 = "0.10"
|
||||
shellexpand = "2.1.0"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = "1.2"
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
@@ -267,7 +279,7 @@ tree-sitter-ocaml = { git = "https://github.com/tree-sitter/tree-sitter-ocaml",
|
||||
tree-sitter-php = "0.21.1"
|
||||
tree-sitter-prisma-io = { git = "https://github.com/victorhqc/tree-sitter-prisma" }
|
||||
tree-sitter-proto = { git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" }
|
||||
tree-sitter-purescript = { git = "https://github.com/ivanmoreau/tree-sitter-purescript", rev = "a37140f0c7034977b90faa73c94fcb8a5e45ed08" }
|
||||
tree-sitter-purescript = { git = "https://github.com/postsolar/tree-sitter-purescript", rev = "v0.1.0" }
|
||||
tree-sitter-python = "0.20.2"
|
||||
tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a" }
|
||||
tree-sitter-ruby = "0.20.0"
|
||||
@@ -283,13 +295,12 @@ tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "
|
||||
unindent = "0.1.7"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
wasmtime = "16"
|
||||
wasmtime = "18.0"
|
||||
which = "6.0.0"
|
||||
sys-locale = "0.3.1"
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "1d8975319c2d5de1bf710e7e21db25b0eee4bc66" }
|
||||
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" }
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "e4a23971ec3071a09c1e84816954c98f96e98e52" }
|
||||
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
|
||||
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "e4fcda0d5259d0acf902aee6de7d2501f2bd6629" }
|
||||
|
||||
@@ -302,10 +313,15 @@ debug = "limited"
|
||||
split-debuginfo = "off"
|
||||
debug = "full"
|
||||
|
||||
[profile.dev.package.taffy]
|
||||
opt-level = 3
|
||||
[profile.dev.package]
|
||||
taffy = { opt-level = 3 }
|
||||
cranelift-codegen = { opt-level = 3 }
|
||||
wasmtime-cranelift = { opt-level = 3 }
|
||||
|
||||
[profile.release]
|
||||
debug = "limited"
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.metadata.cargo-machete]
|
||||
ignored = ["bindgen", "cbindgen", "prost_build", "serde"]
|
||||
|
||||
3
assets/icons/file_icons/bun.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 22.596c6.628 0 12-4.338 12-9.688 0-3.318-2.057-6.248-5.219-7.986-1.286-.715-2.297-1.357-3.139-1.89C14.058 2.025 13.08 1.404 12 1.404c-1.097 0-2.334.785-3.966 1.821a49.92 49.92 0 0 1-2.816 1.697C2.057 6.66 0 9.59 0 12.908c0 5.35 5.372 9.687 12 9.687v.001ZM10.599 4.715c.334-.759.503-1.58.498-2.409 0-.145.202-.187.23-.029.658 2.783-.902 4.162-2.057 4.624-.124.048-.199-.121-.103-.209a5.763 5.763 0 0 0 1.432-1.977Zm2.058-.102a5.82 5.82 0 0 0-.782-2.306v-.016c-.069-.123.086-.263.185-.172 1.962 2.111 1.307 4.067.556 5.051-.082.103-.23-.003-.189-.126a5.85 5.85 0 0 0 .23-2.431Zm1.776-.561a5.727 5.727 0 0 0-1.612-1.806v-.014c-.112-.085-.024-.274.114-.218 2.595 1.087 2.774 3.18 2.459 4.407a.116.116 0 0 1-.049.071.11.11 0 0 1-.153-.026.122.122 0 0 1-.022-.083 5.891 5.891 0 0 0-.737-2.331Zm-5.087.561c-.617.546-1.282.76-2.063 1-.117 0-.195-.078-.156-.181 1.752-.909 2.376-1.649 2.999-2.778 0 0 .155-.118.188.085 0 .304-.349 1.329-.968 1.874Zm4.945 11.237a2.957 2.957 0 0 1-.937 1.553c-.346.346-.8.565-1.286.62a2.178 2.178 0 0 1-1.327-.62 2.955 2.955 0 0 1-.925-1.553.244.244 0 0 1 .064-.198.234.234 0 0 1 .193-.069h3.965a.226.226 0 0 1 .19.07c.05.053.073.125.063.197Zm-5.458-2.176a1.862 1.862 0 0 1-2.384-.245 1.98 1.98 0 0 1-.233-2.447c.207-.319.503-.566.848-.713a1.84 1.84 0 0 1 1.092-.11c.366.075.703.261.967.531a1.98 1.98 0 0 1 .408 2.114 1.931 1.931 0 0 1-.698.869v.001Zm8.495.005a1.86 1.86 0 0 1-2.381-.253 1.964 1.964 0 0 1-.547-1.366c0-.384.11-.76.32-1.079.207-.319.503-.567.849-.713a1.844 1.844 0 0 1 1.093-.108c.367.076.704.262.968.534a1.98 1.98 0 0 1 .4 2.117 1.932 1.932 0 0 1-.702.868Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
6
assets/icons/file_icons/dart.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 9.203125 3.410156 C 8.792969 3 8.378906 2.597656 7.957031 2.207031 C 7.835938 2.078125 7.664062 1.996094 7.476562 1.996094 C 7.472656 1.996094 7.472656 1.996094 7.46875 1.996094 C 7.324219 2.011719 7.1875 2.042969 7.0625 2.089844 L 7.074219 2.085938 L 4.425781 3.40625 Z M 3.71875 3.71875 L 3.71875 9.078125 C 3.714844 9.109375 3.710938 9.144531 3.710938 9.179688 C 3.710938 9.40625 3.800781 9.613281 3.945312 9.765625 L 6.183594 12.003906 L 10.066406 12.003906 L 10.066406 10.066406 Z M 3.40625 3.40625 C 3.40625 3.40625 5.707031 2.257812 6.855469 1.683594 C 7.039062 1.59375 7.25 1.539062 7.476562 1.539062 C 7.496094 1.539062 7.515625 1.539062 7.53125 1.539062 C 7.824219 1.597656 8.082031 1.722656 8.296875 1.902344 L 8.292969 1.898438 L 12.460938 6.066406 L 12.460938 10.519531 L 10.519531 10.519531 L 10.519531 12.460938 L 5.996094 12.460938 L 1.898438 8.367188 C 1.683594 8.140625 1.546875 7.839844 1.542969 7.503906 C 1.558594 7.316406 1.609375 7.148438 1.6875 6.992188 L 1.683594 7 Z M 3.40625 3.40625 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,269 +1,307 @@
|
||||
{
|
||||
"suffixes": {
|
||||
"astro": "astro",
|
||||
"Emakefile": "erlang",
|
||||
"aac": "audio",
|
||||
"accdb": "storage",
|
||||
"app.src": "erlang",
|
||||
"avi": "video",
|
||||
"avif": "image",
|
||||
"bak": "backup",
|
||||
"bash": "terminal",
|
||||
"bash_aliases": "terminal",
|
||||
"bash_logout": "terminal",
|
||||
"bash_profile": "terminal",
|
||||
"bashrc": "terminal",
|
||||
"bmp": "image",
|
||||
"c": "code",
|
||||
"cc": "code",
|
||||
"cjs": "code",
|
||||
"conf": "settings",
|
||||
"cpp": "code",
|
||||
"css": "css",
|
||||
"csv": "storage",
|
||||
"cts": "typescript",
|
||||
"dat": "storage",
|
||||
"db": "storage",
|
||||
"dbf": "storage",
|
||||
"dll": "storage",
|
||||
"doc": "document",
|
||||
"docx": "document",
|
||||
"eex": "elixir",
|
||||
"elm": "elm",
|
||||
"erl": "erlang",
|
||||
"escript": "erlang",
|
||||
"eslintrc": "eslint",
|
||||
"eslintrc.js": "eslint",
|
||||
"eslintrc.json": "eslint",
|
||||
"ex": "elixir",
|
||||
"exs": "elixir",
|
||||
"fish": "terminal",
|
||||
"flac": "audio",
|
||||
"fmp": "storage",
|
||||
"fp7": "storage",
|
||||
"frm": "storage",
|
||||
"gdb": "storage",
|
||||
"gif": "image",
|
||||
"gitattributes": "vcs",
|
||||
"gitignore": "vcs",
|
||||
"gitkeep": "vcs",
|
||||
"gitmodules": "vcs",
|
||||
"go": "go",
|
||||
"h": "code",
|
||||
"handlebars": "code",
|
||||
"hbs": "template",
|
||||
"heex": "elixir",
|
||||
"heif": "image",
|
||||
"heic": "image",
|
||||
"hrl": "erlang",
|
||||
"hs": "haskell",
|
||||
"htm": "template",
|
||||
"html": "template",
|
||||
"ib": "storage",
|
||||
"ico": "image",
|
||||
"ini": "settings",
|
||||
"j2k": "image",
|
||||
"java": "code",
|
||||
"jfif": "image",
|
||||
"jp2": "image",
|
||||
"jpeg": "image",
|
||||
"jpg": "image",
|
||||
"js": "code",
|
||||
"json": "storage",
|
||||
"jsonc": "storage",
|
||||
"jxl": "image",
|
||||
"ldf": "storage",
|
||||
"lock": "lock",
|
||||
"log": "log",
|
||||
"lua": "lua",
|
||||
"m4a": "audio",
|
||||
"m4v": "video",
|
||||
"md": "document",
|
||||
"mdb": "storage",
|
||||
"mdf": "storage",
|
||||
"mdx": "document",
|
||||
"mkv": "video",
|
||||
"mjs": "code",
|
||||
"mka": "audio",
|
||||
"ml": "ocaml",
|
||||
"mli": "ocaml",
|
||||
"mov": "video",
|
||||
"mp3": "audio",
|
||||
"mp4": "video",
|
||||
"mts": "typescript",
|
||||
"myd": "storage",
|
||||
"myi": "storage",
|
||||
"odp": "document",
|
||||
"ods": "document",
|
||||
"odt": "document",
|
||||
"ogg": "audio",
|
||||
"opus": "audio",
|
||||
"pdb": "storage",
|
||||
"pdf": "document",
|
||||
"php": "php",
|
||||
"png": "image",
|
||||
"ppt": "document",
|
||||
"pptx": "document",
|
||||
"prettierignore": "prettier",
|
||||
"prettierrc": "prettier",
|
||||
"prisma": "prisma",
|
||||
"profile": "terminal",
|
||||
"ps1": "terminal",
|
||||
"psd": "image",
|
||||
"py": "python",
|
||||
"qoi": "image",
|
||||
"rb": "ruby",
|
||||
"rebar.config": "erlang",
|
||||
"rkt": "code",
|
||||
"rs": "rust",
|
||||
"rtf": "document",
|
||||
"sav": "storage",
|
||||
"scm": "code",
|
||||
"sdf": "storage",
|
||||
"sh": "terminal",
|
||||
"sqlite": "storage",
|
||||
"svelte": "template",
|
||||
"svg": "image",
|
||||
"swift": "code",
|
||||
"tiff": "image",
|
||||
"toml": "toml",
|
||||
"ts": "typescript",
|
||||
"tsv": "storage",
|
||||
"tsx": "code",
|
||||
"txt": "document",
|
||||
"vue": "vue",
|
||||
"wav": "audio",
|
||||
"webm": "video",
|
||||
"webp": "image",
|
||||
"wma": "audio",
|
||||
"wmv": "video",
|
||||
"wv": "audio",
|
||||
"xls": "document",
|
||||
"xlsx": "document",
|
||||
"xml": "template",
|
||||
"xrl": "erlang",
|
||||
"yaml": "settings",
|
||||
"yml": "settings",
|
||||
"yrl": "erlang",
|
||||
"zlogin": "terminal",
|
||||
"zsh": "terminal",
|
||||
"zsh_aliases": "terminal",
|
||||
"zsh_histfile": "terminal",
|
||||
"zsh_profile": "terminal",
|
||||
"zshenv": "terminal",
|
||||
"zshrc": "terminal"
|
||||
"suffixes": {
|
||||
"astro": "astro",
|
||||
"Emakefile": "erlang",
|
||||
"aac": "audio",
|
||||
"accdb": "storage",
|
||||
"app.src": "erlang",
|
||||
"avi": "video",
|
||||
"avif": "image",
|
||||
"bak": "backup",
|
||||
"bash": "terminal",
|
||||
"bash_aliases": "terminal",
|
||||
"bash_logout": "terminal",
|
||||
"bash_profile": "terminal",
|
||||
"bashrc": "terminal",
|
||||
"bmp": "image",
|
||||
"c": "code",
|
||||
"cc": "code",
|
||||
"cjs": "code",
|
||||
"conf": "settings",
|
||||
"cpp": "code",
|
||||
"css": "css",
|
||||
"csv": "storage",
|
||||
"cts": "typescript",
|
||||
"dart": "dart",
|
||||
"dat": "storage",
|
||||
"db": "storage",
|
||||
"dbf": "storage",
|
||||
"dll": "storage",
|
||||
"doc": "document",
|
||||
"docx": "document",
|
||||
"eex": "elixir",
|
||||
"elm": "elm",
|
||||
"erl": "erlang",
|
||||
"escript": "erlang",
|
||||
"eslintrc": "eslint",
|
||||
"eslintrc.js": "eslint",
|
||||
"eslintrc.json": "eslint",
|
||||
"ex": "elixir",
|
||||
"exs": "elixir",
|
||||
"fish": "terminal",
|
||||
"flac": "audio",
|
||||
"fmp": "storage",
|
||||
"fp7": "storage",
|
||||
"frm": "storage",
|
||||
"gdb": "storage",
|
||||
"gif": "image",
|
||||
"gitattributes": "vcs",
|
||||
"gitignore": "vcs",
|
||||
"gitkeep": "vcs",
|
||||
"gitmodules": "vcs",
|
||||
"go": "go",
|
||||
"graphql": "graphql",
|
||||
"h": "code",
|
||||
"handlebars": "code",
|
||||
"hbs": "template",
|
||||
"heex": "elixir",
|
||||
"heif": "image",
|
||||
"heic": "image",
|
||||
"hrl": "erlang",
|
||||
"hs": "haskell",
|
||||
"htm": "template",
|
||||
"html": "template",
|
||||
"ib": "storage",
|
||||
"ico": "image",
|
||||
"ini": "settings",
|
||||
"j2k": "image",
|
||||
"java": "java",
|
||||
"jfif": "image",
|
||||
"jp2": "image",
|
||||
"jpeg": "image",
|
||||
"jpg": "image",
|
||||
"js": "code",
|
||||
"json": "storage",
|
||||
"jsonc": "storage",
|
||||
"jxl": "image",
|
||||
"kt": "kotlin",
|
||||
"ldf": "storage",
|
||||
"lock": "lock",
|
||||
"lockb": "bun",
|
||||
"log": "log",
|
||||
"lua": "lua",
|
||||
"m4a": "audio",
|
||||
"m4v": "video",
|
||||
"md": "document",
|
||||
"mdb": "storage",
|
||||
"mdf": "storage",
|
||||
"mdx": "document",
|
||||
"mkv": "video",
|
||||
"mjs": "code",
|
||||
"mka": "audio",
|
||||
"ml": "ocaml",
|
||||
"mli": "ocaml",
|
||||
"mov": "video",
|
||||
"mp3": "audio",
|
||||
"mp4": "video",
|
||||
"mts": "typescript",
|
||||
"myd": "storage",
|
||||
"myi": "storage",
|
||||
"nu": "terminal",
|
||||
"odp": "document",
|
||||
"ods": "document",
|
||||
"odt": "document",
|
||||
"ogg": "audio",
|
||||
"opus": "audio",
|
||||
"otf": "font",
|
||||
"pdb": "storage",
|
||||
"pdf": "document",
|
||||
"php": "php",
|
||||
"plist": "template",
|
||||
"png": "image",
|
||||
"ppt": "document",
|
||||
"pptx": "document",
|
||||
"prettierignore": "prettier",
|
||||
"prettierrc": "prettier",
|
||||
"prisma": "prisma",
|
||||
"profile": "terminal",
|
||||
"ps1": "terminal",
|
||||
"psd": "image",
|
||||
"py": "python",
|
||||
"qoi": "image",
|
||||
"rb": "ruby",
|
||||
"rebar.config": "erlang",
|
||||
"rkt": "code",
|
||||
"rs": "rust",
|
||||
"r": "r",
|
||||
"rtf": "document",
|
||||
"sav": "storage",
|
||||
"scm": "code",
|
||||
"sdf": "storage",
|
||||
"sh": "terminal",
|
||||
"sqlite": "storage",
|
||||
"svelte": "template",
|
||||
"svg": "image",
|
||||
"swift": "swift",
|
||||
"tf": "terraform",
|
||||
"tfvars": "terraform",
|
||||
"tiff": "image",
|
||||
"toml": "toml",
|
||||
"ts": "typescript",
|
||||
"tsv": "storage",
|
||||
"ttf": "font",
|
||||
"tsx": "code",
|
||||
"txt": "document",
|
||||
"vue": "vue",
|
||||
"wav": "audio",
|
||||
"webm": "video",
|
||||
"webp": "image",
|
||||
"wma": "audio",
|
||||
"wmv": "video",
|
||||
"wv": "audio",
|
||||
"xls": "document",
|
||||
"xlsx": "document",
|
||||
"xml": "template",
|
||||
"xrl": "erlang",
|
||||
"yaml": "settings",
|
||||
"yml": "settings",
|
||||
"yrl": "erlang",
|
||||
"zlogin": "terminal",
|
||||
"zsh": "terminal",
|
||||
"zsh_aliases": "terminal",
|
||||
"zsh_histfile": "terminal",
|
||||
"zsh_profile": "terminal",
|
||||
"zshenv": "terminal",
|
||||
"zshrc": "terminal"
|
||||
},
|
||||
"types": {
|
||||
"astro": {
|
||||
"icon": "icons/file_icons/astro.svg"
|
||||
},
|
||||
"types": {
|
||||
"astro": {
|
||||
"icon": "icons/file_icons/astro.svg"
|
||||
},
|
||||
"audio": {
|
||||
"icon": "icons/file_icons/audio.svg"
|
||||
},
|
||||
"code": {
|
||||
"icon": "icons/file_icons/code.svg"
|
||||
},
|
||||
"collapsed_chevron": {
|
||||
"icon": "icons/file_icons/chevron_right.svg"
|
||||
},
|
||||
"collapsed_folder": {
|
||||
"icon": "icons/file_icons/folder.svg"
|
||||
},
|
||||
"css": {
|
||||
"icon": "icons/file_icons/css.svg"
|
||||
},
|
||||
"default": {
|
||||
"icon": "icons/file_icons/file.svg"
|
||||
},
|
||||
"document": {
|
||||
"icon": "icons/file_icons/book.svg"
|
||||
},
|
||||
"elixir": {
|
||||
"icon": "icons/file_icons/elixir.svg"
|
||||
},
|
||||
"elm": {
|
||||
"icon": "icons/file_icons/elm.svg"
|
||||
},
|
||||
"erlang": {
|
||||
"icon": "icons/file_icons/erlang.svg"
|
||||
},
|
||||
"eslint": {
|
||||
"icon": "icons/file_icons/eslint.svg"
|
||||
},
|
||||
"expanded_chevron": {
|
||||
"icon": "icons/file_icons/chevron_down.svg"
|
||||
},
|
||||
"expanded_folder": {
|
||||
"icon": "icons/file_icons/folder_open.svg"
|
||||
},
|
||||
"haskell": {
|
||||
"icon": "icons/file_icons/haskell.svg"
|
||||
},
|
||||
"go": {
|
||||
"icon": "icons/file_icons/go.svg"
|
||||
},
|
||||
"image": {
|
||||
"icon": "icons/file_icons/image.svg"
|
||||
},
|
||||
"lock": {
|
||||
"icon": "icons/file_icons/lock.svg"
|
||||
},
|
||||
"log": {
|
||||
"icon": "icons/file_icons/info.svg"
|
||||
},
|
||||
"lua": {
|
||||
"icon": "icons/file_icons/lua.svg"
|
||||
},
|
||||
"ocaml": {
|
||||
"icon": "icons/file_icons/ocaml.svg"
|
||||
},
|
||||
"phoenix": {
|
||||
"icon": "icons/file_icons/phoenix.svg"
|
||||
},
|
||||
"php": {
|
||||
"icon": "icons/file_icons/php.svg"
|
||||
},
|
||||
"prettier": {
|
||||
"icon": "icons/file_icons/prettier.svg"
|
||||
},
|
||||
"prisma": {
|
||||
"icon": "icons/file_icons/prisma.svg"
|
||||
},
|
||||
"python": {
|
||||
"icon": "icons/file_icons/python.svg"
|
||||
},
|
||||
"ruby": {
|
||||
"icon": "icons/file_icons/ruby.svg"
|
||||
},
|
||||
"rust": {
|
||||
"icon": "icons/file_icons/rust.svg"
|
||||
},
|
||||
"settings": {
|
||||
"icon": "icons/file_icons/settings.svg"
|
||||
},
|
||||
"storage": {
|
||||
"icon": "icons/file_icons/database.svg"
|
||||
},
|
||||
"template": {
|
||||
"icon": "icons/file_icons/html.svg"
|
||||
},
|
||||
"terminal": {
|
||||
"icon": "icons/file_icons/terminal.svg"
|
||||
},
|
||||
"toml": {
|
||||
"icon": "icons/file_icons/toml.svg"
|
||||
},
|
||||
"typescript": {
|
||||
"icon": "icons/file_icons/typescript.svg"
|
||||
},
|
||||
"vcs": {
|
||||
"icon": "icons/file_icons/git.svg"
|
||||
},
|
||||
"video": {
|
||||
"icon": "icons/file_icons/video.svg"
|
||||
},
|
||||
"vue": {
|
||||
"icon": "icons/file_icons/vue.svg"
|
||||
}
|
||||
"audio": {
|
||||
"icon": "icons/file_icons/audio.svg"
|
||||
},
|
||||
"code": {
|
||||
"icon": "icons/file_icons/code.svg"
|
||||
},
|
||||
"collapsed_chevron": {
|
||||
"icon": "icons/file_icons/chevron_right.svg"
|
||||
},
|
||||
"collapsed_folder": {
|
||||
"icon": "icons/file_icons/folder.svg"
|
||||
},
|
||||
"css": {
|
||||
"icon": "icons/file_icons/css.svg"
|
||||
},
|
||||
"dart": {
|
||||
"icon": "icons/file_icons/dart.svg"
|
||||
},
|
||||
"default": {
|
||||
"icon": "icons/file_icons/file.svg"
|
||||
},
|
||||
"document": {
|
||||
"icon": "icons/file_icons/book.svg"
|
||||
},
|
||||
"elixir": {
|
||||
"icon": "icons/file_icons/elixir.svg"
|
||||
},
|
||||
"elm": {
|
||||
"icon": "icons/file_icons/elm.svg"
|
||||
},
|
||||
"erlang": {
|
||||
"icon": "icons/file_icons/erlang.svg"
|
||||
},
|
||||
"eslint": {
|
||||
"icon": "icons/file_icons/eslint.svg"
|
||||
},
|
||||
"expanded_chevron": {
|
||||
"icon": "icons/file_icons/chevron_down.svg"
|
||||
},
|
||||
"expanded_folder": {
|
||||
"icon": "icons/file_icons/folder_open.svg"
|
||||
},
|
||||
"font": {
|
||||
"icon": "icons/file_icons/font.svg"
|
||||
},
|
||||
"haskell": {
|
||||
"icon": "icons/file_icons/haskell.svg"
|
||||
},
|
||||
"go": {
|
||||
"icon": "icons/file_icons/go.svg"
|
||||
},
|
||||
"graphql": {
|
||||
"icon": "icons/file_icons/graphql.svg"
|
||||
},
|
||||
"image": {
|
||||
"icon": "icons/file_icons/image.svg"
|
||||
},
|
||||
"java": {
|
||||
"icon": "icons/file_icons/java.svg"
|
||||
},
|
||||
"kotlin":{
|
||||
"icon": "icons/file_icons/kotlin.svg"
|
||||
},
|
||||
"lock": {
|
||||
"icon": "icons/file_icons/lock.svg"
|
||||
},
|
||||
"bun": {
|
||||
"icon": "icons/file_icons/bun.svg"
|
||||
},
|
||||
"log": {
|
||||
"icon": "icons/file_icons/info.svg"
|
||||
},
|
||||
"lua": {
|
||||
"icon": "icons/file_icons/lua.svg"
|
||||
},
|
||||
"ocaml": {
|
||||
"icon": "icons/file_icons/ocaml.svg"
|
||||
},
|
||||
"phoenix": {
|
||||
"icon": "icons/file_icons/phoenix.svg"
|
||||
},
|
||||
"php": {
|
||||
"icon": "icons/file_icons/php.svg"
|
||||
},
|
||||
"prettier": {
|
||||
"icon": "icons/file_icons/prettier.svg"
|
||||
},
|
||||
"prisma": {
|
||||
"icon": "icons/file_icons/prisma.svg"
|
||||
},
|
||||
"python": {
|
||||
"icon": "icons/file_icons/python.svg"
|
||||
},
|
||||
"ruby": {
|
||||
"icon": "icons/file_icons/ruby.svg"
|
||||
},
|
||||
"rust": {
|
||||
"icon": "icons/file_icons/rust.svg"
|
||||
},
|
||||
"r": {
|
||||
"icon": "icons/file_icons/r.svg"
|
||||
},
|
||||
"settings": {
|
||||
"icon": "icons/file_icons/settings.svg"
|
||||
},
|
||||
"storage": {
|
||||
"icon": "icons/file_icons/database.svg"
|
||||
},
|
||||
"swift": {
|
||||
"icon": "icons/file_icons/swift.svg"
|
||||
},
|
||||
"template": {
|
||||
"icon": "icons/file_icons/html.svg"
|
||||
},
|
||||
"terraform": {
|
||||
"icon": "icons/file_icons/terraform.svg"
|
||||
},
|
||||
"terminal": {
|
||||
"icon": "icons/file_icons/terminal.svg"
|
||||
},
|
||||
"toml": {
|
||||
"icon": "icons/file_icons/toml.svg"
|
||||
},
|
||||
"typescript": {
|
||||
"icon": "icons/file_icons/typescript.svg"
|
||||
},
|
||||
"vcs": {
|
||||
"icon": "icons/file_icons/git.svg"
|
||||
},
|
||||
"video": {
|
||||
"icon": "icons/file_icons/video.svg"
|
||||
},
|
||||
"vue": {
|
||||
"icon": "icons/file_icons/vue.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
assets/icons/file_icons/font.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 16.001786 21 L 19.497321 21 M 5.997321 21 L 12 3 L 18.002679 21 M 4.502679 21 L 7.998214 21 M 14.997321 14.000893 L 9.002679 14.000893 " transform="matrix(0.486111,0,0,0.486111,1.166667,1.166667)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 559 B |
1
assets/icons/file_icons/graphql.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="14px" height="14px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="#000000"><path fill-rule="evenodd" clip-rule="evenodd" d="M50 6.90308L87.323 28.4515V71.5484L50 93.0968L12.677 71.5484V28.4515L50 6.90308ZM16.8647 30.8693V62.5251L44.2795 15.0414L16.8647 30.8693ZM50 13.5086L18.3975 68.2457H81.6025L50 13.5086ZM77.4148 72.4334H22.5852L50 88.2613L77.4148 72.4334ZM83.1353 62.5251L55.7205 15.0414L83.1353 30.8693V62.5251Z"/><circle cx="50" cy="9.3209" r="8.82"/><circle cx="85.2292" cy="29.6605" r="8.82"/><circle cx="85.2292" cy="70.3396" r="8.82"/><circle cx="50" cy="90.6791" r="8.82"/><circle cx="14.7659" cy="70.3396" r="8.82"/><circle cx="14.7659" cy="29.6605" r="8.82"/></svg>
|
||||
|
After Width: | Height: | Size: 709 B |
7
assets/icons/file_icons/java.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 10.644531 9.734375 C 9.640625 9.734375 8.824219 8.917969 8.824219 7.910156 C 8.824219 6.90625 9.640625 6.089844 10.644531 6.089844 C 11.652344 6.089844 12.46875 6.90625 12.46875 7.910156 C 12.46875 8.917969 11.652344 9.734375 10.644531 9.734375 Z M 10.644531 7 C 10.144531 7 9.734375 7.410156 9.734375 7.910156 C 9.734375 8.414062 10.144531 8.824219 10.644531 8.824219 C 11.148438 8.824219 11.558594 8.414062 11.558594 7.910156 C 11.558594 7.410156 11.148438 7 10.644531 7 Z M 7.457031 4.265625 L 6.542969 4.265625 L 6.542969 2.441406 L 7.457031 2.441406 Z M 6.089844 4.265625 L 5.175781 4.265625 L 5.175781 1.53125 L 6.089844 1.53125 Z M 4.722656 4.265625 L 3.808594 4.265625 L 3.808594 2.441406 L 4.722656 2.441406 Z M 4.722656 4.265625 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.632812 12.011719 C 3.617188 12.011719 1.988281 10.382812 1.988281 8.367188 L 1.988281 5.632812 L 9.277344 5.632812 L 9.277344 8.367188 C 9.277344 10.382812 7.648438 12.011719 5.632812 12.011719 Z M 5.632812 12.011719 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
6
assets/icons/file_icons/kotlin.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 12.125 12.125 L 1.875 12.125 L 1.875 1.875 L 12.125 1.875 L 7 7 Z M 12.125 12.125 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 385 B |
6
assets/icons/file_icons/r.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15px" height="14px" viewBox="0 0 15 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 14.0625 6.46875 C 14.0625 4.203125 11.125 2.367188 7.5 2.367188 C 3.875 2.367188 0.9375 4.203125 0.9375 6.46875 C 0.9375 8.488281 3.273438 10.160156 6.34375 10.503906 L 6.34375 11.8125 L 8.582031 11.8125 L 8.582031 10.511719 C 9.113281 10.457031 9.636719 10.359375 10.148438 10.21875 L 11.058594 11.8125 L 13.589844 11.8125 L 12.0625 9.410156 C 13.292969 8.667969 14.0625 7.625 14.0625 6.46875 Z M 3.515625 6.773438 C 3.515625 5.226562 5.75 3.96875 8.503906 3.96875 C 11.257812 3.96875 13.292969 4.828125 13.292969 6.773438 C 13.304688 7.757812 12.671875 8.644531 11.699219 9.015625 C 11.65625 8.988281 11.609375 8.964844 11.558594 8.941406 C 11.355469 8.851562 11.144531 8.78125 10.933594 8.71875 C 10.933594 8.71875 12.886719 8.582031 12.886719 6.765625 C 12.886719 4.949219 10.839844 4.914062 10.839844 4.914062 L 6.34375 4.914062 L 6.34375 9.300781 C 4.671875 8.847656 3.515625 7.886719 3.515625 6.773438 Z M 9.957031 7.582031 L 8.601562 7.582031 L 8.601562 6.410156 L 9.957031 6.410156 C 10.125 6.398438 10.292969 6.453125 10.410156 6.5625 C 10.53125 6.675781 10.597656 6.828125 10.585938 6.984375 C 10.589844 7.144531 10.527344 7.296875 10.410156 7.410156 C 10.289062 7.519531 10.128906 7.582031 9.957031 7.582031 Z M 8.582031 9.109375 L 9.183594 9.109375 C 9.300781 9.113281 9.40625 9.15625 9.484375 9.238281 C 9.578125 9.320312 9.65625 9.414062 9.722656 9.511719 C 9.34375 9.554688 8.964844 9.574219 8.582031 9.578125 Z M 8.582031 9.109375 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
6
assets/icons/file_icons/swift.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 8.269531 1.53125 C 13.078125 4.824219 11.523438 8.457031 11.523438 8.457031 C 11.523438 8.457031 12.890625 10.015625 12.335938 11.375 C 12.335938 11.375 11.773438 10.421875 10.828125 10.421875 C 9.914062 10.421875 9.378906 11.375 7.546875 11.375 C 3.460938 11.375 1.53125 7.9375 1.53125 7.9375 C 5.210938 10.375 7.722656 8.648438 7.722656 8.648438 C 6.066406 7.679688 2.539062 3.039062 2.539062 3.039062 C 5.609375 5.675781 6.9375 6.371094 6.9375 6.371094 C 6.144531 5.710938 3.921875 2.484375 3.921875 2.484375 C 5.699219 4.296875 9.230469 6.828125 9.230469 6.828125 C 10.234375 4.027344 8.269531 1.53125 8.269531 1.53125 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 926 B |
6
assets/icons/file_icons/terraform.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 8.01531L19.2375 12.1073V20.2894L12.5066 16.1992V8.01531Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0294 12.1073V20.2894L27.1563 16.1992V8.01531L20.0294 12.1073Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.58781 3.66V11.5787L11.7147 15.5381V7.61937L4.58781 3.66Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 25.04L19.2375 29V21.1348V21.0818L12.5066 17.1219V25.04Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 620 B |
@@ -55,38 +55,14 @@
|
||||
"bindings": {
|
||||
"alt-cmd-/": "search::ToggleRegex",
|
||||
"ctrl-0": "project_panel::ToggleFocus",
|
||||
"cmd-1": [
|
||||
"pane::ActivateItem",
|
||||
0
|
||||
],
|
||||
"cmd-2": [
|
||||
"pane::ActivateItem",
|
||||
1
|
||||
],
|
||||
"cmd-3": [
|
||||
"pane::ActivateItem",
|
||||
2
|
||||
],
|
||||
"cmd-4": [
|
||||
"pane::ActivateItem",
|
||||
3
|
||||
],
|
||||
"cmd-5": [
|
||||
"pane::ActivateItem",
|
||||
4
|
||||
],
|
||||
"cmd-6": [
|
||||
"pane::ActivateItem",
|
||||
5
|
||||
],
|
||||
"cmd-7": [
|
||||
"pane::ActivateItem",
|
||||
6
|
||||
],
|
||||
"cmd-8": [
|
||||
"pane::ActivateItem",
|
||||
7
|
||||
],
|
||||
"cmd-1": ["pane::ActivateItem", 0],
|
||||
"cmd-2": ["pane::ActivateItem", 1],
|
||||
"cmd-3": ["pane::ActivateItem", 2],
|
||||
"cmd-4": ["pane::ActivateItem", 3],
|
||||
"cmd-5": ["pane::ActivateItem", 4],
|
||||
"cmd-6": ["pane::ActivateItem", 5],
|
||||
"cmd-7": ["pane::ActivateItem", 6],
|
||||
"cmd-8": ["pane::ActivateItem", 7],
|
||||
"cmd-9": "pane::ActivateLastItem"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -531,7 +531,8 @@
|
||||
"alt-cmd-shift-c": "project_panel::CopyRelativePath",
|
||||
"f2": "project_panel::Rename",
|
||||
"enter": "project_panel::Rename",
|
||||
"backspace": "project_panel::Delete",
|
||||
"delete": "project_panel::Delete",
|
||||
"cmd-backspace": "project_panel::Delete",
|
||||
"alt-cmd-r": "project_panel::RevealInFinder",
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory"
|
||||
}
|
||||
|
||||
23
assets/keymaps/storybook.json
Normal file
@@ -0,0 +1,23 @@
|
||||
[
|
||||
// Standard macOS bindings
|
||||
{
|
||||
"bindings": {
|
||||
"up": "menu::SelectPrev",
|
||||
"pageup": "menu::SelectFirst",
|
||||
"shift-pageup": "menu::SelectFirst",
|
||||
"ctrl-p": "menu::SelectPrev",
|
||||
"down": "menu::SelectNext",
|
||||
"pagedown": "menu::SelectLast",
|
||||
"shift-pagedown": "menu::SelectFirst",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
"cmd-up": "menu::SelectFirst",
|
||||
"cmd-down": "menu::SelectLast",
|
||||
"enter": "menu::Confirm",
|
||||
"ctrl-enter": "menu::ShowContextMenu",
|
||||
"cmd-enter": "menu::SecondaryConfirm",
|
||||
"escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"cmd-q": "storybook::Quit"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -383,6 +383,7 @@
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"t": "vim::Tag",
|
||||
"s": "vim::Sentence",
|
||||
"'": "vim::Quotes",
|
||||
"`": "vim::BackQuotes",
|
||||
@@ -397,7 +398,8 @@
|
||||
"}": "vim::CurlyBrackets",
|
||||
"shift-b": "vim::CurlyBrackets",
|
||||
"<": "vim::AngleBrackets",
|
||||
">": "vim::AngleBrackets"
|
||||
">": "vim::AngleBrackets",
|
||||
"a": "vim::Argument"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -140,6 +140,14 @@
|
||||
// Whether to show diagnostic indicators in the scrollbar.
|
||||
"diagnostics": true
|
||||
},
|
||||
"gutter": {
|
||||
// Whether to show line numbers in the gutter.
|
||||
"line_numbers": true,
|
||||
// Whether to show code action buttons in the gutter.
|
||||
"code_actions": true,
|
||||
// Whether to show fold buttons in the gutter.
|
||||
"folds": true
|
||||
},
|
||||
// The number of lines to keep above/below the cursor when scrolling.
|
||||
"vertical_scroll_margin": 3,
|
||||
"relative_line_numbers": false,
|
||||
@@ -179,7 +187,10 @@
|
||||
// Whether to reveal it in the project panel automatically,
|
||||
// when a corresponding project entry becomes active.
|
||||
// Gitignored entries are never auto revealed.
|
||||
"auto_reveal_entries": true
|
||||
"auto_reveal_entries": true,
|
||||
/// Whether to fold directories automatically
|
||||
/// when directory has only one directory inside.
|
||||
"auto_fold_dirs": true
|
||||
},
|
||||
"collaboration_panel": {
|
||||
// Whether to show the collaboration panel button in the status bar.
|
||||
@@ -331,7 +342,9 @@
|
||||
"copilot": {
|
||||
// The set of glob patterns for which copilot should be disabled
|
||||
// in any matching file.
|
||||
"disabled_globs": [".env"]
|
||||
"disabled_globs": [
|
||||
".env"
|
||||
]
|
||||
},
|
||||
// Settings specific to journaling
|
||||
"journal": {
|
||||
@@ -440,7 +453,12 @@
|
||||
// Default directories to search for virtual environments, relative
|
||||
// to the current working directory. We recommend overriding this
|
||||
// in your project's settings, rather than globally.
|
||||
"directories": [".env", "env", ".venv", "venv"],
|
||||
"directories": [
|
||||
".env",
|
||||
"env",
|
||||
".venv",
|
||||
"venv"
|
||||
],
|
||||
// Can also be 'csh', 'fish', and `nushell`
|
||||
"activate_script": "default"
|
||||
}
|
||||
@@ -555,6 +573,10 @@
|
||||
// }
|
||||
// }
|
||||
},
|
||||
// Vim settings
|
||||
"vim": {
|
||||
"use_system_clipboard": "always"
|
||||
},
|
||||
// The server to connect to. If the environment variable
|
||||
// ZED_SERVER_URL is set, it will override this setting.
|
||||
"server_url": "https://zed.dev",
|
||||
|
||||
@@ -482,7 +482,7 @@
|
||||
"hidden": "#998b78ff",
|
||||
"hidden.background": "#4c4642ff",
|
||||
"hidden.border": "#544c48ff",
|
||||
"hint": "#8c957dff",
|
||||
"hint": "#6a695bff",
|
||||
"hint.background": "#1e2321ff",
|
||||
"hint.border": "#303a36ff",
|
||||
"ignored": "#c5b597ff",
|
||||
|
||||
@@ -17,9 +17,7 @@ futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
project.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -28,7 +28,6 @@ parking_lot.workspace = true
|
||||
parse_duration = "2.1.1"
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
regex.workspace = true
|
||||
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -362,7 +362,7 @@ impl AssistantPanel {
|
||||
move |cx: &mut BlockContext| {
|
||||
measurements.set(BlockMeasurements {
|
||||
anchor_x: cx.anchor_x,
|
||||
gutter_width: cx.gutter_width,
|
||||
gutter_width: cx.gutter_dimensions.width,
|
||||
});
|
||||
inline_assistant.clone().into_any_element()
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@ doctest = false
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
derive_more.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
rodio = { version = "0.17.1", default-features = false, features = ["wav"] }
|
||||
util.workspace = true
|
||||
|
||||
@@ -13,12 +13,12 @@ doctest = false
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
isahc.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
markdown_preview.workspace = true
|
||||
menu.workspace = true
|
||||
project.workspace = true
|
||||
release_channel.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
@@ -27,6 +27,5 @@ serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
tempfile.workspace = true
|
||||
theme.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -4,12 +4,14 @@ use anyhow::{anyhow, Context, Result};
|
||||
use client::{Client, TelemetrySettings, ZED_APP_PATH};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use db::RELEASE_CHANNEL;
|
||||
use editor::{Editor, MultiBuffer};
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
|
||||
SemanticVersion, Task, ViewContext, VisualContext, WindowContext,
|
||||
SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use isahc::AsyncBody;
|
||||
|
||||
use markdown_preview::markdown_preview_view::MarkdownPreviewView;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde_derive::Serialize;
|
||||
@@ -26,13 +28,24 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use update_notification::UpdateNotification;
|
||||
use util::http::{HttpClient, ZedHttpClient};
|
||||
use util::{
|
||||
http::{HttpClient, HttpClientWithUrl},
|
||||
ResultExt,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
|
||||
const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60);
|
||||
|
||||
actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes]);
|
||||
actions!(
|
||||
auto_update,
|
||||
[
|
||||
Check,
|
||||
DismissErrorMessage,
|
||||
ViewReleaseNotes,
|
||||
ViewReleaseNotesLocally
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct UpdateRequestBody {
|
||||
@@ -54,7 +67,7 @@ pub enum AutoUpdateStatus {
|
||||
pub struct AutoUpdater {
|
||||
status: AutoUpdateStatus,
|
||||
current_version: SemanticVersion,
|
||||
http_client: Arc<ZedHttpClient>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
pending_poll: Option<Task<Option<()>>>,
|
||||
}
|
||||
|
||||
@@ -96,7 +109,13 @@ struct GlobalAutoUpdate(Option<Model<AutoUpdater>>);
|
||||
|
||||
impl Global for GlobalAutoUpdate {}
|
||||
|
||||
pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
|
||||
#[derive(Deserialize)]
|
||||
struct ReleaseNotesBody {
|
||||
title: String,
|
||||
release_notes: String,
|
||||
}
|
||||
|
||||
pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
|
||||
AutoUpdateSetting::register(cx);
|
||||
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
@@ -105,6 +124,10 @@ pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
|
||||
workspace.register_action(|_, action, cx| {
|
||||
view_release_notes(action, cx);
|
||||
});
|
||||
|
||||
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
|
||||
view_release_notes_locally(workspace, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -158,13 +181,78 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
|
||||
let current_version = auto_updater.current_version;
|
||||
let url = &auto_updater
|
||||
.http_client
|
||||
.zed_url(&format!("/releases/{release_channel}/{current_version}"));
|
||||
.build_url(&format!("/releases/{release_channel}/{current_version}"));
|
||||
cx.open_url(&url);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||
let release_channel = ReleaseChannel::global(cx);
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
let client = client::Client::global(cx).http_client();
|
||||
let url = client.build_url(&format!(
|
||||
"/api/release_notes/{}/{}",
|
||||
release_channel.dev_name(),
|
||||
version
|
||||
));
|
||||
|
||||
let markdown = workspace
|
||||
.app_state()
|
||||
.languages
|
||||
.language_for_name("Markdown");
|
||||
|
||||
workspace
|
||||
.with_local_workspace(cx, move |_, cx| {
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let markdown = markdown.await.log_err();
|
||||
let response = client.get(&url, Default::default(), true).await;
|
||||
let Some(mut response) = response.log_err() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut body = Vec::new();
|
||||
response.body_mut().read_to_end(&mut body).await.ok();
|
||||
|
||||
let body: serde_json::Result<ReleaseNotesBody> =
|
||||
serde_json::from_slice(body.as_slice());
|
||||
|
||||
if let Ok(body) = body {
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.create_buffer("", markdown, cx))
|
||||
.expect("creating buffers on a local workspace always succeeds");
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, body.release_notes)], None, cx)
|
||||
});
|
||||
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
|
||||
let tab_description = SharedString::from(body.title.to_string());
|
||||
let editor = cx
|
||||
.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx));
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
|
||||
editor,
|
||||
workspace_handle,
|
||||
Some(tab_description),
|
||||
cx,
|
||||
);
|
||||
workspace.add_item(Box::new(view.clone()), cx);
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
|
||||
let updater = AutoUpdater::get(cx)?;
|
||||
let version = updater.read(cx).current_version;
|
||||
@@ -195,7 +283,7 @@ impl AutoUpdater {
|
||||
cx.default_global::<GlobalAutoUpdate>().0.clone()
|
||||
}
|
||||
|
||||
fn new(current_version: SemanticVersion, http_client: Arc<ZedHttpClient>) -> Self {
|
||||
fn new(current_version: SemanticVersion, http_client: Arc<HttpClientWithUrl>) -> Self {
|
||||
Self {
|
||||
status: AutoUpdateStatus::Idle,
|
||||
current_version,
|
||||
@@ -249,7 +337,7 @@ impl AutoUpdater {
|
||||
(this.http_client.clone(), this.current_version)
|
||||
})?;
|
||||
|
||||
let mut url_string = client.zed_url(&format!(
|
||||
let mut url_string = client.build_url(&format!(
|
||||
"/api/releases/latest?asset=Zed.dmg&os={}&arch={}",
|
||||
OS, ARCH
|
||||
));
|
||||
|
||||
@@ -10,15 +10,10 @@ path = "src/breadcrumbs.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
collections.workspace = true
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools = "0.10"
|
||||
language.workspace = true
|
||||
itertools.workspace = true
|
||||
outline.workspace = true
|
||||
project.workspace = true
|
||||
search.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -21,26 +21,21 @@ test-support = [
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-broadcast = "0.4"
|
||||
audio.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
image = "0.23"
|
||||
language.workspace = true
|
||||
live_kit_client.workspace = true
|
||||
log.workspace = true
|
||||
media.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -156,7 +156,7 @@ impl Room {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
connect.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if !this.read_only() {
|
||||
if this.can_use_microphone() {
|
||||
if let Some(live_kit) = &this.live_kit {
|
||||
if !live_kit.muted_by_user && !live_kit.deafened {
|
||||
return this.share_microphone(cx);
|
||||
@@ -1322,11 +1322,6 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_only(&self) -> bool {
|
||||
!(self.local_participant().role == proto::ChannelRole::Member
|
||||
|| self.local_participant().role == proto::ChannelRole::Admin)
|
||||
}
|
||||
|
||||
pub fn is_speaking(&self) -> bool {
|
||||
self.live_kit
|
||||
.as_ref()
|
||||
@@ -1337,6 +1332,22 @@ impl Room {
|
||||
self.live_kit.as_ref().map(|live_kit| live_kit.deafened)
|
||||
}
|
||||
|
||||
pub fn can_use_microphone(&self) -> bool {
|
||||
use proto::ChannelRole::*;
|
||||
match self.local_participant.role {
|
||||
Admin | Member | Talker => true,
|
||||
Guest | Banned => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_share_projects(&self) -> bool {
|
||||
use proto::ChannelRole::*;
|
||||
match self.local_participant.role {
|
||||
Admin | Member => true,
|
||||
Guest | Banned | Talker => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
|
||||
@@ -17,34 +17,18 @@ anyhow.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
db.workspace = true
|
||||
feature_flags.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
image = "0.23"
|
||||
language.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
release_channel.workspace = true
|
||||
rpc.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
sum_tree.workspace = true
|
||||
tempfile.workspace = true
|
||||
text.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tiny_http = "0.8"
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -120,7 +120,8 @@ impl ChannelMembership {
|
||||
proto::ChannelRole::Admin => 0,
|
||||
proto::ChannelRole::Member => 1,
|
||||
proto::ChannelRole::Banned => 2,
|
||||
proto::ChannelRole::Guest => 3,
|
||||
proto::ChannelRole::Talker => 3,
|
||||
proto::ChannelRole::Guest => 4,
|
||||
},
|
||||
kind_order: match self.kind {
|
||||
proto::channel_member::Kind::Member => 0,
|
||||
|
||||
@@ -94,14 +94,17 @@ impl<'a> Drop for ChannelPathsInsertGuard<'a> {
|
||||
fn channel_path_sorting_key<'a>(
|
||||
id: ChannelId,
|
||||
channels_by_id: &'a BTreeMap<ChannelId, Arc<Channel>>,
|
||||
) -> impl Iterator<Item = &str> {
|
||||
) -> impl Iterator<Item = (&str, u64)> {
|
||||
let (parent_path, name) = channels_by_id
|
||||
.get(&id)
|
||||
.map_or((&[] as &[_], None), |channel| {
|
||||
(channel.parent_path.as_slice(), Some(channel.name.as_ref()))
|
||||
(
|
||||
channel.parent_path.as_slice(),
|
||||
Some((channel.name.as_ref(), channel.id)),
|
||||
)
|
||||
});
|
||||
parent_path
|
||||
.iter()
|
||||
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_ref()))
|
||||
.filter_map(|id| Some((channels_by_id.get(id)?.name.as_ref(), *id)))
|
||||
.chain(name)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,6 @@ serde_derive.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.9"
|
||||
core-foundation.workspace = true
|
||||
core-services = "0.2"
|
||||
plist = "1.3"
|
||||
|
||||
@@ -156,6 +156,39 @@ mod linux {
|
||||
}
|
||||
}
|
||||
|
||||
// todo!("windows")
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows {
|
||||
use std::path::Path;
|
||||
|
||||
use cli::{CliRequest, CliResponse};
|
||||
use ipc_channel::ipc::{IpcReceiver, IpcSender};
|
||||
|
||||
use crate::{Bundle, InfoPlist};
|
||||
|
||||
impl Bundle {
|
||||
pub fn detect(_args_bundle_path: Option<&Path>) -> anyhow::Result<Self> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn plist(&self) -> &InfoPlist {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &Path {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn launch(&self) -> anyhow::Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn zed_version_string(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod mac_os {
|
||||
use anyhow::Context;
|
||||
|
||||
@@ -13,7 +13,7 @@ doctest = false
|
||||
test-support = ["clock/test-support", "collections/test-support", "gpui/test-support", "rpc/test-support"]
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
db.workspace = true
|
||||
|
||||
@@ -42,7 +42,7 @@ use std::{
|
||||
use telemetry::Telemetry;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
use util::http::{HttpClient, ZedHttpClient};
|
||||
use util::http::{HttpClient, HttpClientWithUrl};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
pub use rpc::*;
|
||||
@@ -153,7 +153,7 @@ impl Global for GlobalClient {}
|
||||
pub struct Client {
|
||||
id: AtomicU64,
|
||||
peer: Arc<Peer>,
|
||||
http: Arc<ZedHttpClient>,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
state: RwLock<ClientState>,
|
||||
|
||||
@@ -424,7 +424,7 @@ impl settings::Settings for TelemetrySettings {
|
||||
impl Client {
|
||||
pub fn new(
|
||||
clock: Arc<dyn SystemClock>,
|
||||
http: Arc<ZedHttpClient>,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
cx: &mut AppContext,
|
||||
) -> Arc<Self> {
|
||||
let client = Arc::new(Self {
|
||||
@@ -447,7 +447,7 @@ impl Client {
|
||||
self.id.load(std::sync::atomic::Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn http_client(&self) -> Arc<ZedHttpClient> {
|
||||
pub fn http_client(&self) -> Arc<HttpClientWithUrl> {
|
||||
self.http.clone()
|
||||
}
|
||||
|
||||
@@ -970,14 +970,14 @@ impl Client {
|
||||
}
|
||||
|
||||
async fn get_rpc_url(
|
||||
http: Arc<ZedHttpClient>,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
release_channel: Option<ReleaseChannel>,
|
||||
) -> Result<Url> {
|
||||
if let Some(url) = &*ZED_RPC_URL {
|
||||
return Url::parse(url).context("invalid rpc url");
|
||||
}
|
||||
|
||||
let mut url = http.zed_url("/rpc");
|
||||
let mut url = http.build_url("/rpc");
|
||||
if let Some(preview_param) =
|
||||
release_channel.and_then(|channel| channel.release_query_param())
|
||||
{
|
||||
@@ -1110,7 +1110,7 @@ impl Client {
|
||||
|
||||
// Open the Zed sign-in page in the user's browser, with query parameters that indicate
|
||||
// that the user is signing in from a Zed app running on the same device.
|
||||
let mut url = http.zed_url(&format!(
|
||||
let mut url = http.build_url(&format!(
|
||||
"/native_app_signin?native_app_port={}&native_app_public_key={}",
|
||||
port, public_key_string
|
||||
));
|
||||
@@ -1145,7 +1145,7 @@ impl Client {
|
||||
}
|
||||
|
||||
let post_auth_url =
|
||||
http.zed_url("/native_app_signin_succeeded");
|
||||
http.build_url("/native_app_signin_succeeded");
|
||||
req.respond(
|
||||
tiny_http::Response::empty(302).with_header(
|
||||
tiny_http::Header::from_bytes(
|
||||
@@ -1187,7 +1187,7 @@ impl Client {
|
||||
}
|
||||
|
||||
async fn authenticate_as_admin(
|
||||
http: Arc<ZedHttpClient>,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
login: String,
|
||||
mut api_token: String,
|
||||
) -> Result<Credentials> {
|
||||
|
||||
@@ -20,7 +20,7 @@ use telemetry_events::{
|
||||
EditEvent, EditorEvent, Event, EventRequestBody, EventWrapper, MemoryEvent, SettingEvent,
|
||||
};
|
||||
use tempfile::NamedTempFile;
|
||||
use util::http::{self, HttpClient, Method, ZedHttpClient};
|
||||
use util::http::{self, HttpClient, HttpClientWithUrl, Method};
|
||||
#[cfg(not(debug_assertions))]
|
||||
use util::ResultExt;
|
||||
use util::TryFutureExt;
|
||||
@@ -29,7 +29,7 @@ use self::event_coalescer::EventCoalescer;
|
||||
|
||||
pub struct Telemetry {
|
||||
clock: Arc<dyn SystemClock>,
|
||||
http_client: Arc<ZedHttpClient>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
executor: BackgroundExecutor,
|
||||
state: Arc<Mutex<TelemetryState>>,
|
||||
}
|
||||
@@ -75,7 +75,7 @@ static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
|
||||
impl Telemetry {
|
||||
pub fn new(
|
||||
clock: Arc<dyn SystemClock>,
|
||||
client: Arc<ZedHttpClient>,
|
||||
client: Arc<HttpClientWithUrl>,
|
||||
cx: &mut AppContext,
|
||||
) -> Arc<Self> {
|
||||
let release_channel =
|
||||
@@ -474,7 +474,7 @@ impl Telemetry {
|
||||
|
||||
let request = http::Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(this.http_client.zed_api_url("/telemetry/events"))
|
||||
.uri(this.http_client.build_zed_api_url("/telemetry/events"))
|
||||
.header("Content-Type", "text/plain")
|
||||
.header("x-zed-checksum", checksum)
|
||||
.body(json_bytes.into());
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add migration script here
|
||||
|
||||
CREATE UNIQUE INDEX uix_channels_parent_path_name ON channels(parent_path, name) WHERE (parent_path IS NOT NULL AND parent_path != '');
|
||||
@@ -703,11 +703,6 @@ pub struct EditEventRow {
|
||||
patch: Option<i32>,
|
||||
release_channel: String,
|
||||
|
||||
// SystemInfoBase
|
||||
os_name: String,
|
||||
os_version: Option<String>,
|
||||
architecture: String,
|
||||
|
||||
// ClientEventBase
|
||||
installation_id: Option<String>,
|
||||
// Note: This column name has a typo in the ClickHouse table.
|
||||
@@ -742,9 +737,6 @@ impl EditEventRow {
|
||||
minor: semver.map(|s| s.minor as i32),
|
||||
patch: semver.map(|s| s.patch as i32),
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone(),
|
||||
architecture: body.architecture.clone(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
|
||||
@@ -56,7 +56,7 @@ async fn get_extensions(
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
Query(params): Query<GetExtensionsParams>,
|
||||
) -> Result<Json<GetExtensionsResponse>> {
|
||||
let extensions = app.db.get_extensions(params.filter.as_deref(), 30).await?;
|
||||
let extensions = app.db.get_extensions(params.filter.as_deref(), 500).await?;
|
||||
Ok(Json(GetExtensionsResponse { data: extensions }))
|
||||
}
|
||||
|
||||
|
||||
@@ -100,8 +100,12 @@ pub enum ChannelRole {
|
||||
#[sea_orm(string_value = "member")]
|
||||
#[default]
|
||||
Member,
|
||||
/// Talker can read, but not write.
|
||||
/// They can use microphones and the channel chat
|
||||
#[sea_orm(string_value = "talker")]
|
||||
Talker,
|
||||
/// Guest can read, but not write.
|
||||
/// (thought they can use the channel chat)
|
||||
/// They can not use microphones but can use the chat.
|
||||
#[sea_orm(string_value = "guest")]
|
||||
Guest,
|
||||
/// Banned may not read.
|
||||
@@ -114,8 +118,9 @@ impl ChannelRole {
|
||||
pub fn should_override(&self, other: Self) -> bool {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin => matches!(other, Member | Banned | Guest),
|
||||
Member => matches!(other, Banned | Guest),
|
||||
Admin => matches!(other, Member | Banned | Talker | Guest),
|
||||
Member => matches!(other, Banned | Talker | Guest),
|
||||
Talker => matches!(other, Guest),
|
||||
Banned => matches!(other, Guest),
|
||||
Guest => false,
|
||||
}
|
||||
@@ -134,7 +139,7 @@ impl ChannelRole {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member => true,
|
||||
Guest => visibility == ChannelVisibility::Public,
|
||||
Guest | Talker => visibility == ChannelVisibility::Public,
|
||||
Banned => false,
|
||||
}
|
||||
}
|
||||
@@ -144,7 +149,7 @@ impl ChannelRole {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member => true,
|
||||
Guest | Banned => false,
|
||||
Guest | Talker | Banned => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,16 +157,16 @@ impl ChannelRole {
|
||||
pub fn can_only_see_public_descendants(&self) -> bool {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Guest => true,
|
||||
Guest | Talker => true,
|
||||
Admin | Member | Banned => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// True if the role can share screen/microphone/projects into rooms.
|
||||
pub fn can_publish_to_rooms(&self) -> bool {
|
||||
pub fn can_use_microphone(&self) -> bool {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member => true,
|
||||
Admin | Member | Talker => true,
|
||||
Guest | Banned => false,
|
||||
}
|
||||
}
|
||||
@@ -171,7 +176,7 @@ impl ChannelRole {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member => true,
|
||||
Guest | Banned => false,
|
||||
Talker | Guest | Banned => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +184,7 @@ impl ChannelRole {
|
||||
pub fn can_read_projects(&self) -> bool {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member | Guest => true,
|
||||
Admin | Member | Guest | Talker => true,
|
||||
Banned => false,
|
||||
}
|
||||
}
|
||||
@@ -188,7 +193,7 @@ impl ChannelRole {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member => true,
|
||||
Banned | Guest => false,
|
||||
Banned | Guest | Talker => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,6 +203,7 @@ impl From<proto::ChannelRole> for ChannelRole {
|
||||
match value {
|
||||
proto::ChannelRole::Admin => ChannelRole::Admin,
|
||||
proto::ChannelRole::Member => ChannelRole::Member,
|
||||
proto::ChannelRole::Talker => ChannelRole::Talker,
|
||||
proto::ChannelRole::Guest => ChannelRole::Guest,
|
||||
proto::ChannelRole::Banned => ChannelRole::Banned,
|
||||
}
|
||||
@@ -209,6 +215,7 @@ impl Into<proto::ChannelRole> for ChannelRole {
|
||||
match self {
|
||||
ChannelRole::Admin => proto::ChannelRole::Admin,
|
||||
ChannelRole::Member => proto::ChannelRole::Member,
|
||||
ChannelRole::Talker => proto::ChannelRole::Talker,
|
||||
ChannelRole::Guest => proto::ChannelRole::Guest,
|
||||
ChannelRole::Banned => proto::ChannelRole::Banned,
|
||||
}
|
||||
|
||||
@@ -795,6 +795,7 @@ impl Database {
|
||||
match role {
|
||||
Some(ChannelRole::Admin) => Ok(role.unwrap()),
|
||||
Some(ChannelRole::Member)
|
||||
| Some(ChannelRole::Talker)
|
||||
| Some(ChannelRole::Banned)
|
||||
| Some(ChannelRole::Guest)
|
||||
| None => Err(anyhow!(
|
||||
@@ -813,7 +814,10 @@ impl Database {
|
||||
let channel_role = self.channel_role_for_user(channel, user_id, tx).await?;
|
||||
match channel_role {
|
||||
Some(ChannelRole::Admin) | Some(ChannelRole::Member) => Ok(channel_role.unwrap()),
|
||||
Some(ChannelRole::Banned) | Some(ChannelRole::Guest) | None => Err(anyhow!(
|
||||
Some(ChannelRole::Banned)
|
||||
| Some(ChannelRole::Guest)
|
||||
| Some(ChannelRole::Talker)
|
||||
| None => Err(anyhow!(
|
||||
"user is not a channel member or channel does not exist"
|
||||
))?,
|
||||
}
|
||||
@@ -828,9 +832,10 @@ impl Database {
|
||||
) -> Result<ChannelRole> {
|
||||
let role = self.channel_role_for_user(channel, user_id, tx).await?;
|
||||
match role {
|
||||
Some(ChannelRole::Admin) | Some(ChannelRole::Member) | Some(ChannelRole::Guest) => {
|
||||
Ok(role.unwrap())
|
||||
}
|
||||
Some(ChannelRole::Admin)
|
||||
| Some(ChannelRole::Member)
|
||||
| Some(ChannelRole::Guest)
|
||||
| Some(ChannelRole::Talker) => Ok(role.unwrap()),
|
||||
Some(ChannelRole::Banned) | None => Err(anyhow!(
|
||||
"user is not a channel participant or channel does not exist"
|
||||
))?,
|
||||
|
||||
@@ -51,7 +51,7 @@ impl Database {
|
||||
if !participant
|
||||
.role
|
||||
.unwrap_or(ChannelRole::Member)
|
||||
.can_publish_to_rooms()
|
||||
.can_edit_projects()
|
||||
{
|
||||
return Err(anyhow!("guests cannot share projects"))?;
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ impl Database {
|
||||
|
||||
let called_user_role = match caller.role.unwrap_or(ChannelRole::Member) {
|
||||
ChannelRole::Admin | ChannelRole::Member => ChannelRole::Member,
|
||||
ChannelRole::Guest => ChannelRole::Guest,
|
||||
ChannelRole::Guest | ChannelRole::Talker => ChannelRole::Guest,
|
||||
ChannelRole::Banned => return Err(anyhow!("banned users cannot invite").into()),
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ use std::{
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
use tokio::signal::unix::SignalKind;
|
||||
use tower_http::trace::{self, TraceLayer};
|
||||
use tracing::Level;
|
||||
@@ -109,6 +110,7 @@ async fn main() -> Result<()> {
|
||||
.on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
axum::Server::from_tcp(listener)?
|
||||
.serve(app.into_make_service_with_connect_info::<SocketAddr>())
|
||||
.with_graceful_shutdown(async move {
|
||||
@@ -127,6 +129,10 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
// todo!("windows")
|
||||
#[cfg(windows)]
|
||||
unimplemented!();
|
||||
}
|
||||
_ => {
|
||||
Err(anyhow!(
|
||||
|
||||
@@ -28,7 +28,7 @@ use axum::{
|
||||
Extension, Router, TypedHeader,
|
||||
};
|
||||
use collections::{HashMap, HashSet};
|
||||
pub use connection_pool::ConnectionPool;
|
||||
pub use connection_pool::{ConnectionPool, ZedVersion};
|
||||
use futures::{
|
||||
channel::oneshot,
|
||||
future::{self, BoxFuture},
|
||||
@@ -558,6 +558,7 @@ impl Server {
|
||||
connection: Connection,
|
||||
address: String,
|
||||
user: User,
|
||||
zed_version: ZedVersion,
|
||||
impersonator: Option<User>,
|
||||
mut send_connection_id: Option<oneshot::Sender<ConnectionId>>,
|
||||
executor: Executor,
|
||||
@@ -599,7 +600,7 @@ impl Server {
|
||||
|
||||
{
|
||||
let mut pool = this.connection_pool.lock();
|
||||
pool.add_connection(connection_id, user_id, user.admin);
|
||||
pool.add_connection(connection_id, user_id, user.admin, zed_version);
|
||||
this.peer.send(connection_id, build_initial_contacts_update(contacts, &pool))?;
|
||||
this.peer.send(connection_id, build_update_user_channels(&channels_for_user))?;
|
||||
this.peer.send(connection_id, build_channels_update(
|
||||
@@ -879,17 +880,20 @@ pub async fn handle_websocket_request(
|
||||
.into_response();
|
||||
}
|
||||
|
||||
// the first version of zed that sent this header was 0.121.x
|
||||
if let Some(version) = app_version_header.map(|header| header.0 .0) {
|
||||
// 0.123.0 was a nightly version with incompatible collab changes
|
||||
// that were reverted.
|
||||
if version == "0.123.0".parse().unwrap() {
|
||||
return (
|
||||
StatusCode::UPGRADE_REQUIRED,
|
||||
"client must be upgraded".to_string(),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
let Some(version) = app_version_header.map(|header| ZedVersion(header.0 .0)) else {
|
||||
return (
|
||||
StatusCode::UPGRADE_REQUIRED,
|
||||
"no version header found".to_string(),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
if !version.is_supported() {
|
||||
return (
|
||||
StatusCode::UPGRADE_REQUIRED,
|
||||
"client must be upgraded".to_string(),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
|
||||
let socket_address = socket_address.to_string();
|
||||
@@ -906,6 +910,7 @@ pub async fn handle_websocket_request(
|
||||
connection,
|
||||
socket_address,
|
||||
user,
|
||||
version,
|
||||
impersonator.0,
|
||||
None,
|
||||
Executor::Production,
|
||||
@@ -1311,6 +1316,22 @@ async fn set_room_participant_role(
|
||||
response: Response<proto::SetRoomParticipantRole>,
|
||||
session: Session,
|
||||
) -> Result<()> {
|
||||
let user_id = UserId::from_proto(request.user_id);
|
||||
let role = ChannelRole::from(request.role());
|
||||
|
||||
if role == ChannelRole::Talker {
|
||||
let pool = session.connection_pool().await;
|
||||
|
||||
for connection in pool.user_connections(user_id) {
|
||||
if !connection.zed_version.supports_talker_role() {
|
||||
Err(anyhow!(
|
||||
"This user is on zed {} which does not support unmute",
|
||||
connection.zed_version
|
||||
))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (live_kit_room, can_publish) = {
|
||||
let room = session
|
||||
.db()
|
||||
@@ -1318,13 +1339,13 @@ async fn set_room_participant_role(
|
||||
.set_room_participant_role(
|
||||
session.user_id,
|
||||
RoomId::from_proto(request.room_id),
|
||||
UserId::from_proto(request.user_id),
|
||||
ChannelRole::from(request.role()),
|
||||
user_id,
|
||||
role,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let live_kit_room = room.live_kit_room.clone();
|
||||
let can_publish = ChannelRole::from(request.role()).can_publish_to_rooms();
|
||||
let can_publish = ChannelRole::from(request.role()).can_use_microphone();
|
||||
room_updated(&room, &session.peer);
|
||||
(live_kit_room, can_publish)
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ use collections::{BTreeMap, HashSet};
|
||||
use rpc::ConnectionId;
|
||||
use serde::Serialize;
|
||||
use tracing::instrument;
|
||||
use util::SemanticVersion;
|
||||
|
||||
#[derive(Default, Serialize)]
|
||||
pub struct ConnectionPool {
|
||||
@@ -16,10 +17,30 @@ struct ConnectedUser {
|
||||
connection_ids: HashSet<ConnectionId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ZedVersion(pub SemanticVersion);
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Display for ZedVersion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ZedVersion {
|
||||
pub fn is_supported(&self) -> bool {
|
||||
self.0 != SemanticVersion::new(0, 123, 0)
|
||||
}
|
||||
pub fn supports_talker_role(&self) -> bool {
|
||||
self.0 >= SemanticVersion::new(0, 125, 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Connection {
|
||||
pub user_id: UserId,
|
||||
pub admin: bool,
|
||||
pub zed_version: ZedVersion,
|
||||
}
|
||||
|
||||
impl ConnectionPool {
|
||||
@@ -29,9 +50,21 @@ impl ConnectionPool {
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub fn add_connection(&mut self, connection_id: ConnectionId, user_id: UserId, admin: bool) {
|
||||
self.connections
|
||||
.insert(connection_id, Connection { user_id, admin });
|
||||
pub fn add_connection(
|
||||
&mut self,
|
||||
connection_id: ConnectionId,
|
||||
user_id: UserId,
|
||||
admin: bool,
|
||||
zed_version: ZedVersion,
|
||||
) {
|
||||
self.connections.insert(
|
||||
connection_id,
|
||||
Connection {
|
||||
user_id,
|
||||
admin,
|
||||
zed_version,
|
||||
},
|
||||
);
|
||||
let connected_user = self.connected_users.entry(user_id).or_default();
|
||||
connected_user.connection_ids.insert(connection_id);
|
||||
}
|
||||
@@ -57,6 +90,19 @@ impl ConnectionPool {
|
||||
self.connections.values()
|
||||
}
|
||||
|
||||
pub fn user_connections(&self, user_id: UserId) -> impl Iterator<Item = &Connection> + '_ {
|
||||
self.connected_users
|
||||
.get(&user_id)
|
||||
.into_iter()
|
||||
.map(|state| {
|
||||
state
|
||||
.connection_ids
|
||||
.iter()
|
||||
.flat_map(|cid| self.connections.get(cid))
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn user_connection_ids(&self, user_id: UserId) -> impl Iterator<Item = ConnectionId> + '_ {
|
||||
self.connected_users
|
||||
.get(&user_id)
|
||||
|
||||
@@ -104,7 +104,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
|
||||
});
|
||||
assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
|
||||
assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx)));
|
||||
assert!(room_b.read_with(cx_b, |room, _| room.read_only()));
|
||||
assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));
|
||||
assert!(room_b
|
||||
.update(cx_b, |room, cx| room.share_microphone(cx))
|
||||
.await
|
||||
@@ -130,7 +130,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
|
||||
assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx)));
|
||||
|
||||
// B sees themselves as muted, and can unmute.
|
||||
assert!(room_b.read_with(cx_b, |room, _| !room.read_only()));
|
||||
assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));
|
||||
room_b.read_with(cx_b, |room, _| assert!(room.is_muted()));
|
||||
room_b.update(cx_b, |room, cx| room.toggle_mute(cx));
|
||||
cx_a.run_until_parked();
|
||||
@@ -223,7 +223,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
let room_b = cx_b
|
||||
.read(ActiveCall::global)
|
||||
.update(cx_b, |call, _| call.room().unwrap().clone());
|
||||
assert!(room_b.read_with(cx_b, |room, _| room.read_only()));
|
||||
assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));
|
||||
|
||||
// A tries to grant write access to B, but cannot because B has not
|
||||
// yet signed the zed CLA.
|
||||
@@ -240,7 +240,26 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
.await
|
||||
.unwrap_err();
|
||||
cx_a.run_until_parked();
|
||||
assert!(room_b.read_with(cx_b, |room, _| room.read_only()));
|
||||
assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects()));
|
||||
assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));
|
||||
|
||||
// A tries to grant write access to B, but cannot because B has not
|
||||
// yet signed the zed CLA.
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| {
|
||||
call.room().unwrap().update(cx, |room, cx| {
|
||||
room.set_participant_role(
|
||||
client_b.user_id().unwrap(),
|
||||
proto::ChannelRole::Talker,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a.run_until_parked();
|
||||
assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects()));
|
||||
assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));
|
||||
|
||||
// User B signs the zed CLA.
|
||||
server
|
||||
@@ -264,5 +283,6 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a.run_until_parked();
|
||||
assert!(room_b.read_with(cx_b, |room, _| !room.read_only()));
|
||||
assert!(room_b.read_with(cx_b, |room, _| room.can_share_projects()));
|
||||
assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
db::{tests::TestDb, NewUserParams, UserId},
|
||||
executor::Executor,
|
||||
rpc::{Server, CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
|
||||
rpc::{Server, ZedVersion, CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
|
||||
AppState, Config,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
@@ -38,7 +38,7 @@ use std::{
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use util::http::FakeHttpClient;
|
||||
use util::{http::FakeHttpClient, SemanticVersion};
|
||||
use workspace::{Workspace, WorkspaceStore};
|
||||
|
||||
pub struct TestServer {
|
||||
@@ -233,6 +233,7 @@ impl TestServer {
|
||||
server_conn,
|
||||
client_name,
|
||||
user,
|
||||
ZedVersion(SemanticVersion::new(1, 0, 0)),
|
||||
None,
|
||||
Some(connection_id_tx),
|
||||
Executor::Deterministic(cx.background_executor().clone()),
|
||||
|
||||
@@ -34,19 +34,17 @@ clock.workspace = true
|
||||
collections.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
extensions_ui.workspace = true
|
||||
feedback.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
notifications.workspace = true
|
||||
parking_lot.workspace = true
|
||||
picker.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
recent_projects.workspace = true
|
||||
rich_text.workspace = true
|
||||
@@ -60,13 +58,13 @@ smallvec.workspace = true
|
||||
story = { workspace = true, optional = true }
|
||||
theme.workspace = true
|
||||
theme_selector.workspace = true
|
||||
time_format.workspace = true
|
||||
time.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
vcs_menu.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
sys-locale.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -7,9 +7,9 @@ use collections::HashMap;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, CursorStyle,
|
||||
DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight,
|
||||
HighlightStyle, ListOffset, ListScrollEvent, ListState, Model, Render, StyledText,
|
||||
actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, ClipboardItem,
|
||||
CursorStyle, DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle,
|
||||
FontWeight, HighlightStyle, ListOffset, ListScrollEvent, ListState, Model, Render, StyledText,
|
||||
Subscription, Task, View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
@@ -488,11 +488,10 @@ impl ChatPanel {
|
||||
.child(Label::new(message.sender.github_login.clone())),
|
||||
)
|
||||
.child(
|
||||
Label::new(format_timestamp(
|
||||
Label::new(time_format::format_localized_timestamp(
|
||||
OffsetDateTime::now_utc(),
|
||||
message.timestamp,
|
||||
self.local_timezone,
|
||||
None,
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
@@ -629,6 +628,20 @@ impl ChatPanel {
|
||||
})
|
||||
}),
|
||||
)
|
||||
.entry(
|
||||
"Copy message text",
|
||||
None,
|
||||
cx.handler_for(&this, move |this, cx| {
|
||||
this.active_chat().map(|active_chat| {
|
||||
if let Some(message) =
|
||||
active_chat.read(cx).find_loaded_message(message_id)
|
||||
{
|
||||
let text = message.body.clone();
|
||||
cx.write_to_clipboard(ClipboardItem::new(text))
|
||||
}
|
||||
});
|
||||
}),
|
||||
)
|
||||
.when(can_delete_message, move |menu| {
|
||||
menu.entry(
|
||||
"Delete message",
|
||||
@@ -959,94 +972,13 @@ impl Panel for ChatPanel {
|
||||
|
||||
impl EventEmitter<PanelEvent> for ChatPanel {}
|
||||
|
||||
fn is_12_hour_clock(locale: String) -> bool {
|
||||
[
|
||||
"es-MX", "es-CO", "es-SV", "es-NI",
|
||||
"es-HN", // Mexico, Colombia, El Salvador, Nicaragua, Honduras
|
||||
"en-US", "en-CA", "en-AU", "en-NZ", // U.S, Canada, Australia, New Zealand
|
||||
"ar-SA", "ar-EG", "ar-JO", // Saudi Arabia, Egypt, Jordan
|
||||
"en-IN", "hi-IN", // India, Hindu
|
||||
"en-PK", "ur-PK", // Pakistan, Urdu
|
||||
"en-PH", "fil-PH", // Philippines, Filipino
|
||||
"bn-BD", "ccp-BD", // Bangladesh, Chakma
|
||||
"en-IE", "ga-IE", // Ireland, Irish
|
||||
"en-MY", "ms-MY", // Malaysia, Malay
|
||||
]
|
||||
.contains(&locale.as_str())
|
||||
}
|
||||
|
||||
fn format_timestamp(
|
||||
reference: OffsetDateTime,
|
||||
timestamp: OffsetDateTime,
|
||||
timezone: UtcOffset,
|
||||
locale: Option<String>,
|
||||
) -> String {
|
||||
let locale = match locale {
|
||||
Some(locale) => locale,
|
||||
None => sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")),
|
||||
};
|
||||
let timestamp_local = timestamp.to_offset(timezone);
|
||||
let timestamp_local_hour = timestamp_local.hour();
|
||||
let timestamp_local_minute = timestamp_local.minute();
|
||||
|
||||
let (hour, meridiem) = if is_12_hour_clock(locale) {
|
||||
let meridiem = if timestamp_local_hour >= 12 {
|
||||
"pm"
|
||||
} else {
|
||||
"am"
|
||||
};
|
||||
|
||||
let hour_12 = match timestamp_local_hour {
|
||||
0 => 12, // Midnight
|
||||
13..=23 => timestamp_local_hour - 12, // PM hours
|
||||
_ => timestamp_local_hour, // AM hours
|
||||
};
|
||||
|
||||
(hour_12, Some(meridiem))
|
||||
} else {
|
||||
(timestamp_local_hour, None)
|
||||
};
|
||||
|
||||
let formatted_time = match meridiem {
|
||||
Some(meridiem) => format!("{:02}:{:02} {}", hour, timestamp_local_minute, meridiem),
|
||||
None => format!("{:02}:{:02}", hour, timestamp_local_minute),
|
||||
};
|
||||
|
||||
let reference_local = reference.to_offset(timezone);
|
||||
let reference_local_date = reference_local.date();
|
||||
let timestamp_local_date = timestamp_local.date();
|
||||
|
||||
if timestamp_local_date == reference_local_date {
|
||||
return formatted_time;
|
||||
}
|
||||
|
||||
if reference_local_date.previous_day() == Some(timestamp_local_date) {
|
||||
return format!("yesterday at {}", formatted_time);
|
||||
}
|
||||
|
||||
match meridiem {
|
||||
Some(_) => format!(
|
||||
"{:02}/{:02}/{}",
|
||||
timestamp_local_date.month() as u32,
|
||||
timestamp_local_date.day(),
|
||||
timestamp_local_date.year()
|
||||
),
|
||||
None => format!(
|
||||
"{:02}/{:02}/{}",
|
||||
timestamp_local_date.day(),
|
||||
timestamp_local_date.month() as u32,
|
||||
timestamp_local_date.year()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::HighlightStyle;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rich_text::Highlight;
|
||||
use time::{Date, OffsetDateTime, Time, UtcOffset};
|
||||
use time::OffsetDateTime;
|
||||
use util::test::marked_text_ranges;
|
||||
|
||||
#[gpui::test]
|
||||
@@ -1197,150 +1129,4 @@ mod tests {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_locale() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 16, 45, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 12, 15, 30, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(
|
||||
reference,
|
||||
timestamp,
|
||||
test_timezone(),
|
||||
Some(String::from("en-GB"))
|
||||
),
|
||||
"15:30"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_today() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 16, 45, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 12, 15, 30, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(
|
||||
reference,
|
||||
timestamp,
|
||||
test_timezone(),
|
||||
Some(String::from("en-US"))
|
||||
),
|
||||
"03:30 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 11, 9, 0, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(
|
||||
reference,
|
||||
timestamp,
|
||||
test_timezone(),
|
||||
Some(String::from("en-US"))
|
||||
),
|
||||
"yesterday at 09:00 am"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday_less_than_24_hours_ago() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 19, 59, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 11, 20, 0, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(
|
||||
reference,
|
||||
timestamp,
|
||||
test_timezone(),
|
||||
Some(String::from("en-US"))
|
||||
),
|
||||
"yesterday at 08:00 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday_more_than_24_hours_ago() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 19, 59, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 11, 18, 0, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(
|
||||
reference,
|
||||
timestamp,
|
||||
test_timezone(),
|
||||
Some(String::from("en-US"))
|
||||
),
|
||||
"yesterday at 06:00 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday_over_midnight() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 0, 5, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 11, 23, 55, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(
|
||||
reference,
|
||||
timestamp,
|
||||
test_timezone(),
|
||||
Some(String::from("en-US"))
|
||||
),
|
||||
"yesterday at 11:55 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday_over_month() {
|
||||
let reference = create_offset_datetime(1990, 4, 2, 9, 0, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 1, 20, 0, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(
|
||||
reference,
|
||||
timestamp,
|
||||
test_timezone(),
|
||||
Some(String::from("en-US"))
|
||||
),
|
||||
"yesterday at 08:00 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_before_yesterday() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 10, 20, 20, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(
|
||||
reference,
|
||||
timestamp,
|
||||
test_timezone(),
|
||||
Some(String::from("en-US"))
|
||||
),
|
||||
"04/10/1990"
|
||||
);
|
||||
}
|
||||
|
||||
fn test_timezone() -> UtcOffset {
|
||||
UtcOffset::from_hms(0, 0, 0).expect("Valid timezone offset")
|
||||
}
|
||||
|
||||
fn create_offset_datetime(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> OffsetDateTime {
|
||||
let date =
|
||||
Date::from_calendar_date(year, time::Month::try_from(month).unwrap(), day).unwrap();
|
||||
let time = Time::from_hms(hour, minute, second).unwrap();
|
||||
date.with_time(time).assume_utc() // Assume UTC for simplicity
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ use std::{mem, sync::Arc};
|
||||
use theme::{ActiveTheme, ThemeSettings};
|
||||
use ui::{
|
||||
prelude::*, tooltip_container, Avatar, AvatarAvailabilityIndicator, Button, Color, ContextMenu,
|
||||
Icon, IconButton, IconName, IconSize, Label, ListHeader, ListItem, Tooltip,
|
||||
Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem, Tooltip,
|
||||
};
|
||||
use util::{maybe, ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
@@ -854,6 +854,10 @@ impl CollabPanel {
|
||||
.into_any_element()
|
||||
} else if role == proto::ChannelRole::Guest {
|
||||
Label::new("Guest").color(Color::Muted).into_any_element()
|
||||
} else if role == proto::ChannelRole::Talker {
|
||||
Label::new("Mic only")
|
||||
.color(Color::Muted)
|
||||
.into_any_element()
|
||||
} else {
|
||||
div().into_any_element()
|
||||
})
|
||||
@@ -959,6 +963,8 @@ impl CollabPanel {
|
||||
is_selected: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
let has_channel_buffer_changed = channel_store.has_channel_buffer_changed(channel_id);
|
||||
ListItem::new("channel-notes")
|
||||
.selected(is_selected)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
@@ -966,9 +972,19 @@ impl CollabPanel {
|
||||
}))
|
||||
.start_slot(
|
||||
h_flex()
|
||||
.relative()
|
||||
.gap_1()
|
||||
.child(render_tree_branch(false, true, cx))
|
||||
.child(IconButton::new(0, IconName::File)),
|
||||
.child(IconButton::new(0, IconName::File))
|
||||
.children(has_channel_buffer_changed.then(|| {
|
||||
div()
|
||||
.w_1p5()
|
||||
.z_index(1)
|
||||
.absolute()
|
||||
.right(px(2.))
|
||||
.top(px(2.))
|
||||
.child(Indicator::dot().color(Color::Info))
|
||||
})),
|
||||
)
|
||||
.child(Label::new("notes"))
|
||||
.tooltip(move |cx| Tooltip::text("Open Channel Notes", cx))
|
||||
@@ -980,6 +996,8 @@ impl CollabPanel {
|
||||
is_selected: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
let has_messages_notification = channel_store.has_new_messages(channel_id);
|
||||
ListItem::new("channel-chat")
|
||||
.selected(is_selected)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
@@ -987,9 +1005,19 @@ impl CollabPanel {
|
||||
}))
|
||||
.start_slot(
|
||||
h_flex()
|
||||
.relative()
|
||||
.gap_1()
|
||||
.child(render_tree_branch(false, false, cx))
|
||||
.child(IconButton::new(0, IconName::MessageBubbles)),
|
||||
.child(IconButton::new(0, IconName::MessageBubbles))
|
||||
.children(has_messages_notification.then(|| {
|
||||
div()
|
||||
.w_1p5()
|
||||
.z_index(1)
|
||||
.absolute()
|
||||
.right(px(2.))
|
||||
.top(px(4.))
|
||||
.child(Indicator::dot().color(Color::Info))
|
||||
})),
|
||||
)
|
||||
.child(Label::new("chat"))
|
||||
.tooltip(move |cx| Tooltip::text("Open Chat", cx))
|
||||
@@ -1013,13 +1041,38 @@ impl CollabPanel {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let this = cx.view().clone();
|
||||
if !(role == proto::ChannelRole::Guest || role == proto::ChannelRole::Member) {
|
||||
if !(role == proto::ChannelRole::Guest
|
||||
|| role == proto::ChannelRole::Talker
|
||||
|| role == proto::ChannelRole::Member)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let context_menu = ContextMenu::build(cx, |context_menu, cx| {
|
||||
let context_menu = ContextMenu::build(cx, |mut context_menu, cx| {
|
||||
if role == proto::ChannelRole::Guest {
|
||||
context_menu.entry(
|
||||
context_menu = context_menu.entry(
|
||||
"Grant Mic Access",
|
||||
None,
|
||||
cx.handler_for(&this, move |_, cx| {
|
||||
ActiveCall::global(cx)
|
||||
.update(cx, |call, cx| {
|
||||
let Some(room) = call.room() else {
|
||||
return Task::ready(Ok(()));
|
||||
};
|
||||
room.update(cx, |room, cx| {
|
||||
room.set_participant_role(
|
||||
user_id,
|
||||
proto::ChannelRole::Talker,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.detach_and_prompt_err("Failed to grant mic access", cx, |_, _| None)
|
||||
}),
|
||||
);
|
||||
}
|
||||
if role == proto::ChannelRole::Guest || role == proto::ChannelRole::Talker {
|
||||
context_menu = context_menu.entry(
|
||||
"Grant Write Access",
|
||||
None,
|
||||
cx.handler_for(&this, move |_, cx| {
|
||||
@@ -1043,10 +1096,16 @@ impl CollabPanel {
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
} else if role == proto::ChannelRole::Member {
|
||||
context_menu.entry(
|
||||
"Revoke Write Access",
|
||||
);
|
||||
}
|
||||
if role == proto::ChannelRole::Member || role == proto::ChannelRole::Talker {
|
||||
let label = if role == proto::ChannelRole::Talker {
|
||||
"Mute"
|
||||
} else {
|
||||
"Revoke Access"
|
||||
};
|
||||
context_menu = context_menu.entry(
|
||||
label,
|
||||
None,
|
||||
cx.handler_for(&this, move |_, cx| {
|
||||
ActiveCall::global(cx)
|
||||
@@ -1062,12 +1121,12 @@ impl CollabPanel {
|
||||
)
|
||||
})
|
||||
})
|
||||
.detach_and_prompt_err("Failed to revoke write access", cx, |_, _| None)
|
||||
.detach_and_prompt_err("Failed to revoke access", cx, |_, _| None)
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
unreachable!()
|
||||
);
|
||||
}
|
||||
|
||||
context_menu
|
||||
});
|
||||
|
||||
cx.focus_view(&context_menu);
|
||||
@@ -2490,13 +2549,26 @@ impl CollabPanel {
|
||||
},
|
||||
))
|
||||
.start_slot(
|
||||
Icon::new(if is_public {
|
||||
IconName::Public
|
||||
} else {
|
||||
IconName::Hash
|
||||
})
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
div()
|
||||
.relative()
|
||||
.child(
|
||||
Icon::new(if is_public {
|
||||
IconName::Public
|
||||
} else {
|
||||
IconName::Hash
|
||||
})
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.children(has_notes_notification.then(|| {
|
||||
div()
|
||||
.w_1p5()
|
||||
.z_index(1)
|
||||
.absolute()
|
||||
.right(px(-1.))
|
||||
.top(px(-1.))
|
||||
.child(Indicator::dot().color(Color::Info))
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -2530,9 +2602,7 @@ impl CollabPanel {
|
||||
this.join_channel_chat(channel_id, cx)
|
||||
}))
|
||||
.tooltip(|cx| Tooltip::text("Open channel chat", cx))
|
||||
.when(!has_messages_notification, |this| {
|
||||
this.visible_on_hover("")
|
||||
}),
|
||||
.visible_on_hover(""),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("channel_notes", IconName::File)
|
||||
@@ -2548,9 +2618,7 @@ impl CollabPanel {
|
||||
this.open_channel_notes(channel_id, cx)
|
||||
}))
|
||||
.tooltip(|cx| Tooltip::text("Open channel notes", cx))
|
||||
.when(!has_notes_notification, |this| {
|
||||
this.visible_on_hover("")
|
||||
}),
|
||||
.visible_on_hover(""),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -2560,6 +2628,7 @@ impl CollabPanel {
|
||||
cx.new_view(|_| JoinChannelTooltip {
|
||||
channel_store: channel_store.clone(),
|
||||
channel_id,
|
||||
has_notes_notification,
|
||||
})
|
||||
.into()
|
||||
}
|
||||
@@ -2845,17 +2914,25 @@ impl Render for DraggedChannelView {
|
||||
struct JoinChannelTooltip {
|
||||
channel_store: Model<ChannelStore>,
|
||||
channel_id: ChannelId,
|
||||
has_notes_notification: bool,
|
||||
}
|
||||
|
||||
impl Render for JoinChannelTooltip {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
tooltip_container(cx, |div, cx| {
|
||||
tooltip_container(cx, |container, cx| {
|
||||
let participants = self
|
||||
.channel_store
|
||||
.read(cx)
|
||||
.channel_participants(self.channel_id);
|
||||
|
||||
div.child(Label::new("Join Channel"))
|
||||
container
|
||||
.child(Label::new("Join channel"))
|
||||
.children(self.has_notes_notification.then(|| {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Indicator::dot().color(Color::Info))
|
||||
.child(Label::new("Unread notes"))
|
||||
}))
|
||||
.children(participants.iter().map(|participant| {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
|
||||
@@ -266,7 +266,7 @@ pub struct ChannelModalDelegate {
|
||||
impl PickerDelegate for ChannelModalDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Search collaborator by username...".into()
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Search collaborator by username...".into()
|
||||
}
|
||||
|
||||
|
||||
@@ -187,9 +187,10 @@ impl Render for CollabTitlebarItem {
|
||||
let is_muted = room.is_muted();
|
||||
let is_deafened = room.is_deafened().unwrap_or(false);
|
||||
let is_screen_sharing = room.is_screen_sharing();
|
||||
let read_only = room.read_only();
|
||||
let can_use_microphone = room.can_use_microphone();
|
||||
let can_share_projects = room.can_share_projects();
|
||||
|
||||
this.when(is_local && !read_only, |this| {
|
||||
this.when(is_local && can_share_projects, |this| {
|
||||
this.child(
|
||||
Button::new(
|
||||
"toggle_sharing",
|
||||
@@ -235,7 +236,7 @@ impl Render for CollabTitlebarItem {
|
||||
)
|
||||
.pr_2(),
|
||||
)
|
||||
.when(!read_only, |this| {
|
||||
.when(can_use_microphone, |this| {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
"mute-microphone",
|
||||
@@ -276,7 +277,7 @@ impl Render for CollabTitlebarItem {
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(is_deafened)
|
||||
.tooltip(move |cx| {
|
||||
if !read_only {
|
||||
if can_use_microphone {
|
||||
Tooltip::with_meta(
|
||||
"Deafen Audio",
|
||||
None,
|
||||
@@ -289,7 +290,7 @@ impl Render for CollabTitlebarItem {
|
||||
})
|
||||
.on_click(move |_, cx| crate::toggle_deafen(&Default::default(), cx)),
|
||||
)
|
||||
.when(!read_only, |this| {
|
||||
.when(can_share_projects, |this| {
|
||||
this.child(
|
||||
IconButton::new("screen-share", ui::IconName::Screen)
|
||||
.style(ButtonStyle::Subtle)
|
||||
@@ -421,14 +422,20 @@ impl CollabTitlebarItem {
|
||||
worktree.root_name()
|
||||
});
|
||||
|
||||
names.next().unwrap_or("")
|
||||
names.next()
|
||||
};
|
||||
let is_project_selected = name.is_some();
|
||||
let name = if let Some(name) = name {
|
||||
util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH)
|
||||
} else {
|
||||
"Open recent project".to_string()
|
||||
};
|
||||
|
||||
let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
|
||||
let workspace = self.workspace.clone();
|
||||
popover_menu("project_name_trigger")
|
||||
.trigger(
|
||||
Button::new("project_name_trigger", name)
|
||||
.when(!is_project_selected, |b| b.color(Color::Muted))
|
||||
.style(ButtonStyle::Subtle)
|
||||
.label_size(LabelSize::Small)
|
||||
.tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
|
||||
@@ -689,6 +696,7 @@ impl CollabTitlebarItem {
|
||||
.menu(|cx| {
|
||||
ContextMenu::build(cx, |menu, _| {
|
||||
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||
.action("Extensions", extensions_ui::Extensions.boxed_clone())
|
||||
.action("Theme", theme_selector::Toggle.boxed_clone())
|
||||
.separator()
|
||||
.action("Share Feedback", feedback::GiveFeedback.boxed_clone())
|
||||
@@ -714,6 +722,7 @@ impl CollabTitlebarItem {
|
||||
ContextMenu::build(cx, |menu, _| {
|
||||
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||
.action("Theme", theme_selector::Toggle.boxed_clone())
|
||||
.action("Extensions", extensions_ui::Extensions.boxed_clone())
|
||||
.separator()
|
||||
.action("Share Feedback", feedback::GiveFeedback.boxed_clone())
|
||||
})
|
||||
|
||||
@@ -14,6 +14,6 @@ path = "src/color.rs"
|
||||
doctest = true
|
||||
|
||||
[dependencies]
|
||||
itertools = { version = "0.11.0", optional = true }
|
||||
itertools = { workspace = true, optional = true }
|
||||
palette = "0.7.3"
|
||||
story = { workspace = true, optional = true }
|
||||
|
||||
@@ -13,12 +13,11 @@ doctest = false
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
# HACK: We're only depending on `copilot` here for `CommandPaletteFilter`. See the attached comment on that type.
|
||||
copilot.workspace = true
|
||||
editor.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
picker.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
release_channel.workspace = true
|
||||
serde.workspace = true
|
||||
@@ -28,7 +27,6 @@ ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
postage.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
|
||||
@@ -6,7 +6,9 @@ use std::{
|
||||
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::HashMap;
|
||||
use copilot::CommandPaletteFilter;
|
||||
use command_palette_hooks::{
|
||||
CommandInterceptResult, CommandPaletteFilter, CommandPaletteInterceptor,
|
||||
};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Global,
|
||||
@@ -101,18 +103,6 @@ impl Render for CommandPalette {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandPaletteInterceptor(
|
||||
pub Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>,
|
||||
);
|
||||
|
||||
impl Global for CommandPaletteInterceptor {}
|
||||
|
||||
pub struct CommandInterceptResult {
|
||||
pub action: Box<dyn Action>,
|
||||
pub string: String,
|
||||
pub positions: Vec<usize>,
|
||||
}
|
||||
|
||||
pub struct CommandPaletteDelegate {
|
||||
command_palette: WeakView<CommandPalette>,
|
||||
all_commands: Vec<Command>,
|
||||
@@ -231,7 +221,7 @@ impl CommandPaletteDelegate {
|
||||
impl PickerDelegate for CommandPaletteDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Execute a command...".into()
|
||||
}
|
||||
|
||||
|
||||
14
crates/command_palette_hooks/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "command_palette_hooks"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lib]
|
||||
path = "src/command_palette_hooks.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
collections.workspace = true
|
||||
gpui.workspace = true
|
||||
24
crates/command_palette_hooks/src/command_palette_hooks.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use std::any::TypeId;
|
||||
|
||||
use collections::HashSet;
|
||||
use gpui::{Action, AppContext, Global};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CommandPaletteFilter {
|
||||
pub hidden_namespaces: HashSet<&'static str>,
|
||||
pub hidden_action_types: HashSet<TypeId>,
|
||||
}
|
||||
|
||||
impl Global for CommandPaletteFilter {}
|
||||
|
||||
pub struct CommandPaletteInterceptor(
|
||||
pub Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>,
|
||||
);
|
||||
|
||||
impl Global for CommandPaletteInterceptor {}
|
||||
|
||||
pub struct CommandInterceptResult {
|
||||
pub action: Box<dyn Action>,
|
||||
pub string: String,
|
||||
pub positions: Vec<usize>,
|
||||
}
|
||||
@@ -22,8 +22,9 @@ test-support = [
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-tar = "0.4.2"
|
||||
async-tar.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
|
||||
@@ -3,6 +3,7 @@ use anyhow::{anyhow, Context as _, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use collections::{HashMap, HashSet};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Global, Model,
|
||||
@@ -32,17 +33,6 @@ use util::{
|
||||
ResultExt,
|
||||
};
|
||||
|
||||
// HACK: This type is only defined in `copilot` since it is the earliest ancestor
|
||||
// of the crates that use it.
|
||||
//
|
||||
// This is not great. Let's find a better place for it to live.
|
||||
#[derive(Default)]
|
||||
pub struct CommandPaletteFilter {
|
||||
pub hidden_namespaces: HashSet<&'static str>,
|
||||
pub hidden_action_types: HashSet<TypeId>,
|
||||
}
|
||||
|
||||
impl Global for CommandPaletteFilter {}
|
||||
actions!(
|
||||
copilot,
|
||||
[
|
||||
|
||||
@@ -14,22 +14,16 @@ test-support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
collections.workspace = true
|
||||
gpui.workspace = true
|
||||
indoc.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
release_channel.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
smol.workspace = true
|
||||
sqlez.workspace = true
|
||||
sqlez_macros.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
tempfile.workspace = true
|
||||
|
||||
@@ -885,7 +885,7 @@ mod tests {
|
||||
use super::*;
|
||||
use editor::{
|
||||
display_map::{BlockContext, TransformBlock},
|
||||
DisplayPoint,
|
||||
DisplayPoint, GutterDimensions,
|
||||
};
|
||||
use gpui::{px, TestAppContext, VisualTestContext, WindowContext};
|
||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
|
||||
@@ -1584,7 +1584,6 @@ mod tests {
|
||||
}
|
||||
|
||||
fn editor_blocks(editor: &View<Editor>, cx: &mut WindowContext) -> Vec<(u32, SharedString)> {
|
||||
let editor_view = editor.clone();
|
||||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
snapshot
|
||||
@@ -1593,19 +1592,16 @@ mod tests {
|
||||
.filter_map(|(ix, (row, block))| {
|
||||
let name: SharedString = match block {
|
||||
TransformBlock::Custom(block) => cx.with_element_context({
|
||||
let editor_view = editor_view.clone();
|
||||
|cx| -> Option<SharedString> {
|
||||
block
|
||||
.render(&mut BlockContext {
|
||||
context: cx,
|
||||
anchor_x: px(0.),
|
||||
gutter_padding: px(0.),
|
||||
gutter_width: px(0.),
|
||||
gutter_dimensions: &GutterDimensions::default(),
|
||||
line_height: px(0.),
|
||||
em_width: px(0.),
|
||||
max_width: px(0.),
|
||||
block_id: ix,
|
||||
view: editor_view,
|
||||
editor_style: &editor::EditorStyle::default(),
|
||||
})
|
||||
.inner_id()?
|
||||
|
||||
@@ -21,6 +21,7 @@ test-support = [
|
||||
"workspace/test-support",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"tree-sitter-html"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
@@ -37,7 +38,7 @@ fuzzy.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
indoc = "1.0.4"
|
||||
itertools = "0.10"
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
lazy_static.workspace = true
|
||||
linkify.workspace = true
|
||||
@@ -46,20 +47,16 @@ lsp.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
rand.workspace = true
|
||||
rich_text.workspace = true
|
||||
rpc.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
snippet.workspace = true
|
||||
sqlez.workspace = true
|
||||
sum_tree.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
@@ -87,7 +84,6 @@ text = { workspace = true, features = ["test-support"] }
|
||||
tree-sitter-html.workspace = true
|
||||
tree-sitter-rust.workspace = true
|
||||
tree-sitter-typescript.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
unindent.workspace = true
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -165,6 +165,8 @@ gpui::actions!(
|
||||
GoToPrevHunk,
|
||||
GoToTypeDefinition,
|
||||
GoToTypeDefinitionSplit,
|
||||
GoToImplementation,
|
||||
GoToImplementationSplit,
|
||||
OpenUrl,
|
||||
HalfPageDown,
|
||||
HalfPageUp,
|
||||
|
||||
@@ -24,10 +24,7 @@ mod tab_map;
|
||||
mod wrap_map;
|
||||
|
||||
use crate::EditorStyle;
|
||||
use crate::{
|
||||
hover_links::InlayHighlight, movement::TextLayoutDetails, Anchor, AnchorRangeExt, InlayId,
|
||||
MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
|
||||
};
|
||||
use crate::{hover_links::InlayHighlight, movement::TextLayoutDetails, InlayId};
|
||||
pub use block_map::{BlockMap, BlockPoint};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use fold_map::FoldMap;
|
||||
@@ -37,6 +34,7 @@ use language::{
|
||||
language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
|
||||
};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use multi_buffer::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
|
||||
use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
|
||||
use sum_tree::{Bias, TreeMap};
|
||||
use tab_map::TabMap;
|
||||
|
||||
@@ -2,10 +2,11 @@ use super::{
|
||||
wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
|
||||
Highlights,
|
||||
};
|
||||
use crate::{Anchor, Editor, EditorStyle, ExcerptId, ExcerptRange, ToPoint as _};
|
||||
use crate::{EditorStyle, GutterDimensions};
|
||||
use collections::{Bound, HashMap, HashSet};
|
||||
use gpui::{AnyElement, ElementContext, Pixels, View};
|
||||
use gpui::{AnyElement, ElementContext, Pixels};
|
||||
use language::{BufferSnapshot, Chunk, Patch, Point};
|
||||
use multi_buffer::{Anchor, ExcerptId, ExcerptRange, ToPoint as _};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
@@ -85,11 +86,9 @@ pub enum BlockStyle {
|
||||
|
||||
pub struct BlockContext<'a, 'b> {
|
||||
pub context: &'b mut ElementContext<'a>,
|
||||
pub view: View<Editor>,
|
||||
pub anchor_x: Pixels,
|
||||
pub max_width: Pixels,
|
||||
pub gutter_width: Pixels,
|
||||
pub gutter_padding: Pixels,
|
||||
pub gutter_dimensions: &'b GutterDimensions,
|
||||
pub em_width: Pixels,
|
||||
pub line_height: Pixels,
|
||||
pub block_id: usize,
|
||||
|
||||
@@ -2,9 +2,9 @@ use super::{
|
||||
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
|
||||
Highlights,
|
||||
};
|
||||
use crate::{Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset};
|
||||
use gpui::{ElementId, HighlightStyle, Hsla};
|
||||
use language::{Chunk, Edit, Point, TextSummary};
|
||||
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cmp::{self, Ordering},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::{Anchor, InlayId, MultiBufferSnapshot, ToOffset};
|
||||
use crate::InlayId;
|
||||
use collections::{BTreeMap, BTreeSet};
|
||||
use gpui::HighlightStyle;
|
||||
use language::{Chunk, Edit, Point, TextSummary};
|
||||
use multi_buffer::{MultiBufferChunks, MultiBufferRows};
|
||||
use multi_buffer::{Anchor, MultiBufferChunks, MultiBufferRows, MultiBufferSnapshot, ToOffset};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cmp,
|
||||
|
||||
@@ -2,8 +2,8 @@ use super::{
|
||||
fold_map::{self, FoldChunks, FoldEdit, FoldPoint, FoldSnapshot},
|
||||
Highlights,
|
||||
};
|
||||
use crate::MultiBufferSnapshot;
|
||||
use language::{Chunk, Point};
|
||||
use multi_buffer::MultiBufferSnapshot;
|
||||
use std::{cmp, mem, num::NonZeroU32, ops::Range};
|
||||
use sum_tree::Bias;
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ use super::{
|
||||
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
|
||||
Highlights,
|
||||
};
|
||||
use crate::MultiBufferSnapshot;
|
||||
use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
|
||||
use language::{Chunk, Point};
|
||||
use lazy_static::lazy_static;
|
||||
use multi_buffer::MultiBufferSnapshot;
|
||||
use smol::future::yield_now;
|
||||
use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
|
||||
use sum_tree::{Bias, Cursor, SumTree};
|
||||
|
||||
@@ -88,6 +88,7 @@ pub use multi_buffer::{
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use project::project_settings::{GitGutterSetting, ProjectSettings};
|
||||
use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
|
||||
use rand::prelude::*;
|
||||
use rpc::proto::*;
|
||||
@@ -121,7 +122,7 @@ use ui::{
|
||||
};
|
||||
use util::{maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
|
||||
use workspace::Toast;
|
||||
use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace};
|
||||
use workspace::{searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace};
|
||||
|
||||
use crate::hover_links::find_url;
|
||||
|
||||
@@ -355,7 +356,6 @@ type InlayBackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec<InlayHighlight>);
|
||||
///
|
||||
/// See the [module level documentation](self) for more information.
|
||||
pub struct Editor {
|
||||
handle: WeakView<Self>,
|
||||
focus_handle: FocusHandle,
|
||||
/// The text buffer being edited
|
||||
buffer: Model<MultiBuffer>,
|
||||
@@ -443,7 +443,8 @@ pub struct EditorSnapshot {
|
||||
}
|
||||
|
||||
pub struct GutterDimensions {
|
||||
pub padding: Pixels,
|
||||
pub left_padding: Pixels,
|
||||
pub right_padding: Pixels,
|
||||
pub width: Pixels,
|
||||
pub margin: Pixels,
|
||||
}
|
||||
@@ -451,7 +452,8 @@ pub struct GutterDimensions {
|
||||
impl Default for GutterDimensions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
padding: Pixels::ZERO,
|
||||
left_padding: Pixels::ZERO,
|
||||
right_padding: Pixels::ZERO,
|
||||
width: Pixels::ZERO,
|
||||
margin: Pixels::ZERO,
|
||||
}
|
||||
@@ -1346,6 +1348,7 @@ pub(crate) struct NavigationData {
|
||||
enum GotoDefinitionKind {
|
||||
Symbol,
|
||||
Type,
|
||||
Implementation,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -1484,7 +1487,6 @@ impl Editor {
|
||||
cx.on_blur(&focus_handle, Self::handle_blur).detach();
|
||||
|
||||
let mut this = Self {
|
||||
handle: cx.view().downgrade(),
|
||||
focus_handle,
|
||||
buffer: buffer.clone(),
|
||||
display_map: display_map.clone(),
|
||||
@@ -1682,10 +1684,6 @@ impl Editor {
|
||||
self.workspace.as_ref()?.0.upgrade()
|
||||
}
|
||||
|
||||
pub fn pane(&self, cx: &AppContext) -> Option<View<Pane>> {
|
||||
self.workspace()?.read(cx).pane_for(&self.handle.upgrade()?)
|
||||
}
|
||||
|
||||
pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> {
|
||||
self.buffer().read(cx).title(cx)
|
||||
}
|
||||
@@ -1751,7 +1749,7 @@ impl Editor {
|
||||
self.completion_provider = Some(hub);
|
||||
}
|
||||
|
||||
pub fn placeholder_text(&self) -> Option<&str> {
|
||||
pub fn placeholder_text(&self, _cx: &mut WindowContext) -> Option<&str> {
|
||||
self.placeholder_text.as_deref()
|
||||
}
|
||||
|
||||
@@ -4057,7 +4055,8 @@ impl Editor {
|
||||
if self.available_code_actions.is_some() {
|
||||
Some(
|
||||
IconButton::new("code_actions_indicator", ui::IconName::Bolt)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.size(ui::ButtonSize::None)
|
||||
.icon_color(Color::Muted)
|
||||
.selected(is_active)
|
||||
.on_click(cx.listener(|editor, _e, cx| {
|
||||
@@ -4206,8 +4205,43 @@ impl Editor {
|
||||
active_index: 0,
|
||||
ranges: tabstops,
|
||||
});
|
||||
}
|
||||
|
||||
// Check whether the just-entered snippet ends with an auto-closable bracket.
|
||||
if self.autoclose_regions.is_empty() {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
for selection in &mut self.selections.all::<Point>(cx) {
|
||||
let selection_head = selection.head();
|
||||
let Some(scope) = snapshot.language_scope_at(selection_head) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut bracket_pair = None;
|
||||
let next_chars = snapshot.chars_at(selection_head).collect::<String>();
|
||||
let prev_chars = snapshot
|
||||
.reversed_chars_at(selection_head)
|
||||
.collect::<String>();
|
||||
for (pair, enabled) in scope.brackets() {
|
||||
if enabled
|
||||
&& pair.close
|
||||
&& prev_chars.starts_with(pair.start.as_str())
|
||||
&& next_chars.starts_with(pair.end.as_str())
|
||||
{
|
||||
bracket_pair = Some(pair.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(pair) = bracket_pair {
|
||||
let start = snapshot.anchor_after(selection_head);
|
||||
let end = snapshot.anchor_after(selection_head);
|
||||
self.autoclose_regions.push(AutocloseRegion {
|
||||
selection_id: selection.id,
|
||||
range: start..end,
|
||||
pair,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -7317,6 +7351,18 @@ impl Editor {
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx);
|
||||
}
|
||||
|
||||
pub fn go_to_implementation(&mut self, _: &GoToImplementation, cx: &mut ViewContext<Self>) {
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, cx);
|
||||
}
|
||||
|
||||
pub fn go_to_implementation_split(
|
||||
&mut self,
|
||||
_: &GoToImplementationSplit,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, cx);
|
||||
}
|
||||
|
||||
pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext<Self>) {
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx);
|
||||
}
|
||||
@@ -7354,12 +7400,14 @@ impl Editor {
|
||||
let definitions = project.update(cx, |project, cx| match kind {
|
||||
GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx),
|
||||
GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
|
||||
GotoDefinitionKind::Implementation => project.implementation(&buffer, head, cx),
|
||||
});
|
||||
|
||||
cx.spawn(|editor, mut cx| async move {
|
||||
let definitions = definitions.await?;
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
editor.navigate_to_hover_links(
|
||||
Some(kind),
|
||||
definitions.into_iter().map(HoverLink::Text).collect(),
|
||||
split,
|
||||
cx,
|
||||
@@ -7392,8 +7440,9 @@ impl Editor {
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn navigate_to_hover_links(
|
||||
pub(crate) fn navigate_to_hover_links(
|
||||
&mut self,
|
||||
kind: Option<GotoDefinitionKind>,
|
||||
mut definitions: Vec<HoverLink>,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
@@ -7462,13 +7511,18 @@ impl Editor {
|
||||
cx.spawn(|editor, mut cx| async move {
|
||||
let (title, location_tasks, workspace) = editor
|
||||
.update(&mut cx, |editor, cx| {
|
||||
let tab_kind = match kind {
|
||||
Some(GotoDefinitionKind::Implementation) => "Implementations",
|
||||
_ => "Definitions",
|
||||
};
|
||||
let title = definitions
|
||||
.iter()
|
||||
.find_map(|definition| match definition {
|
||||
HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
|
||||
let buffer = origin.buffer.read(cx);
|
||||
format!(
|
||||
"Definitions for {}",
|
||||
"{} for {}",
|
||||
tab_kind,
|
||||
buffer
|
||||
.text_for_range(origin.range.clone())
|
||||
.collect::<String>()
|
||||
@@ -7477,7 +7531,7 @@ impl Editor {
|
||||
HoverLink::InlayHint(_, _) => None,
|
||||
HoverLink::Url(_) => None,
|
||||
})
|
||||
.unwrap_or("Definitions".to_string());
|
||||
.unwrap_or(tab_kind.to_string());
|
||||
let location_tasks = definitions
|
||||
.into_iter()
|
||||
.map(|definition| match definition {
|
||||
@@ -9564,7 +9618,7 @@ impl EditorSnapshot {
|
||||
self.is_focused
|
||||
}
|
||||
|
||||
pub fn placeholder_text(&self) -> Option<&Arc<str>> {
|
||||
pub fn placeholder_text(&self, _cx: &mut WindowContext) -> Option<&Arc<str>> {
|
||||
self.placeholder_text.as_ref()
|
||||
}
|
||||
|
||||
@@ -9580,23 +9634,50 @@ impl EditorSnapshot {
|
||||
max_line_number_width: Pixels,
|
||||
cx: &AppContext,
|
||||
) -> GutterDimensions {
|
||||
if self.show_gutter {
|
||||
let descent = cx.text_system().descent(font_id, font_size);
|
||||
let gutter_padding_factor = 4.0;
|
||||
let gutter_padding = (em_width * gutter_padding_factor).round();
|
||||
if !self.show_gutter {
|
||||
return GutterDimensions::default();
|
||||
}
|
||||
let descent = cx.text_system().descent(font_id, font_size);
|
||||
|
||||
let show_git_gutter = matches!(
|
||||
ProjectSettings::get_global(cx).git.git_gutter,
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
);
|
||||
let gutter_settings = EditorSettings::get_global(cx).gutter;
|
||||
|
||||
let line_gutter_width = if gutter_settings.line_numbers {
|
||||
// Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
|
||||
let min_width_for_number_on_gutter = em_width * 4.0;
|
||||
let gutter_width =
|
||||
max_line_number_width.max(min_width_for_number_on_gutter) + gutter_padding * 2.0;
|
||||
let gutter_margin = -descent;
|
||||
|
||||
GutterDimensions {
|
||||
padding: gutter_padding,
|
||||
width: gutter_width,
|
||||
margin: gutter_margin,
|
||||
}
|
||||
max_line_number_width.max(min_width_for_number_on_gutter)
|
||||
} else {
|
||||
GutterDimensions::default()
|
||||
0.0.into()
|
||||
};
|
||||
|
||||
let left_padding = if gutter_settings.code_actions {
|
||||
em_width * 3.0
|
||||
} else if show_git_gutter && gutter_settings.line_numbers {
|
||||
em_width * 2.0
|
||||
} else if show_git_gutter || gutter_settings.line_numbers {
|
||||
em_width
|
||||
} else {
|
||||
px(0.)
|
||||
};
|
||||
|
||||
let right_padding = if gutter_settings.folds && gutter_settings.line_numbers {
|
||||
em_width * 4.0
|
||||
} else if gutter_settings.folds {
|
||||
em_width * 3.0
|
||||
} else if gutter_settings.line_numbers {
|
||||
em_width
|
||||
} else {
|
||||
px(0.)
|
||||
};
|
||||
|
||||
GutterDimensions {
|
||||
left_padding,
|
||||
right_padding,
|
||||
width: line_gutter_width + left_padding + right_padding,
|
||||
margin: -descent,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9703,7 +9784,6 @@ impl Render for Editor {
|
||||
status: cx.theme().status().clone(),
|
||||
inlays_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().hint),
|
||||
font_weight: Some(FontWeight::BOLD),
|
||||
..HighlightStyle::default()
|
||||
},
|
||||
suggestions_style: HighlightStyle {
|
||||
@@ -10103,9 +10183,14 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
|
||||
.group(group_id.clone())
|
||||
.relative()
|
||||
.size_full()
|
||||
.pl(cx.gutter_width)
|
||||
.w(cx.max_width + cx.gutter_width)
|
||||
.child(div().flex().w(cx.anchor_x - cx.gutter_width).flex_shrink())
|
||||
.pl(cx.gutter_dimensions.width)
|
||||
.w(cx.max_width + cx.gutter_dimensions.width)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.w(cx.anchor_x - cx.gutter_dimensions.width)
|
||||
.flex_shrink(),
|
||||
)
|
||||
.child(div().flex().flex_shrink_0().child(
|
||||
StyledText::new(text_without_backticks.clone()).with_highlights(
|
||||
&text_style,
|
||||
|
||||
@@ -12,6 +12,7 @@ pub struct EditorSettings {
|
||||
pub use_on_type_format: bool,
|
||||
pub toolbar: Toolbar,
|
||||
pub scrollbar: Scrollbar,
|
||||
pub gutter: Gutter,
|
||||
pub vertical_scroll_margin: f32,
|
||||
pub relative_line_numbers: bool,
|
||||
pub seed_search_query_from_cursor: SeedQuerySetting,
|
||||
@@ -45,6 +46,13 @@ pub struct Scrollbar {
|
||||
pub diagnostics: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct Gutter {
|
||||
pub line_numbers: bool,
|
||||
pub code_actions: bool,
|
||||
pub folds: bool,
|
||||
}
|
||||
|
||||
/// When to show the scrollbar in the editor.
|
||||
///
|
||||
/// Default: auto
|
||||
@@ -97,6 +105,8 @@ pub struct EditorSettingsContent {
|
||||
pub toolbar: Option<ToolbarContent>,
|
||||
/// Scrollbar related settings
|
||||
pub scrollbar: Option<ScrollbarContent>,
|
||||
/// Gutter related settings
|
||||
pub gutter: Option<GutterContent>,
|
||||
|
||||
/// The number of lines to keep above/below the cursor when auto-scrolling.
|
||||
///
|
||||
@@ -157,6 +167,23 @@ pub struct ScrollbarContent {
|
||||
pub diagnostics: Option<bool>,
|
||||
}
|
||||
|
||||
/// Gutter related settings
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct GutterContent {
|
||||
/// Whether to show line numbers in the gutter.
|
||||
///
|
||||
/// Default: true
|
||||
pub line_numbers: Option<bool>,
|
||||
/// Whether to show code action buttons in the gutter.
|
||||
///
|
||||
/// Default: true
|
||||
pub code_actions: Option<bool>,
|
||||
/// Whether to show fold buttons in the gutter.
|
||||
///
|
||||
/// Default: true
|
||||
pub folds: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for EditorSettings {
|
||||
const KEY: Option<&'static str> = None;
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ use crate::{
|
||||
mouse_context_menu,
|
||||
scroll::scroll_amount::ScrollAmount,
|
||||
CursorShape, DisplayPoint, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode,
|
||||
EditorSettings, EditorSnapshot, EditorStyle, HalfPageDown, HalfPageUp, HoveredCursor, LineDown,
|
||||
LineUp, OpenExcerpts, PageDown, PageUp, Point, SelectPhase, Selection, SoftWrap, ToPoint,
|
||||
CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
|
||||
EditorSettings, EditorSnapshot, EditorStyle, GutterDimensions, HalfPageDown, HalfPageUp,
|
||||
HoveredCursor, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, SelectPhase, Selection,
|
||||
SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::{BTreeMap, HashMap};
|
||||
@@ -260,6 +260,8 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::go_to_prev_hunk);
|
||||
register_action(view, cx, Editor::go_to_definition);
|
||||
register_action(view, cx, Editor::go_to_definition_split);
|
||||
register_action(view, cx, Editor::go_to_implementation);
|
||||
register_action(view, cx, Editor::go_to_implementation_split);
|
||||
register_action(view, cx, Editor::go_to_type_definition);
|
||||
register_action(view, cx, Editor::go_to_type_definition_split);
|
||||
register_action(view, cx, Editor::open_url);
|
||||
@@ -714,25 +716,22 @@ impl EditorElement {
|
||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||
let scroll_top = scroll_position.y * line_height;
|
||||
|
||||
if bounds.contains(&cx.mouse_position()) {
|
||||
let stacking_order = cx.stacking_order().clone();
|
||||
cx.set_cursor_style(CursorStyle::Arrow, stacking_order);
|
||||
}
|
||||
|
||||
let show_gutter = matches!(
|
||||
let show_git_gutter = matches!(
|
||||
ProjectSettings::get_global(cx).git.git_gutter,
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
);
|
||||
|
||||
if show_gutter {
|
||||
if show_git_gutter {
|
||||
Self::paint_diff_hunks(bounds, layout, cx);
|
||||
}
|
||||
|
||||
let gutter_settings = EditorSettings::get_global(cx).gutter;
|
||||
|
||||
for (ix, line) in layout.line_numbers.iter().enumerate() {
|
||||
if let Some(line) = line {
|
||||
let line_origin = bounds.origin
|
||||
+ point(
|
||||
bounds.size.width - line.width - layout.gutter_padding,
|
||||
bounds.size.width - line.width - layout.gutter_dimensions.right_padding,
|
||||
ix as f32 * line_height - (scroll_top % line_height),
|
||||
);
|
||||
|
||||
@@ -743,6 +742,7 @@ impl EditorElement {
|
||||
cx.with_z_index(1, |cx| {
|
||||
for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
|
||||
if let Some(fold_indicator) = fold_indicator {
|
||||
debug_assert!(gutter_settings.folds);
|
||||
let mut fold_indicator = fold_indicator.into_any_element();
|
||||
let available_space = size(
|
||||
AvailableSpace::MinContent,
|
||||
@@ -751,11 +751,12 @@ impl EditorElement {
|
||||
let fold_indicator_size = fold_indicator.measure(available_space, cx);
|
||||
|
||||
let position = point(
|
||||
bounds.size.width - layout.gutter_padding,
|
||||
bounds.size.width - layout.gutter_dimensions.right_padding,
|
||||
ix as f32 * line_height - (scroll_top % line_height),
|
||||
);
|
||||
let centering_offset = point(
|
||||
(layout.gutter_padding + layout.gutter_margin - fold_indicator_size.width)
|
||||
(layout.gutter_dimensions.right_padding + layout.gutter_dimensions.margin
|
||||
- fold_indicator_size.width)
|
||||
/ 2.,
|
||||
(line_height - fold_indicator_size.height) / 2.,
|
||||
);
|
||||
@@ -765,6 +766,7 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
if let Some(indicator) = layout.code_actions_indicator.take() {
|
||||
debug_assert!(gutter_settings.code_actions);
|
||||
let mut button = indicator.button.into_any_element();
|
||||
let available_space = size(
|
||||
AvailableSpace::MinContent,
|
||||
@@ -775,7 +777,9 @@ impl EditorElement {
|
||||
let mut x = Pixels::ZERO;
|
||||
let mut y = indicator.row as f32 * line_height - scroll_top;
|
||||
// Center indicator.
|
||||
x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;
|
||||
x += (layout.gutter_dimensions.margin + layout.gutter_dimensions.left_padding
|
||||
- indicator_size.width)
|
||||
/ 2.;
|
||||
y += (line_height - indicator_size.height) / 2.;
|
||||
|
||||
button.draw(bounds.origin + point(x, y), available_space, cx);
|
||||
@@ -890,7 +894,9 @@ impl EditorElement {
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let start_row = layout.visible_display_row_range.start;
|
||||
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
||||
// Offset the content_bounds from the text_bounds by the gutter margin (which is roughly half a character wide) to make hit testing work more like how we want.
|
||||
let content_origin =
|
||||
text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO);
|
||||
let line_end_overshoot = 0.15 * layout.position_map.line_height;
|
||||
let whitespace_setting = self
|
||||
.editor
|
||||
@@ -909,7 +915,7 @@ impl EditorElement {
|
||||
bounds: text_bounds,
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
if text_bounds.contains(&cx.mouse_position()) {
|
||||
if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) {
|
||||
if self
|
||||
.editor
|
||||
.read(cx)
|
||||
@@ -917,15 +923,9 @@ impl EditorElement {
|
||||
.as_ref()
|
||||
.is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
|
||||
{
|
||||
cx.set_cursor_style(
|
||||
CursorStyle::PointingHand,
|
||||
interactive_text_bounds.stacking_order.clone(),
|
||||
);
|
||||
cx.set_cursor_style(CursorStyle::PointingHand);
|
||||
} else {
|
||||
cx.set_cursor_style(
|
||||
CursorStyle::IBeam,
|
||||
interactive_text_bounds.stacking_order.clone(),
|
||||
);
|
||||
cx.set_cursor_style(CursorStyle::IBeam);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1165,7 +1165,8 @@ impl EditorElement {
|
||||
layout: &LayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
||||
let content_origin =
|
||||
text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO);
|
||||
let line_end_overshoot = layout.line_end_overshoot();
|
||||
|
||||
// A softer than perfect black
|
||||
@@ -1191,7 +1192,8 @@ impl EditorElement {
|
||||
layout: &mut LayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
||||
let content_origin =
|
||||
text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO);
|
||||
let start_row = layout.visible_display_row_range.start;
|
||||
if let Some((position, mut context_menu)) = layout.context_menu.take() {
|
||||
let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
||||
@@ -1549,11 +1551,8 @@ impl EditorElement {
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
let mut mouse_position = cx.mouse_position();
|
||||
if track_bounds.contains(&mouse_position) {
|
||||
cx.set_cursor_style(
|
||||
CursorStyle::Arrow,
|
||||
interactive_track_bounds.stacking_order.clone(),
|
||||
);
|
||||
if interactive_track_bounds.visibly_contains(&mouse_position, cx) {
|
||||
cx.set_cursor_style(CursorStyle::Arrow);
|
||||
}
|
||||
|
||||
cx.on_mouse_event({
|
||||
@@ -1831,7 +1830,10 @@ impl EditorElement {
|
||||
Vec<Option<(FoldStatus, BufferRow, bool)>>,
|
||||
) {
|
||||
let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
|
||||
let include_line_numbers = snapshot.mode == EditorMode::Full;
|
||||
let include_line_numbers =
|
||||
EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full;
|
||||
let include_fold_statuses =
|
||||
EditorSettings::get_global(cx).gutter.folds && snapshot.mode == EditorMode::Full;
|
||||
let mut shaped_line_numbers = Vec::with_capacity(rows.len());
|
||||
let mut fold_statuses = Vec::with_capacity(rows.len());
|
||||
let mut line_number = String::new();
|
||||
@@ -1876,6 +1878,8 @@ impl EditorElement {
|
||||
.shape_line(line_number.clone().into(), font_size, &[run])
|
||||
.unwrap();
|
||||
shaped_line_numbers.push(Some(shaped_line));
|
||||
}
|
||||
if include_fold_statuses {
|
||||
fold_statuses.push(
|
||||
is_singleton
|
||||
.then(|| {
|
||||
@@ -1900,7 +1904,7 @@ impl EditorElement {
|
||||
rows: Range<u32>,
|
||||
line_number_layouts: &[Option<ShapedLine>],
|
||||
snapshot: &EditorSnapshot,
|
||||
cx: &ViewContext<Editor>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Vec<LineWithInvisibles> {
|
||||
if rows.start >= rows.end {
|
||||
return Vec::new();
|
||||
@@ -1910,7 +1914,7 @@ impl EditorElement {
|
||||
if snapshot.is_empty() {
|
||||
let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
|
||||
let placeholder_color = cx.theme().colors().text_placeholder;
|
||||
let placeholder_text = snapshot.placeholder_text();
|
||||
let placeholder_text = snapshot.placeholder_text(cx);
|
||||
|
||||
let placeholder_lines = placeholder_text
|
||||
.as_ref()
|
||||
@@ -1972,7 +1976,13 @@ impl EditorElement {
|
||||
.unwrap()
|
||||
.width;
|
||||
|
||||
let gutter_dimensions = snapshot.gutter_dimensions(font_id, font_size, em_width, self.max_line_number_width(&snapshot, cx), cx);
|
||||
let gutter_dimensions = snapshot.gutter_dimensions(
|
||||
font_id,
|
||||
font_size,
|
||||
em_width,
|
||||
self.max_line_number_width(&snapshot, cx),
|
||||
cx,
|
||||
);
|
||||
|
||||
editor.gutter_width = gutter_dimensions.width;
|
||||
|
||||
@@ -2216,7 +2226,6 @@ impl EditorElement {
|
||||
.width;
|
||||
let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
|
||||
|
||||
let editor_view = cx.view().clone();
|
||||
let (scroll_width, blocks) = cx.with_element_context(|cx| {
|
||||
cx.with_element_id(Some("editor_blocks"), |cx| {
|
||||
self.layout_blocks(
|
||||
@@ -2225,15 +2234,13 @@ impl EditorElement {
|
||||
bounds.size.width,
|
||||
scroll_width,
|
||||
text_width,
|
||||
gutter_dimensions.padding,
|
||||
gutter_dimensions.width,
|
||||
&gutter_dimensions,
|
||||
em_width,
|
||||
gutter_dimensions.width + gutter_dimensions.margin,
|
||||
line_height,
|
||||
&style,
|
||||
&line_layouts,
|
||||
editor,
|
||||
editor_view,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -2263,6 +2270,8 @@ impl EditorElement {
|
||||
snapshot = editor.snapshot(cx);
|
||||
}
|
||||
|
||||
let gutter_settings = EditorSettings::get_global(cx).gutter;
|
||||
|
||||
let mut context_menu = None;
|
||||
let mut code_actions_indicator = None;
|
||||
if let Some(newest_selection_head) = newest_selection_head {
|
||||
@@ -2284,12 +2293,14 @@ impl EditorElement {
|
||||
Some(crate::ContextMenu::CodeActions(_))
|
||||
);
|
||||
|
||||
code_actions_indicator = editor
|
||||
.render_code_actions_indicator(&style, active, cx)
|
||||
.map(|element| CodeActionsIndicator {
|
||||
row: newest_selection_head.row(),
|
||||
button: element,
|
||||
});
|
||||
if gutter_settings.code_actions {
|
||||
code_actions_indicator = editor
|
||||
.render_code_actions_indicator(&style, active, cx)
|
||||
.map(|element| CodeActionsIndicator {
|
||||
row: newest_selection_head.row(),
|
||||
button: element,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2307,29 +2318,32 @@ impl EditorElement {
|
||||
None
|
||||
} else {
|
||||
editor.hover_state.render(
|
||||
&snapshot,
|
||||
&style,
|
||||
visible_rows,
|
||||
max_size,
|
||||
editor.workspace.as_ref().map(|(w, _)| w.clone()),
|
||||
cx,
|
||||
)
|
||||
&snapshot,
|
||||
&style,
|
||||
visible_rows,
|
||||
max_size,
|
||||
editor.workspace.as_ref().map(|(w, _)| w.clone()),
|
||||
cx,
|
||||
)
|
||||
};
|
||||
|
||||
let editor_view = cx.view().clone();
|
||||
let fold_indicators = cx.with_element_context(|cx| {
|
||||
|
||||
cx.with_element_id(Some("gutter_fold_indicators"), |_cx| {
|
||||
editor.render_fold_indicators(
|
||||
fold_statuses,
|
||||
&style,
|
||||
editor.gutter_hovered,
|
||||
line_height,
|
||||
gutter_dimensions.margin,
|
||||
editor_view,
|
||||
)
|
||||
})
|
||||
});
|
||||
let fold_indicators = if gutter_settings.folds {
|
||||
cx.with_element_context(|cx| {
|
||||
cx.with_element_id(Some("gutter_fold_indicators"), |_cx| {
|
||||
editor.render_fold_indicators(
|
||||
fold_statuses,
|
||||
&style,
|
||||
editor.gutter_hovered,
|
||||
line_height,
|
||||
gutter_dimensions.margin,
|
||||
editor_view,
|
||||
)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let invisible_symbol_font_size = font_size / 2.;
|
||||
let tab_invisible = cx
|
||||
@@ -2382,13 +2396,12 @@ impl EditorElement {
|
||||
visible_display_row_range: start_row..end_row,
|
||||
wrap_guides,
|
||||
gutter_size,
|
||||
gutter_padding: gutter_dimensions.padding,
|
||||
gutter_dimensions,
|
||||
text_size,
|
||||
scrollbar_row_range,
|
||||
show_scrollbars,
|
||||
is_singleton,
|
||||
max_row,
|
||||
gutter_margin: gutter_dimensions.margin,
|
||||
active_rows,
|
||||
highlighted_rows,
|
||||
highlighted_ranges,
|
||||
@@ -2415,15 +2428,13 @@ impl EditorElement {
|
||||
editor_width: Pixels,
|
||||
scroll_width: Pixels,
|
||||
text_width: Pixels,
|
||||
gutter_padding: Pixels,
|
||||
gutter_width: Pixels,
|
||||
gutter_dimensions: &GutterDimensions,
|
||||
em_width: Pixels,
|
||||
text_x: Pixels,
|
||||
line_height: Pixels,
|
||||
style: &EditorStyle,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
editor: &mut Editor,
|
||||
editor_view: View<Editor>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (Pixels, Vec<BlockLayout>) {
|
||||
let mut block_id = 0;
|
||||
@@ -2459,13 +2470,11 @@ impl EditorElement {
|
||||
block.render(&mut BlockContext {
|
||||
context: cx,
|
||||
anchor_x,
|
||||
gutter_padding,
|
||||
gutter_dimensions,
|
||||
line_height,
|
||||
gutter_width,
|
||||
em_width,
|
||||
block_id,
|
||||
max_width: scroll_width.max(text_width),
|
||||
view: editor_view.clone(),
|
||||
editor_style: &self.style,
|
||||
})
|
||||
}
|
||||
@@ -2565,12 +2574,14 @@ impl EditorElement {
|
||||
h_flex()
|
||||
.id(("collapsed context", block_id))
|
||||
.size_full()
|
||||
.gap(gutter_padding)
|
||||
.gap(gutter_dimensions.left_padding + gutter_dimensions.right_padding)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_end()
|
||||
.flex_none()
|
||||
.w(gutter_width - gutter_padding)
|
||||
.w(gutter_dimensions.width
|
||||
- (gutter_dimensions.left_padding
|
||||
+ gutter_dimensions.right_padding))
|
||||
.h_full()
|
||||
.text_buffer(cx)
|
||||
.text_color(cx.theme().colors().editor_line_number)
|
||||
@@ -2631,7 +2642,7 @@ impl EditorElement {
|
||||
BlockStyle::Sticky => editor_width,
|
||||
BlockStyle::Flex => editor_width
|
||||
.max(fixed_block_max_width)
|
||||
.max(gutter_width + scroll_width),
|
||||
.max(gutter_dimensions.width + scroll_width),
|
||||
BlockStyle::Fixed => unreachable!(),
|
||||
};
|
||||
let available_space = size(
|
||||
@@ -2648,7 +2659,7 @@ impl EditorElement {
|
||||
});
|
||||
}
|
||||
(
|
||||
scroll_width.max(fixed_block_max_width - gutter_width),
|
||||
scroll_width.max(fixed_block_max_width - gutter_dimensions.width),
|
||||
blocks,
|
||||
)
|
||||
}
|
||||
@@ -3165,8 +3176,7 @@ type BufferRow = u32;
|
||||
pub struct LayoutState {
|
||||
position_map: Arc<PositionMap>,
|
||||
gutter_size: Size<Pixels>,
|
||||
gutter_padding: Pixels,
|
||||
gutter_margin: Pixels,
|
||||
gutter_dimensions: GutterDimensions,
|
||||
text_size: gpui::Size<Pixels>,
|
||||
mode: EditorMode,
|
||||
wrap_guides: SmallVec<[(Pixels, bool); 2]>,
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
|
||||
let snapshot = editor.snapshot(cx);
|
||||
if let Some((opening_range, closing_range)) = snapshot
|
||||
.buffer_snapshot
|
||||
.innermost_enclosing_bracket_ranges(head..head)
|
||||
.innermost_enclosing_bracket_ranges(head..head, None)
|
||||
{
|
||||
editor.highlight_background::<MatchingBracketHighlight>(
|
||||
vec![
|
||||
|
||||
@@ -138,7 +138,7 @@ impl Editor {
|
||||
cx.focus(&self.focus_handle);
|
||||
}
|
||||
|
||||
self.navigate_to_hover_links(hovered_link_state.links, modifiers.alt, cx);
|
||||
self.navigate_to_hover_links(None, hovered_link_state.links, modifiers.alt, cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,9 +199,10 @@ fn show_hover(
|
||||
if symbol_range
|
||||
.as_text_range()
|
||||
.map(|range| {
|
||||
range
|
||||
.to_offset(&snapshot.buffer_snapshot)
|
||||
.contains(&multibuffer_offset)
|
||||
let hover_range = range.to_offset(&snapshot.buffer_snapshot);
|
||||
// LSP returns a hover result for the end index of ranges that should be hovered, so we need to
|
||||
// use an inclusive range here to check if we should dismiss the popover
|
||||
(hover_range.start..=hover_range.end).contains(&multibuffer_offset)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToTypeDefinition,
|
||||
Rename, RevealInFinder, SelectMode, ToggleCodeActions,
|
||||
DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToImplementation,
|
||||
GoToTypeDefinition, Rename, RevealInFinder, SelectMode, ToggleCodeActions,
|
||||
};
|
||||
use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
|
||||
|
||||
@@ -48,6 +48,7 @@ pub fn deploy_context_menu(
|
||||
menu.action("Rename Symbol", Box::new(Rename))
|
||||
.action("Go to Definition", Box::new(GoToDefinition))
|
||||
.action("Go to Type Definition", Box::new(GoToTypeDefinition))
|
||||
.action("Go to Implementation", Box::new(GoToImplementation))
|
||||
.action("Find All References", Box::new(FindAllReferences))
|
||||
.action(
|
||||
"Code Actions",
|
||||
|
||||
@@ -214,6 +214,22 @@ impl EditorLspTestContext {
|
||||
Self::new(language, capabilities, cx).await
|
||||
}
|
||||
|
||||
pub async fn new_html(cx: &mut gpui::TestAppContext) -> Self {
|
||||
let language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "HTML".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["html".into()],
|
||||
..Default::default()
|
||||
},
|
||||
block_comment: Some(("<!-- ".into(), " -->".into())),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_html::language()),
|
||||
);
|
||||
Self::new(language, Default::default(), cx).await
|
||||
}
|
||||
|
||||
// Constructs lsp range using a marked string with '[', ']' range delimiters
|
||||
pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
|
||||
let ranges = self.ranges(marked_text);
|
||||
|
||||
@@ -16,7 +16,6 @@ path = "src/extension_json_schemas.rs"
|
||||
anyhow.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-tar.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
@@ -33,7 +32,6 @@ toml.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
use util::http::{AsyncBody, ZedHttpClient};
|
||||
use util::http::{AsyncBody, HttpClientWithUrl};
|
||||
use util::TryFutureExt;
|
||||
use util::{http::HttpClient, paths::EXTENSIONS_DIR, ResultExt};
|
||||
|
||||
@@ -32,7 +32,7 @@ pub struct ExtensionsApiResponse {
|
||||
pub data: Vec<Extension>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Extension {
|
||||
pub id: Arc<str>,
|
||||
pub version: Arc<str>,
|
||||
@@ -52,10 +52,24 @@ pub enum ExtensionStatus {
|
||||
Removing,
|
||||
}
|
||||
|
||||
impl ExtensionStatus {
|
||||
pub fn is_installing(&self) -> bool {
|
||||
matches!(self, Self::Installing)
|
||||
}
|
||||
|
||||
pub fn is_upgrading(&self) -> bool {
|
||||
matches!(self, Self::Upgrading)
|
||||
}
|
||||
|
||||
pub fn is_removing(&self) -> bool {
|
||||
matches!(self, Self::Removing)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtensionStore {
|
||||
manifest: Arc<RwLock<Manifest>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<ZedHttpClient>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
extensions_dir: PathBuf,
|
||||
extensions_being_installed: HashSet<Arc<str>>,
|
||||
extensions_being_uninstalled: HashSet<Arc<str>>,
|
||||
@@ -111,7 +125,7 @@ actions!(zed, [ReloadExtensions]);
|
||||
|
||||
pub fn init(
|
||||
fs: Arc<fs::RealFs>,
|
||||
http_client: Arc<ZedHttpClient>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
cx: &mut AppContext,
|
||||
@@ -143,7 +157,7 @@ impl ExtensionStore {
|
||||
pub fn new(
|
||||
extensions_dir: PathBuf,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<ZedHttpClient>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
@@ -222,7 +236,7 @@ impl ExtensionStore {
|
||||
search: Option<&str>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<Extension>>> {
|
||||
let url = self.http_client.zed_api_url(&format!(
|
||||
let url = self.http_client.build_zed_api_url(&format!(
|
||||
"/extensions{query}",
|
||||
query = search
|
||||
.map(|search| format!("?filter={search}"))
|
||||
@@ -262,7 +276,7 @@ impl ExtensionStore {
|
||||
log::info!("installing extension {extension_id} {version}");
|
||||
let url = self
|
||||
.http_client
|
||||
.zed_api_url(&format!("/extensions/{extension_id}/{version}/download"));
|
||||
.build_zed_api_url(&format!("/extensions/{extension_id}/{version}/download"));
|
||||
|
||||
let extensions_dir = self.extensions_dir();
|
||||
let http_client = self.http_client.clone();
|
||||
|
||||
@@ -12,26 +12,13 @@ path = "src/extensions_ui.rs"
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-tar.workspace = true
|
||||
client.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
extension.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
log.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -11,7 +11,7 @@ use settings::Settings;
|
||||
use std::time::Duration;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use theme::ThemeSettings;
|
||||
use ui::prelude::*;
|
||||
use ui::{prelude::*, ToggleButton, Tooltip};
|
||||
|
||||
use workspace::{
|
||||
item::{Item, ItemEvent},
|
||||
@@ -30,11 +30,19 @@ pub fn init(cx: &mut AppContext) {
|
||||
.detach();
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
enum ExtensionFilter {
|
||||
All,
|
||||
Installed,
|
||||
NotInstalled,
|
||||
}
|
||||
|
||||
pub struct ExtensionsPage {
|
||||
list: UniformListScrollHandle,
|
||||
telemetry: Arc<Telemetry>,
|
||||
is_fetching_extensions: bool,
|
||||
extensions_entries: Vec<Extension>,
|
||||
filter: ExtensionFilter,
|
||||
extension_entries: Vec<Extension>,
|
||||
query_editor: View<Editor>,
|
||||
query_contains_error: bool,
|
||||
_subscription: gpui::Subscription,
|
||||
@@ -54,7 +62,8 @@ impl ExtensionsPage {
|
||||
list: UniformListScrollHandle::new(),
|
||||
telemetry: workspace.client().telemetry().clone(),
|
||||
is_fetching_extensions: false,
|
||||
extensions_entries: Vec::new(),
|
||||
filter: ExtensionFilter::All,
|
||||
extension_entries: Vec::new(),
|
||||
query_contains_error: false,
|
||||
extension_fetch_task: None,
|
||||
_subscription: subscription,
|
||||
@@ -65,6 +74,28 @@ impl ExtensionsPage {
|
||||
})
|
||||
}
|
||||
|
||||
fn filtered_extension_entries(&self, cx: &mut ViewContext<Self>) -> Vec<Extension> {
|
||||
let extension_store = ExtensionStore::global(cx).read(cx);
|
||||
|
||||
self.extension_entries
|
||||
.iter()
|
||||
.filter(|extension| match self.filter {
|
||||
ExtensionFilter::All => true,
|
||||
ExtensionFilter::Installed => {
|
||||
let status = extension_store.extension_status(&extension.id);
|
||||
|
||||
matches!(status, ExtensionStatus::Installed(_))
|
||||
}
|
||||
ExtensionFilter::NotInstalled => {
|
||||
let status = extension_store.extension_status(&extension.id);
|
||||
|
||||
matches!(status, ExtensionStatus::NotInstalled)
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn install_extension(
|
||||
&self,
|
||||
extension_id: Arc<str>,
|
||||
@@ -94,7 +125,7 @@ impl ExtensionsPage {
|
||||
let fetch_result = extensions.await;
|
||||
match fetch_result {
|
||||
Ok(extensions) => this.update(&mut cx, |this, cx| {
|
||||
this.extensions_entries = extensions;
|
||||
this.extension_entries = extensions;
|
||||
this.is_fetching_extensions = false;
|
||||
cx.notify();
|
||||
}),
|
||||
@@ -113,7 +144,7 @@ impl ExtensionsPage {
|
||||
}
|
||||
|
||||
fn render_extensions(&mut self, range: Range<usize>, cx: &mut ViewContext<Self>) -> Vec<Div> {
|
||||
self.extensions_entries[range]
|
||||
self.filtered_extension_entries(cx)[range]
|
||||
.iter()
|
||||
.map(|extension| self.render_entry(extension, cx))
|
||||
.collect()
|
||||
@@ -161,40 +192,53 @@ impl ExtensionsPage {
|
||||
};
|
||||
|
||||
let install_or_uninstall_button = match status {
|
||||
ExtensionStatus::NotInstalled | ExtensionStatus::Installing => {
|
||||
Button::new(SharedString::from(extension.id.clone()), "Install")
|
||||
.on_click(cx.listener({
|
||||
let extension_id = extension.id.clone();
|
||||
let version = extension.version.clone();
|
||||
move |this, _, cx| {
|
||||
this.telemetry
|
||||
.report_app_event("extensions: install extension".to_string());
|
||||
this.install_extension(extension_id.clone(), version.clone(), cx);
|
||||
}
|
||||
}))
|
||||
.disabled(matches!(status, ExtensionStatus::Installing))
|
||||
}
|
||||
ExtensionStatus::NotInstalled | ExtensionStatus::Installing => Button::new(
|
||||
SharedString::from(extension.id.clone()),
|
||||
if status.is_installing() {
|
||||
"Installing..."
|
||||
} else {
|
||||
"Install"
|
||||
},
|
||||
)
|
||||
.on_click(cx.listener({
|
||||
let extension_id = extension.id.clone();
|
||||
let version = extension.version.clone();
|
||||
move |this, _, cx| {
|
||||
this.telemetry
|
||||
.report_app_event("extensions: install extension".to_string());
|
||||
this.install_extension(extension_id.clone(), version.clone(), cx);
|
||||
}
|
||||
}))
|
||||
.disabled(status.is_installing()),
|
||||
ExtensionStatus::Installed(_)
|
||||
| ExtensionStatus::Upgrading
|
||||
| ExtensionStatus::Removing => {
|
||||
Button::new(SharedString::from(extension.id.clone()), "Uninstall")
|
||||
.on_click(cx.listener({
|
||||
let extension_id = extension.id.clone();
|
||||
move |this, _, cx| {
|
||||
this.telemetry
|
||||
.report_app_event("extensions: uninstall extension".to_string());
|
||||
this.uninstall_extension(extension_id.clone(), cx);
|
||||
}
|
||||
}))
|
||||
.disabled(matches!(
|
||||
status,
|
||||
ExtensionStatus::Upgrading | ExtensionStatus::Removing
|
||||
))
|
||||
}
|
||||
| ExtensionStatus::Removing => Button::new(
|
||||
SharedString::from(extension.id.clone()),
|
||||
if status.is_upgrading() {
|
||||
"Upgrading..."
|
||||
} else if status.is_removing() {
|
||||
"Removing..."
|
||||
} else {
|
||||
"Uninstall"
|
||||
},
|
||||
)
|
||||
.on_click(cx.listener({
|
||||
let extension_id = extension.id.clone();
|
||||
move |this, _, cx| {
|
||||
this.telemetry
|
||||
.report_app_event("extensions: uninstall extension".to_string());
|
||||
this.uninstall_extension(extension_id.clone(), cx);
|
||||
}
|
||||
}))
|
||||
.disabled(matches!(
|
||||
status,
|
||||
ExtensionStatus::Upgrading | ExtensionStatus::Removing
|
||||
)),
|
||||
}
|
||||
.color(Color::Accent);
|
||||
|
||||
let repository_url = extension.repository.clone();
|
||||
let tooltip_text = Tooltip::text(repository_url.clone(), cx);
|
||||
|
||||
div().w_full().child(
|
||||
v_flex()
|
||||
@@ -269,7 +313,8 @@ impl ExtensionsPage {
|
||||
.style(ButtonStyle::Filled)
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.open_url(&repository_url);
|
||||
})),
|
||||
}))
|
||||
.tooltip(move |_| tooltip_text.clone()),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -379,10 +424,44 @@ impl ExtensionsPage {
|
||||
Some(search)
|
||||
}
|
||||
}
|
||||
|
||||
fn render_empty_state(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let has_search = self.search_query(cx).is_some();
|
||||
|
||||
let message = if self.is_fetching_extensions {
|
||||
"Loading extensions..."
|
||||
} else {
|
||||
match self.filter {
|
||||
ExtensionFilter::All => {
|
||||
if has_search {
|
||||
"No extensions that match your search."
|
||||
} else {
|
||||
"No extensions."
|
||||
}
|
||||
}
|
||||
ExtensionFilter::Installed => {
|
||||
if has_search {
|
||||
"No installed extensions that match your search."
|
||||
} else {
|
||||
"No installed extensions."
|
||||
}
|
||||
}
|
||||
ExtensionFilter::NotInstalled => {
|
||||
if has_search {
|
||||
"No not installed extensions that match your search."
|
||||
} else {
|
||||
"No not installed extensions."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Label::new(message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ExtensionsPage {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.p_4()
|
||||
@@ -393,25 +472,63 @@ impl Render for ExtensionsPage {
|
||||
.w_full()
|
||||
.child(Headline::new("Extensions").size(HeadlineSize::XLarge)),
|
||||
)
|
||||
.child(h_flex().w_56().child(self.render_search(cx)))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.child(h_flex().child(self.render_search(cx)))
|
||||
.child(
|
||||
h_flex()
|
||||
.child(
|
||||
ToggleButton::new("filter-all", "All")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.selected(self.filter == ExtensionFilter::All)
|
||||
.on_click(cx.listener(|this, _event, _cx| {
|
||||
this.filter = ExtensionFilter::All;
|
||||
}))
|
||||
.tooltip(move |cx| Tooltip::text("Show all extensions", cx))
|
||||
.first(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new("filter-installed", "Installed")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.selected(self.filter == ExtensionFilter::Installed)
|
||||
.on_click(cx.listener(|this, _event, _cx| {
|
||||
this.filter = ExtensionFilter::Installed;
|
||||
}))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text("Show installed extensions", cx)
|
||||
})
|
||||
.middle(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new("filter-not-installed", "Not Installed")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.selected(self.filter == ExtensionFilter::NotInstalled)
|
||||
.on_click(cx.listener(|this, _event, _cx| {
|
||||
this.filter = ExtensionFilter::NotInstalled;
|
||||
}))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text("Show not installed extensions", cx)
|
||||
})
|
||||
.last(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(v_flex().size_full().overflow_y_hidden().map(|this| {
|
||||
if self.extensions_entries.is_empty() {
|
||||
let message = if self.is_fetching_extensions {
|
||||
"Loading extensions..."
|
||||
} else if self.search_query(cx).is_some() {
|
||||
"No extensions that match your search."
|
||||
} else {
|
||||
"No extensions."
|
||||
};
|
||||
|
||||
return this.child(Label::new(message));
|
||||
let entries = self.filtered_extension_entries(cx);
|
||||
if entries.is_empty() {
|
||||
return this.child(self.render_empty_state(cx));
|
||||
}
|
||||
|
||||
this.child(
|
||||
canvas({
|
||||
let view = cx.view().clone();
|
||||
let scroll_handle = self.list.clone();
|
||||
let item_count = self.extensions_entries.len();
|
||||
let item_count = entries.len();
|
||||
move |bounds, cx| {
|
||||
uniform_list::<_, Div, _>(
|
||||
view,
|
||||
|
||||
@@ -9,5 +9,4 @@ license = "GPL-3.0-or-later"
|
||||
path = "src/feature_flags.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
gpui.workspace = true
|
||||
|
||||
@@ -299,7 +299,7 @@ impl FeedbackModal {
|
||||
let installation_id = telemetry.installation_id();
|
||||
let is_staff = telemetry.is_staff();
|
||||
let http_client = zed_client.http_client();
|
||||
let feedback_endpoint = http_client.zed_url("/api/feedback");
|
||||
let feedback_endpoint = http_client.build_url("/api/feedback");
|
||||
let request = FeedbackRequestBody {
|
||||
feedback_text: &feedback_text,
|
||||
email,
|
||||
|
||||
@@ -18,10 +18,7 @@ gpui.workspace = true
|
||||
itertools = "0.11"
|
||||
menu.workspace = true
|
||||
picker.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
|
||||
@@ -663,7 +663,7 @@ impl FileFinderDelegate {
|
||||
impl PickerDelegate for FileFinderDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Search project files...".into()
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ tempfile.workspace = true
|
||||
lazy_static.workspace = true
|
||||
parking_lot.workspace = true
|
||||
smol.workspace = true
|
||||
regex.workspace = true
|
||||
git2.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
@@ -32,11 +31,17 @@ log.workspace = true
|
||||
libc = "0.2"
|
||||
time.workspace = true
|
||||
|
||||
gpui = { workspace = true, optional = true}
|
||||
gpui = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||
notify = "6.1.1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows-sys = { version = "0.52", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Storage_FileSystem",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
|
||||
|
||||
@@ -245,9 +245,8 @@ impl Fs for RealFs {
|
||||
#[cfg(unix)]
|
||||
let inode = metadata.ino();
|
||||
|
||||
// todo!("windows")
|
||||
#[cfg(windows)]
|
||||
let inode = 0;
|
||||
let inode = file_id(path).await?;
|
||||
|
||||
Ok(Some(Metadata {
|
||||
inode,
|
||||
@@ -1337,6 +1336,41 @@ pub fn copy_recursive<'a>(
|
||||
.boxed()
|
||||
}
|
||||
|
||||
// todo!(windows)
|
||||
// can we get file id not open the file twice?
|
||||
// https://github.com/rust-lang/rust/issues/63010
|
||||
#[cfg(target_os = "windows")]
|
||||
async fn file_id(path: impl AsRef<Path>) -> Result<u64> {
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
|
||||
use smol::fs::windows::OpenOptionsExt;
|
||||
use windows_sys::Win32::{
|
||||
Foundation::HANDLE,
|
||||
Storage::FileSystem::{
|
||||
GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION, FILE_FLAG_BACKUP_SEMANTICS,
|
||||
},
|
||||
};
|
||||
|
||||
let file = smol::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
|
||||
.open(path)
|
||||
.await?;
|
||||
|
||||
let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle
|
||||
// This function supports Windows XP+
|
||||
smol::unblock(move || {
|
||||
let ret = unsafe { GetFileInformationByHandle(file.as_raw_handle() as HANDLE, &mut info) };
|
||||
if ret == 0 {
|
||||
return Err(anyhow!(format!("{}", std::io::Error::last_os_error())));
|
||||
};
|
||||
|
||||
Ok(((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -9,19 +9,13 @@ license = "GPL-3.0-or-later"
|
||||
path = "src/git.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
futures.workspace = true
|
||||
git2.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
smol.workspace = true
|
||||
sum_tree.workspace = true
|
||||
text.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
unindent.workspace = true
|
||||
|
||||
@@ -13,9 +13,6 @@ doctest = false
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
menu.workspace = true
|
||||
postage.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
|
||||
@@ -10,7 +10,6 @@ license = "Apache-2.0"
|
||||
[features]
|
||||
test-support = [
|
||||
"backtrace",
|
||||
"dhat",
|
||||
"env_logger",
|
||||
"collections/test-support",
|
||||
"util/test-support",
|
||||
@@ -26,7 +25,6 @@ doctest = false
|
||||
anyhow.workspace = true
|
||||
async-task = "4.7"
|
||||
backtrace = { version = "0.3", optional = true }
|
||||
bitflags = "2.4.0"
|
||||
blade-graphics = { workspace = true, optional = true }
|
||||
blade-macros = { workspace = true, optional = true }
|
||||
blade-rwh = { workspace = true, optional = true }
|
||||
@@ -34,23 +32,22 @@ bytemuck = { version = "1", optional = true }
|
||||
collections.workspace = true
|
||||
ctor.workspace = true
|
||||
derive_more.workspace = true
|
||||
dhat = { version = "0.3", optional = true }
|
||||
env_logger = { version = "0.9", optional = true }
|
||||
etagere = "0.2"
|
||||
futures.workspace = true
|
||||
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4" }
|
||||
gpui_macros.workspace = true
|
||||
image = "0.23"
|
||||
itertools = "0.10"
|
||||
itertools.workspace = true
|
||||
lazy_static.workspace = true
|
||||
linkme = "0.3"
|
||||
log.workspace = true
|
||||
num_cpus = "1.13"
|
||||
ordered-float.workspace = true
|
||||
parking = "2.0.0"
|
||||
parking_lot.workspace = true
|
||||
pathfinder_geometry = "0.5"
|
||||
postage.workspace = true
|
||||
profiling.workspace = true
|
||||
rand.workspace = true
|
||||
raw-window-handle = "0.6"
|
||||
refineable.workspace = true
|
||||
@@ -76,10 +73,7 @@ waker-fn = "1.1.0"
|
||||
[dev-dependencies]
|
||||
backtrace = "0.3"
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
dhat = "0.3"
|
||||
env_logger.workspace = true
|
||||
png = "0.16"
|
||||
simplelog = "0.9"
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
|
||||
[build-dependencies]
|
||||
@@ -89,7 +83,7 @@ cbindgen = "0.26.0"
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
block = "0.1"
|
||||
cocoa = "0.25"
|
||||
core-foundation = { version = "0.9.3", features = ["with-uuid"] }
|
||||
core-foundation.workspace = true
|
||||
core-graphics = "0.23"
|
||||
core-text = "20.1"
|
||||
foreign-types = "0.5"
|
||||
@@ -115,3 +109,11 @@ blade-macros.workspace = true
|
||||
blade-rwh.workspace = true
|
||||
bytemuck = "1"
|
||||
cosmic-text = "0.10.0"
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
path = "examples/hello_world.rs"
|
||||
|
||||
[[example]]
|
||||
name = "image"
|
||||
path = "examples/image/image.rs"
|
||||
|
||||