Compare commits
105 Commits
fix-font-f
...
window-dec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cede52c87 | ||
|
|
fd8c0d78d4 | ||
|
|
ca72b4aea2 | ||
|
|
f1cc51081d | ||
|
|
818e6e53d6 | ||
|
|
ed09bb949c | ||
|
|
52583fe1ed | ||
|
|
8ec478cbcd | ||
|
|
6d10b16f79 | ||
|
|
05af87e416 | ||
|
|
75d2e04a1d | ||
|
|
492040dec4 | ||
|
|
47aa761ca9 | ||
|
|
98699a65c1 | ||
|
|
f024fcff3d | ||
|
|
2f05f5bc5c | ||
|
|
22a9293cba | ||
|
|
cceebee397 | ||
|
|
38fb841d1f | ||
|
|
48763d0663 | ||
|
|
089cc85d4a | ||
|
|
995b082c64 | ||
|
|
64755a7aea | ||
|
|
3348c3ab4c | ||
|
|
dceb0827e8 | ||
|
|
c1e18059f8 | ||
|
|
351a3c0815 | ||
|
|
28c5e33e0c | ||
|
|
5c7a8f779a | ||
|
|
b7cb2381f2 | ||
|
|
7db68547fa | ||
|
|
eb845ee201 | ||
|
|
8ea2bd4c7e | ||
|
|
7460381285 | ||
|
|
eab98eb9c9 | ||
|
|
6eda9c9745 | ||
|
|
8dd7c2cddf | ||
|
|
3bbe574341 | ||
|
|
51ee60b421 | ||
|
|
193be271a8 | ||
|
|
ce48555f8d | ||
|
|
ecd9422d11 | ||
|
|
0eb26d29ee | ||
|
|
3a43adba00 | ||
|
|
3419f5fc42 | ||
|
|
e7214a429d | ||
|
|
c9ac7b8e35 | ||
|
|
e243856559 | ||
|
|
c516b8f038 | ||
|
|
03447b9e18 | ||
|
|
464a4439f7 | ||
|
|
0e60730742 | ||
|
|
25ad3185e0 | ||
|
|
bac6e2fee7 | ||
|
|
065ab93ca7 | ||
|
|
83592306c5 | ||
|
|
e650c0166d | ||
|
|
f1859e3645 | ||
|
|
b1a0188467 | ||
|
|
218629cdd4 | ||
|
|
0761383752 | ||
|
|
b616f9c27f | ||
|
|
5e465f2029 | ||
|
|
7d767ff0a3 | ||
|
|
3cabd4bf64 | ||
|
|
2972bdc0e2 | ||
|
|
a295b90597 | ||
|
|
891f195f7b | ||
|
|
6e1b99b039 | ||
|
|
00d1561156 | ||
|
|
d5fbf75ccf | ||
|
|
61bbb3539a | ||
|
|
c560a24e7d | ||
|
|
da03610555 | ||
|
|
363ac6bc96 | ||
|
|
97159bd88d | ||
|
|
0b57df5deb | ||
|
|
7652a8ae23 | ||
|
|
1d193585b0 | ||
|
|
af5efcea1f | ||
|
|
228202a469 | ||
|
|
e1fbef0dfd | ||
|
|
7d7fd7d25d | ||
|
|
6a1b257d39 | ||
|
|
a695322f83 | ||
|
|
cb2d05b78f | ||
|
|
45d4de75b3 | ||
|
|
20c1f8245a | ||
|
|
b16075d00c | ||
|
|
da22e0dd0b | ||
|
|
fb3ef0d140 | ||
|
|
e71b642f44 | ||
|
|
6cedfa0ce7 | ||
|
|
209b1d1931 | ||
|
|
6986ac4c27 | ||
|
|
d50d1611b9 | ||
|
|
1260c616ba | ||
|
|
89951f7e66 | ||
|
|
cd81dad2fa | ||
|
|
3a08d7ab43 | ||
|
|
49dc63812a | ||
|
|
c0a3642f77 | ||
|
|
4d5441c09d | ||
|
|
2dc840132b | ||
|
|
5d766f61fa |
4
.github/workflows/ci.yml
vendored
@@ -254,7 +254,7 @@ jobs:
|
||||
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
target/release/Zed.dmg
|
||||
body_file: target/release-notes.md
|
||||
body_path: target/release-notes.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -402,7 +402,7 @@ jobs:
|
||||
|
||||
- name: Upload app bundle to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
|
||||
10
.mailmap
@@ -7,10 +7,12 @@
|
||||
# Reference: https://git-scm.com/docs/gitmailmap
|
||||
|
||||
# Keep these entries sorted alphabetically.
|
||||
# In Zed: `editor: sort lines case sensitive`
|
||||
# In Zed: `editor: sort lines case insensitive`
|
||||
|
||||
Alex Viscreanu <alexviscreanu@gmail.com>
|
||||
Alex Viscreanu <alexviscreanu@gmail.com> <alexandru.viscreanu@kiwi.com>
|
||||
amtoaer <amtoaer@gmail.com>
|
||||
amtoaer <amtoaer@gmail.com> <amtoaer@outlook.com>
|
||||
Antonio Scandurra <me@as-cii.com>
|
||||
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
|
||||
Bennet Bo Fenner <bennet@zed.dev>
|
||||
@@ -20,6 +22,8 @@ Christian Bergschneider <christian.bergschneider@gmx.de>
|
||||
Christian Bergschneider <christian.bergschneider@gmx.de> <magiclake@gmx.de>
|
||||
Conrad Irwin <conrad@zed.dev>
|
||||
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
|
||||
Danilo Leal <danilo@zed.dev>
|
||||
Danilo Leal <danilo@zed.dev> <67129314+danilo-leal@users.noreply.github.com>
|
||||
Evren Sen <146845123+evrsen@users.noreply.github.com>
|
||||
Fernando Tagawa <tagawafernando@gmail.com>
|
||||
Fernando Tagawa <tagawafernando@gmail.com> <fernando.tagawa.gamail.com@gmail.com>
|
||||
@@ -54,12 +58,16 @@ Nate Butler <iamnbutler@gmail.com> <nate@zed.dev>
|
||||
Nathan Sobo <nathan@zed.dev>
|
||||
Nathan Sobo <nathan@zed.dev> <nathan@warp.dev>
|
||||
Nathan Sobo <nathan@zed.dev> <nathansobo@gmail.com>
|
||||
Nigel Jose <nigelmjose@gmail.com>
|
||||
Nigel Jose <nigelmjose@gmail.com> <nigel.jose@student.manchester.ac.uk>
|
||||
Peter Tripp <peter@zed.dev>
|
||||
Peter Tripp <peter@zed.dev> <petertripp@gmail.com>
|
||||
Petros Amoiridis <petros@hey.com>
|
||||
Petros Amoiridis <petros@hey.com> <petros@zed.dev>
|
||||
Piotr Osiewicz <piotr@zed.dev>
|
||||
Piotr Osiewicz <piotr@zed.dev> <24362066+osiewicz@users.noreply.github.com>
|
||||
Pocæus <github@pocaeus.com>
|
||||
Pocæus <github@pocaeus.com> <pseudomata@proton.me>
|
||||
Rashid Almheiri <r.muhairi@pm.me>
|
||||
Rashid Almheiri <r.muhairi@pm.me> <69181766+huwaireb@users.noreply.github.com>
|
||||
Richard Feldman <oss@rtfeldman.com>
|
||||
|
||||
@@ -19,6 +19,13 @@
|
||||
"JavaScript": {
|
||||
"tab_size": 2,
|
||||
"formatter": "prettier"
|
||||
},
|
||||
"Rust": {
|
||||
"tasks": {
|
||||
"variables": {
|
||||
"RUST_DEFAULT_PACKAGE_RUN": "zed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"formatter": "auto",
|
||||
|
||||
157
Cargo.lock
generated
@@ -374,6 +374,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"assistant_slash_command",
|
||||
"async-watch",
|
||||
"breadcrumbs",
|
||||
"cargo_toml",
|
||||
"chrono",
|
||||
"client",
|
||||
@@ -382,14 +383,15 @@ dependencies = [
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"file_icons",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.28",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"heed",
|
||||
"html_to_markdown",
|
||||
"html_to_markdown 0.1.0",
|
||||
"http 0.1.0",
|
||||
"indexed_docs",
|
||||
"indoc",
|
||||
"language",
|
||||
"log",
|
||||
@@ -405,7 +407,6 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rope",
|
||||
"rustdoc",
|
||||
"schemars",
|
||||
"search",
|
||||
"semantic_index",
|
||||
@@ -417,6 +418,7 @@ dependencies = [
|
||||
"strsim 0.11.1",
|
||||
"strum",
|
||||
"telemetry_events",
|
||||
"terminal",
|
||||
"terminal_view",
|
||||
"theme",
|
||||
"tiktoken-rs",
|
||||
@@ -2551,18 +2553,13 @@ name = "collab_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"auto_update",
|
||||
"call",
|
||||
"channel",
|
||||
"client",
|
||||
"collections",
|
||||
"command_palette",
|
||||
"db",
|
||||
"dev_server_projects",
|
||||
"editor",
|
||||
"emojis",
|
||||
"extensions_ui",
|
||||
"feedback",
|
||||
"futures 0.3.28",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
@@ -2575,7 +2572,6 @@ dependencies = [
|
||||
"picker",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"recent_projects",
|
||||
"release_channel",
|
||||
"rich_text",
|
||||
"rpc",
|
||||
@@ -2587,15 +2583,14 @@ dependencies = [
|
||||
"smallvec",
|
||||
"story",
|
||||
"theme",
|
||||
"theme_selector",
|
||||
"time",
|
||||
"time_format",
|
||||
"title_bar",
|
||||
"tree-sitter-markdown",
|
||||
"ui",
|
||||
"util",
|
||||
"vcs_menu",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3922,6 +3917,7 @@ dependencies = [
|
||||
"futures 0.3.28",
|
||||
"gpui",
|
||||
"http 0.1.0",
|
||||
"indexed_docs",
|
||||
"isahc",
|
||||
"language",
|
||||
"log",
|
||||
@@ -4893,6 +4889,7 @@ dependencies = [
|
||||
"log",
|
||||
"media",
|
||||
"metal",
|
||||
"mio 1.0.0",
|
||||
"num_cpus",
|
||||
"objc",
|
||||
"oo7",
|
||||
@@ -5147,9 +5144,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.3"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
@@ -5243,6 +5240,18 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html_to_markdown"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e608e8dd0939bfb6b516d96a5919751b835297a02230aecb88d2fc84ebebaa8a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"html5ever",
|
||||
"markup5ever_rcdom",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.1.0"
|
||||
@@ -5486,6 +5495,31 @@ version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
|
||||
|
||||
[[package]]
|
||||
name = "indexed_docs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"collections",
|
||||
"derive_more",
|
||||
"fs",
|
||||
"futures 0.3.28",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"heed",
|
||||
"html_to_markdown 0.1.0",
|
||||
"http 0.1.0",
|
||||
"indexmap 1.9.3",
|
||||
"indoc",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"strum",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
@@ -5626,7 +5660,7 @@ version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.3",
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
@@ -5657,7 +5691,7 @@ dependencies = [
|
||||
"fnv",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 0.8.11",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"tempfile",
|
||||
@@ -6606,6 +6640,19 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4929e1f84c5e54c3ec6141cd5d8b5a5c055f031f80cf78f2072920173cb4d880"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.6.0"
|
||||
@@ -6844,7 +6891,7 @@ dependencies = [
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"mio 0.8.11",
|
||||
"walkdir",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
@@ -7024,7 +7071,7 @@ version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.3",
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -9012,31 +9059,6 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustdoc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"collections",
|
||||
"derive_more",
|
||||
"fs",
|
||||
"futures 0.3.28",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"heed",
|
||||
"html_to_markdown",
|
||||
"http 0.1.0",
|
||||
"indexmap 1.9.3",
|
||||
"indoc",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"strum",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.23"
|
||||
@@ -11046,6 +11068,41 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "title_bar"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"auto_update",
|
||||
"call",
|
||||
"client",
|
||||
"collections",
|
||||
"command_palette",
|
||||
"dev_server_projects",
|
||||
"editor",
|
||||
"extensions_ui",
|
||||
"feedback",
|
||||
"gpui",
|
||||
"http 0.1.0",
|
||||
"notifications",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"recent_projects",
|
||||
"rpc",
|
||||
"serde",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"story",
|
||||
"theme",
|
||||
"theme_selector",
|
||||
"tree-sitter-markdown",
|
||||
"ui",
|
||||
"util",
|
||||
"vcs_menu",
|
||||
"windows 0.57.0",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.37.0"
|
||||
@@ -11055,7 +11112,7 @@ dependencies = [
|
||||
"backtrace",
|
||||
"bytes 1.5.0",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 0.8.11",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
@@ -13550,7 +13607,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.142.0"
|
||||
version = "0.144.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
@@ -13750,6 +13807,7 @@ dependencies = [
|
||||
name = "zed_gleam"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"html_to_markdown 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zed_extension_api 0.0.7",
|
||||
]
|
||||
|
||||
@@ -13797,7 +13855,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_prisma"
|
||||
version = "0.0.2"
|
||||
version = "0.0.3"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.4",
|
||||
]
|
||||
@@ -13838,6 +13896,13 @@ dependencies = [
|
||||
"zed_extension_api 0.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_test_extension"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_toml"
|
||||
version = "0.1.1"
|
||||
@@ -13861,7 +13926,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_zig"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.7",
|
||||
]
|
||||
|
||||
49
Cargo.toml
@@ -21,6 +21,7 @@ members = [
|
||||
"crates/command_palette_hooks",
|
||||
"crates/copilot",
|
||||
"crates/db",
|
||||
"crates/dev_server_projects",
|
||||
"crates/diagnostics",
|
||||
"crates/editor",
|
||||
"crates/extension",
|
||||
@@ -44,6 +45,7 @@ members = [
|
||||
"crates/html_to_markdown",
|
||||
"crates/http",
|
||||
"crates/image_viewer",
|
||||
"crates/indexed_docs",
|
||||
"crates/inline_completion_button",
|
||||
"crates/install_cli",
|
||||
"crates/journal",
|
||||
@@ -77,14 +79,10 @@ members = [
|
||||
"crates/refineable",
|
||||
"crates/refineable/derive_refineable",
|
||||
"crates/release_channel",
|
||||
"crates/dev_server_projects",
|
||||
"crates/repl",
|
||||
"crates/rich_text",
|
||||
"crates/rope",
|
||||
"crates/rpc",
|
||||
"crates/rustdoc",
|
||||
"crates/task",
|
||||
"crates/tasks_ui",
|
||||
"crates/search",
|
||||
"crates/semantic_index",
|
||||
"crates/semantic_version",
|
||||
@@ -95,17 +93,20 @@ members = [
|
||||
"crates/story",
|
||||
"crates/storybook",
|
||||
"crates/sum_tree",
|
||||
"crates/tab_switcher",
|
||||
"crates/supermaven",
|
||||
"crates/supermaven_api",
|
||||
"crates/tab_switcher",
|
||||
"crates/task",
|
||||
"crates/tasks_ui",
|
||||
"crates/telemetry_events",
|
||||
"crates/terminal",
|
||||
"crates/terminal_view",
|
||||
"crates/text",
|
||||
"crates/theme",
|
||||
"crates/theme_importer",
|
||||
"crates/theme_selector",
|
||||
"crates/telemetry_events",
|
||||
"crates/time_format",
|
||||
"crates/title_bar",
|
||||
"crates/ui",
|
||||
"crates/ui_text_field",
|
||||
"crates/util",
|
||||
@@ -139,6 +140,7 @@ members = [
|
||||
"extensions/snippets",
|
||||
"extensions/svelte",
|
||||
"extensions/terraform",
|
||||
"extensions/test-extension",
|
||||
"extensions/toml",
|
||||
"extensions/uiua",
|
||||
"extensions/vue",
|
||||
@@ -157,10 +159,8 @@ assets = { path = "crates/assets" }
|
||||
assistant = { path = "crates/assistant" }
|
||||
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
||||
assistant_tooling = { path = "crates/assistant_tooling" }
|
||||
async-watch = "0.3.1"
|
||||
audio = { path = "crates/audio" }
|
||||
auto_update = { path = "crates/auto_update" }
|
||||
base64 = "0.13"
|
||||
breadcrumbs = { path = "crates/breadcrumbs" }
|
||||
call = { path = "crates/call" }
|
||||
channel = { path = "crates/channel" }
|
||||
@@ -173,8 +173,8 @@ collections = { path = "crates/collections" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||
copilot = { path = "crates/copilot" }
|
||||
dashmap = "5.5.3"
|
||||
db = { path = "crates/db" }
|
||||
dev_server_projects = { path = "crates/dev_server_projects" }
|
||||
diagnostics = { path = "crates/diagnostics" }
|
||||
editor = { path = "crates/editor" }
|
||||
extension = { path = "crates/extension" }
|
||||
@@ -195,9 +195,10 @@ gpui_macros = { path = "crates/gpui_macros" }
|
||||
headless = { path = "crates/headless" }
|
||||
html_to_markdown = { path = "crates/html_to_markdown" }
|
||||
http = { path = "crates/http" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
image_viewer = { path = "crates/image_viewer" }
|
||||
indexed_docs = { path = "crates/indexed_docs" }
|
||||
inline_completion_button = { path = "crates/inline_completion_button" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
journal = { path = "crates/journal" }
|
||||
language = { path = "crates/language" }
|
||||
language_selector = { path = "crates/language_selector" }
|
||||
@@ -223,21 +224,16 @@ plugin = { path = "crates/plugin" }
|
||||
plugin_macros = { path = "crates/plugin_macros" }
|
||||
prettier = { path = "crates/prettier" }
|
||||
project = { path = "crates/project" }
|
||||
proto = { path = "crates/proto" }
|
||||
worktree = { path = "crates/worktree" }
|
||||
project_panel = { path = "crates/project_panel" }
|
||||
project_symbols = { path = "crates/project_symbols" }
|
||||
proto = { path = "crates/proto" }
|
||||
quick_action_bar = { path = "crates/quick_action_bar" }
|
||||
recent_projects = { path = "crates/recent_projects" }
|
||||
release_channel = { path = "crates/release_channel" }
|
||||
dev_server_projects = { path = "crates/dev_server_projects" }
|
||||
repl = { path = "crates/repl" }
|
||||
rich_text = { path = "crates/rich_text" }
|
||||
rope = { path = "crates/rope" }
|
||||
rpc = { path = "crates/rpc" }
|
||||
rustdoc = { path = "crates/rustdoc" }
|
||||
task = { path = "crates/task" }
|
||||
tasks_ui = { path = "crates/tasks_ui" }
|
||||
search = { path = "crates/search" }
|
||||
semantic_index = { path = "crates/semantic_index" }
|
||||
semantic_version = { path = "crates/semantic_version" }
|
||||
@@ -245,20 +241,23 @@ settings = { path = "crates/settings" }
|
||||
snippet = { path = "crates/snippet" }
|
||||
sqlez = { path = "crates/sqlez" }
|
||||
sqlez_macros = { path = "crates/sqlez_macros" }
|
||||
supermaven = { path = "crates/supermaven" }
|
||||
supermaven_api = { path = "crates/supermaven_api" }
|
||||
story = { path = "crates/story" }
|
||||
storybook = { path = "crates/storybook" }
|
||||
sum_tree = { path = "crates/sum_tree" }
|
||||
supermaven = { path = "crates/supermaven" }
|
||||
supermaven_api = { path = "crates/supermaven_api" }
|
||||
tab_switcher = { path = "crates/tab_switcher" }
|
||||
task = { path = "crates/task" }
|
||||
tasks_ui = { path = "crates/tasks_ui" }
|
||||
telemetry_events = { path = "crates/telemetry_events" }
|
||||
terminal = { path = "crates/terminal" }
|
||||
terminal_view = { path = "crates/terminal_view" }
|
||||
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" }
|
||||
time_format = { path = "crates/time_format" }
|
||||
title_bar = { path = "crates/title_bar" }
|
||||
ui = { path = "crates/ui" }
|
||||
ui_text_field = { path = "crates/ui_text_field" }
|
||||
util = { path = "crates/util" }
|
||||
@@ -266,12 +265,13 @@ vcs_menu = { path = "crates/vcs_menu" }
|
||||
vim = { path = "crates/vim" }
|
||||
welcome = { path = "crates/welcome" }
|
||||
workspace = { path = "crates/workspace" }
|
||||
worktree = { path = "crates/worktree" }
|
||||
zed = { path = "crates/zed" }
|
||||
zed_actions = { path = "crates/zed_actions" }
|
||||
|
||||
alacritty_terminal = "0.23"
|
||||
anyhow = "1.0.57"
|
||||
any_vec = "0.13"
|
||||
anyhow = "1.0.57"
|
||||
ashpd = "0.8.0"
|
||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||
async-dispatcher = { version = "0.1"}
|
||||
@@ -279,20 +279,23 @@ async-fs = "1.6"
|
||||
async-recursion = "1.0.0"
|
||||
async-tar = "0.4.2"
|
||||
async-trait = "0.1"
|
||||
async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
base64 = "0.13"
|
||||
bitflags = "2.4.2"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
||||
cap-std = "3.0"
|
||||
cargo_toml = "0.20"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
clickhouse = { version = "0.11.6" }
|
||||
cocoa = "0.25"
|
||||
ctor = "0.2.6"
|
||||
core-foundation = { version = "0.9.3" }
|
||||
core-foundation-sys = "0.8.6"
|
||||
ctor = "0.2.6"
|
||||
dashmap = "5.5.3"
|
||||
derive_more = "0.99.17"
|
||||
dirs = "4.0"
|
||||
emojis = "0.6.1"
|
||||
|
||||
BIN
assets/fonts/plex-mono/ZedPlexMono-Bold.ttf
Normal file
BIN
assets/fonts/plex-mono/ZedPlexMono-BoldItalic.ttf
Normal file
BIN
assets/fonts/plex-mono/ZedPlexMono-Italic.ttf
Normal file
BIN
assets/fonts/plex-mono/ZedPlexMono-Regular.ttf
Normal file
92
assets/fonts/plex-mono/license.txt
Normal file
@@ -0,0 +1,92 @@
|
||||
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
assets/fonts/plex-sans/ZedPlexSans-Bold.ttf
Normal file
BIN
assets/fonts/plex-sans/ZedPlexSans-BoldItalic.ttf
Normal file
BIN
assets/fonts/plex-sans/ZedPlexSans-Italic.ttf
Normal file
BIN
assets/fonts/plex-sans/ZedPlexSans-Regular.ttf
Normal file
92
assets/fonts/plex-sans/license.txt
Normal file
@@ -0,0 +1,92 @@
|
||||
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
1
assets/icons/chevron_up_down.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-up-down"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
|
||||
|
After Width: | Height: | Size: 276 B |
@@ -94,6 +94,7 @@
|
||||
"lua": "lua",
|
||||
"m4a": "audio",
|
||||
"m4v": "video",
|
||||
"markdown": "document",
|
||||
"md": "document",
|
||||
"mdb": "storage",
|
||||
"mdf": "storage",
|
||||
|
||||
1
assets/icons/font.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-type"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" x2="15" y1="20" y2="20"/><line x1="12" x2="12" y1="4" y2="20"/></svg>
|
||||
|
After Width: | Height: | Size: 329 B |
1
assets/icons/font_size.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-a-large-small"><path d="M21 14h-5"/><path d="M16 16v-3.5a2.5 2.5 0 0 1 5 0V16"/><path d="M4.5 13h6"/><path d="m3 16 4.5-9 4.5 9"/></svg>
|
||||
|
After Width: | Height: | Size: 339 B |
1
assets/icons/font_weight.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bold"><path d="M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8"/></svg>
|
||||
|
After Width: | Height: | Size: 296 B |
4
assets/icons/generic_close.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.5 4.5L4.5 11.5" stroke="black" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
<path d="M4.5 4.5L11.5 11.5" stroke="black" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 291 B |
3
assets/icons/generic_maximize.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.5 4.5H4.5V11.5H11.5V4.5Z" stroke="#FBF1C7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 161 B |
3
assets/icons/generic_minimize.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 8H12" stroke="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 138 B |
4
assets/icons/generic_restore.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 6.5H3.5V12.5H9.5V6.5Z" stroke="#FBF1C7"/>
|
||||
<path d="M10 8.5L12.5 8.5L12.5 3.5L7.5 3.5L7.5 6" stroke="#FBF1C7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 228 B |
6
assets/icons/line_height.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 13.6667H12" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 2.33333H12" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 11L8 5L11 11" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 9H10" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 539 B |
1
assets/icons/visible.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
|
After Width: | Height: | Size: 301 B |
@@ -70,11 +70,8 @@
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
"a": "project_panel::NewFile",
|
||||
"shift-a": "project_panel::NewDirectory",
|
||||
"f2": "project_panel::Rename",
|
||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"shift-d": "project_panel::Duplicate",
|
||||
"cmd-x": "project_panel::Cut",
|
||||
"cmd-c": "project_panel::Copy",
|
||||
"cmd-v": "project_panel::Paste",
|
||||
@@ -85,5 +82,13 @@
|
||||
"ctrl-f": "project_panel::ExpandSelectedEntry",
|
||||
"ctrl-shift-c": "project_panel::CopyPath"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel && not_editing",
|
||||
"bindings": {
|
||||
"a": "project_panel::NewFile",
|
||||
"shift-a": "project_panel::NewDirectory",
|
||||
"shift-d": "project_panel::Duplicate"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -55,11 +55,13 @@
|
||||
"up": "editor::MoveUp",
|
||||
"ctrl-up": "editor::LineUp",
|
||||
"ctrl-down": "editor::LineDown",
|
||||
"pageup": "editor::PageUp",
|
||||
"pageup": "editor::MovePageUp",
|
||||
"alt-pageup": "editor::PageUp",
|
||||
"shift-pageup": "editor::SelectPageUp",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"down": "editor::MoveDown",
|
||||
"pagedown": "editor::PageDown",
|
||||
"pagedown": "editor::MovePageDown",
|
||||
"alt-pagedown": "editor::PageDown",
|
||||
"shift-pagedown": "editor::SelectPageDown",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"left": "editor::MoveLeft",
|
||||
@@ -153,6 +155,7 @@
|
||||
// }
|
||||
// ],
|
||||
"ctrl->": "assistant::QuoteSelection",
|
||||
"ctrl-<": "assistant::InsertIntoEditor",
|
||||
"ctrl-alt-e": "editor::SelectEnclosingSymbol"
|
||||
}
|
||||
},
|
||||
@@ -546,6 +549,7 @@
|
||||
"ctrl-enter": "assistant::Assist",
|
||||
"ctrl-s": "workspace::Save",
|
||||
"ctrl->": "assistant::QuoteSelection",
|
||||
"ctrl-<": "assistant::InsertIntoEditor",
|
||||
"shift-enter": "assistant::Split",
|
||||
"ctrl-r": "assistant::CycleMessageRole",
|
||||
"enter": "assistant::ConfirmCommand",
|
||||
@@ -651,13 +655,20 @@
|
||||
"ctrl-insert": "terminal::Copy",
|
||||
"shift-ctrl-v": "terminal::Paste",
|
||||
"shift-insert": "terminal::Paste",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"up": ["terminal::SendKeystroke", "up"],
|
||||
"pageup": ["terminal::SendKeystroke", "pageup"],
|
||||
"down": ["terminal::SendKeystroke", "down"],
|
||||
"pagedown": ["terminal::SendKeystroke", "pagedown"],
|
||||
"escape": ["terminal::SendKeystroke", "escape"],
|
||||
"enter": ["terminal::SendKeystroke", "enter"],
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"]
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
||||
"shift-pageup": "terminal::ScrollPageUp",
|
||||
"shift-pagedown": "terminal::ScrollPageDown",
|
||||
"shift-up": "terminal::ScrollLineUp",
|
||||
"shift-down": "terminal::ScrollLineDown",
|
||||
"shift-home": "terminal::ScrollToTop",
|
||||
"shift-end": "terminal::ScrollToBottom"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -61,13 +61,17 @@
|
||||
"cmd-shift-z": "editor::Redo",
|
||||
"up": "editor::MoveUp",
|
||||
"ctrl-up": "editor::MoveToStartOfParagraph",
|
||||
"pageup": "editor::PageUp",
|
||||
"shift-pageup": "editor::MovePageUp",
|
||||
"pageup": "editor::MovePageUp",
|
||||
"shift-pageup": "editor::SelectPageUp",
|
||||
"cmd-pageup": "editor::PageUp",
|
||||
"ctrl-pageup": "editor::LineUp",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"down": "editor::MoveDown",
|
||||
"ctrl-down": "editor::MoveToEndOfParagraph",
|
||||
"pagedown": "editor::PageDown",
|
||||
"shift-pagedown": "editor::MovePageDown",
|
||||
"pagedown": "editor::MovePageDown",
|
||||
"shift-pagedown": "editor::SelectPageDown",
|
||||
"cmd-pagedown": "editor::PageDown",
|
||||
"ctrl-pagedown": "editor::LineDown",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"left": "editor::MoveLeft",
|
||||
"right": "editor::MoveRight",
|
||||
@@ -189,6 +193,7 @@
|
||||
}
|
||||
],
|
||||
"cmd->": "assistant::QuoteSelection",
|
||||
"cmd-<": "assistant::InsertIntoEditor",
|
||||
"cmd-alt-e": "editor::SelectEnclosingSymbol"
|
||||
}
|
||||
},
|
||||
@@ -234,6 +239,7 @@
|
||||
"cmd-enter": "assistant::Assist",
|
||||
"cmd-s": "workspace::Save",
|
||||
"cmd->": "assistant::QuoteSelection",
|
||||
"cmd-<": "assistant::InsertIntoEditor",
|
||||
"shift-enter": "assistant::Split",
|
||||
"ctrl-r": "assistant::CycleMessageRole",
|
||||
"enter": "assistant::ConfirmCommand",
|
||||
@@ -684,6 +690,7 @@
|
||||
"cmd-c": "terminal::Copy",
|
||||
"cmd-v": "terminal::Paste",
|
||||
"cmd-k": "terminal::Clear",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
// Some nice conveniences
|
||||
"cmd-backspace": ["terminal::SendText", "\u0015"],
|
||||
"cmd-right": ["terminal::SendText", "\u0005"],
|
||||
@@ -699,7 +706,13 @@
|
||||
"pagedown": ["terminal::SendKeystroke", "pagedown"],
|
||||
"escape": ["terminal::SendKeystroke", "escape"],
|
||||
"enter": ["terminal::SendKeystroke", "enter"],
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"]
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
||||
"shift-pageup": "terminal::ScrollPageUp",
|
||||
"shift-pagedown": "terminal::ScrollPageDown",
|
||||
"shift-up": "terminal::ScrollLineUp",
|
||||
"shift-down": "terminal::ScrollLineDown",
|
||||
"shift-home": "terminal::ScrollToTop",
|
||||
"shift-end": "terminal::ScrollToBottom"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
"bindings": {
|
||||
"cmd-shift-o": "file_finder::Toggle",
|
||||
"cmd-shift-a": "command_palette::Toggle",
|
||||
"shift shift": "command_palette::Toggle",
|
||||
"cmd-alt-o": "project_symbols::Toggle",
|
||||
"cmd-1": "workspace::ToggleLeftDock",
|
||||
"cmd-6": "diagnostics::Deploy"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
|
||||
"cmd-shift-l": "editor::SplitSelectionIntoLines",
|
||||
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
|
||||
"cmd-shift-d": "editor::DuplicateLineDown",
|
||||
"shift-f12": "editor::FindAllReferences",
|
||||
"alt-cmd-down": "editor::GoToDefinition",
|
||||
"ctrl-alt-cmd-down": "editor::GoToDefinitionSplit",
|
||||
|
||||
@@ -47,19 +47,16 @@
|
||||
"{": "vim::StartOfParagraph",
|
||||
"}": "vim::EndOfParagraph",
|
||||
"|": "vim::GoToColumn",
|
||||
|
||||
// Word motions
|
||||
"w": "vim::NextWordStart",
|
||||
"e": "vim::NextWordEnd",
|
||||
"b": "vim::PreviousWordStart",
|
||||
"g e": "vim::PreviousWordEnd",
|
||||
|
||||
// Subword motions
|
||||
// "w": "vim::NextSubwordStart",
|
||||
// "b": "vim::PreviousSubwordStart",
|
||||
// "e": "vim::NextSubwordEnd",
|
||||
// "g e": "vim::PreviousSubwordEnd",
|
||||
|
||||
"shift-w": [
|
||||
"vim::NextWordStart",
|
||||
{
|
||||
@@ -78,8 +75,12 @@
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
|
||||
|
||||
"g shift-e": [
|
||||
"vim::PreviousWordEnd",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"/": "vim::Search",
|
||||
"g /": "pane::DeploySearch",
|
||||
"?": [
|
||||
@@ -126,8 +127,22 @@
|
||||
}
|
||||
],
|
||||
"m": ["vim::PushOperator", "Mark"],
|
||||
"'": ["vim::PushOperator", { "Jump": { "line": true } }],
|
||||
"`": ["vim::PushOperator", { "Jump": { "line": false } }],
|
||||
"'": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Jump": {
|
||||
"line": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"`": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Jump": {
|
||||
"line": false
|
||||
}
|
||||
}
|
||||
],
|
||||
";": "vim::RepeatFind",
|
||||
",": "vim::RepeatFindReversed",
|
||||
"ctrl-o": "pane::GoBack",
|
||||
@@ -331,7 +346,6 @@
|
||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w n": ["workspace::NewFileInDirection", "Up"],
|
||||
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"],
|
||||
|
||||
"ctrl-w d": "editor::GoToDefinitionSplit",
|
||||
"ctrl-w g d": "editor::GoToDefinitionSplit",
|
||||
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
|
||||
@@ -389,6 +403,9 @@
|
||||
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
||||
"g ~": ["vim::PushOperator", "OppositeCase"],
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
"q": "vim::ToggleRecord",
|
||||
"shift-q": "vim::ReplayLastRecording",
|
||||
"@": ["vim::PushOperator", "ReplayRegister"],
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
// tree-sitter related commands
|
||||
@@ -676,7 +693,8 @@
|
||||
{
|
||||
"context": "EmptyPane || SharedScreen",
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle"
|
||||
":": "command_palette::Toggle",
|
||||
"g /": "pane::DeploySearch"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -704,5 +722,14 @@
|
||||
"g g": "menu::SelectFirst",
|
||||
"-": "project_panel::SelectParent"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "OutlinePanel",
|
||||
"bindings": {
|
||||
"j": "menu::SelectNext",
|
||||
"k": "menu::SelectPrev",
|
||||
"shift-g": "menu::SelectLast",
|
||||
"g g": "menu::SelectFirst"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
{
|
||||
// The name of the Zed theme to use for the UI.
|
||||
//
|
||||
// The theme can also be set to follow system preferences:
|
||||
//
|
||||
// "theme": {
|
||||
// "mode": "system",
|
||||
// "light": "One Light",
|
||||
// "dark": "One Dark"
|
||||
// }
|
||||
//
|
||||
// Where `mode` is one of:
|
||||
// `mode` is one of:
|
||||
// - "system": Use the theme that corresponds to the system's appearance
|
||||
// - "light": Use the theme indicated by the "light" field
|
||||
// - "dark": Use the theme indicated by the "dark" field
|
||||
"theme": "One Dark",
|
||||
"theme": {
|
||||
"mode": "system",
|
||||
"light": "One Light",
|
||||
"dark": "One Dark"
|
||||
},
|
||||
// The name of a base set of key bindings to use.
|
||||
// This setting can take four values, each named after another
|
||||
// text editor:
|
||||
@@ -29,7 +25,7 @@
|
||||
"inline_completion_provider": "copilot"
|
||||
},
|
||||
// The name of a font to use for rendering text in the editor
|
||||
"buffer_font_family": "Zed Mono",
|
||||
"buffer_font_family": "Zed Plex Mono",
|
||||
// The OpenType features to enable for text in the editor.
|
||||
"buffer_font_features": {
|
||||
// Disable ligatures:
|
||||
@@ -42,16 +38,17 @@
|
||||
// Set the buffer's line height.
|
||||
// May take 3 values:
|
||||
// 1. Use a line height that's comfortable for reading (1.618)
|
||||
// "line_height": "comfortable"
|
||||
// "buffer_line_height": "comfortable"
|
||||
// 2. Use a standard line height, (1.3)
|
||||
// "line_height": "standard",
|
||||
// "buffer_line_height": "standard",
|
||||
// 3. Use a custom line height
|
||||
// "line_height": {
|
||||
// "buffer_line_height": {
|
||||
// "custom": 2
|
||||
// },
|
||||
"buffer_line_height": "comfortable",
|
||||
// The name of a font to use for rendering text in the UI
|
||||
"ui_font_family": ".SystemUIFont",
|
||||
// (On macOS) You can set this to ".SysmtemUIFont" to use the system font
|
||||
"ui_font_family": "Zed Plex Sans",
|
||||
// The OpenType features to enable for text in the UI
|
||||
"ui_font_features": {
|
||||
// Disable ligatures:
|
||||
@@ -165,11 +162,11 @@
|
||||
// 1. Draw tabs and spaces only for the selected text (default):
|
||||
// "selection"
|
||||
// 2. Do not draw any tabs or spaces:
|
||||
// "none"
|
||||
// "none"
|
||||
// 3. Draw all invisible symbols:
|
||||
// "all"
|
||||
// "all"
|
||||
// 4. Draw whitespaces at boundaries only:
|
||||
// "boundaries"
|
||||
// "boundary"
|
||||
// For a whitespace to be on a boundary, any of the following conditions need to be met:
|
||||
// - It is a tab
|
||||
// - It is adjacent to an edge (start or end)
|
||||
@@ -310,8 +307,8 @@
|
||||
// when a corresponding project entry becomes active.
|
||||
// Gitignored entries are never auto revealed.
|
||||
"auto_reveal_entries": true,
|
||||
/// Whether to fold directories automatically
|
||||
/// when a directory has only one directory inside.
|
||||
// Whether to fold directories automatically and show compact folders
|
||||
// (e.g. "a/b/c" ) when a directory has only one subdirectory inside.
|
||||
"auto_fold_dirs": false,
|
||||
/// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
@@ -669,13 +666,17 @@
|
||||
// "font_size": 15,
|
||||
// Set the terminal's font family. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font family.
|
||||
// "font_family": "Zed Mono",
|
||||
// "font_family": "Zed Plex Mono",
|
||||
// Sets the maximum number of lines in the terminal's scrollback buffer.
|
||||
// Default: 10_000, maximum: 100_000 (all bigger values set will be treated as 100_000), 0 disables the scrolling.
|
||||
// Existing terminals will not pick up this change until they are recreated.
|
||||
// "max_scroll_history_lines": 10000,
|
||||
},
|
||||
"code_actions_on_format": {},
|
||||
/// Settings related to running tasks.
|
||||
"tasks": {
|
||||
"variables": {}
|
||||
},
|
||||
// An object whose keys are language names, and whose values
|
||||
// are arrays of filenames or extensions of files that should
|
||||
// use those languages.
|
||||
@@ -688,7 +689,9 @@
|
||||
// "TOML": ["Embargo.lock"]
|
||||
// }
|
||||
//
|
||||
"file_types": {},
|
||||
"file_types": {
|
||||
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json"]
|
||||
},
|
||||
// The extensions that Zed should automatically install on startup.
|
||||
//
|
||||
// If you don't want any of these extensions, add this field to your settings
|
||||
@@ -758,6 +761,11 @@
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"JSONC": {
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"Markdown": {
|
||||
"format_on_save": "off",
|
||||
"prettier": {
|
||||
|
||||
@@ -8,5 +8,10 @@
|
||||
// from the command palette or from `Zed` application menu.
|
||||
{
|
||||
"ui_font_size": 16,
|
||||
"buffer_font_size": 16
|
||||
"buffer_font_size": 16,
|
||||
"theme": {
|
||||
"mode": "system",
|
||||
"light": "One Light",
|
||||
"dark": "One Dark"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,4 +52,13 @@ impl Assets {
|
||||
|
||||
cx.text_system().add_fonts(embedded_fonts)
|
||||
}
|
||||
|
||||
pub fn load_test_fonts(&self, cx: &AppContext) {
|
||||
cx.text_system()
|
||||
.add_fonts(vec![self
|
||||
.load("fonts/plex-mono/ZedPlexMono-Regular.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,18 @@ path = "src/assistant.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
anthropic = { workspace = true, features = ["schemars"] }
|
||||
anyhow.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
async-watch.workspace = true
|
||||
breadcrumbs.workspace = true
|
||||
cargo_toml.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
editor.workspace = true
|
||||
file_icons.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
@@ -31,6 +32,7 @@ gpui.workspace = true
|
||||
heed.workspace = true
|
||||
html_to_markdown.workspace = true
|
||||
http.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
indoc.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
@@ -44,7 +46,6 @@ paths.workspace = true
|
||||
project.workspace = true
|
||||
regex.workspace = true
|
||||
rope.workspace = true
|
||||
rustdoc.workspace = true
|
||||
schemars.workspace = true
|
||||
search.workspace = true
|
||||
semantic_index.workspace = true
|
||||
@@ -56,6 +57,7 @@ smol.workspace = true
|
||||
strsim = "0.11"
|
||||
strum.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
terminal.workspace = true
|
||||
terminal_view.workspace = true
|
||||
theme.workspace = true
|
||||
tiktoken-rs.workspace = true
|
||||
|
||||
@@ -9,6 +9,7 @@ mod prompts;
|
||||
mod search;
|
||||
mod slash_command;
|
||||
mod streaming_diff;
|
||||
mod terminal_inline_assistant;
|
||||
|
||||
pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
|
||||
use assistant_settings::{AnthropicModel, AssistantSettings, CloudModel, OllamaModel, OpenAiModel};
|
||||
@@ -19,15 +20,16 @@ pub(crate) use completion_provider::*;
|
||||
pub(crate) use context_store::*;
|
||||
use fs::Fs;
|
||||
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
pub(crate) use inline_assistant::*;
|
||||
pub(crate) use model_selector::*;
|
||||
use rustdoc::RustdocStore;
|
||||
use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use slash_command::{
|
||||
active_command, default_command, diagnostics_command, fetch_command, file_command, now_command,
|
||||
project_command, prompt_command, rustdoc_command, search_command, tabs_command, term_command,
|
||||
active_command, default_command, diagnostics_command, docs_command, fetch_command,
|
||||
file_command, now_command, project_command, prompt_command, search_command, tabs_command,
|
||||
term_command,
|
||||
};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
@@ -42,11 +44,13 @@ actions!(
|
||||
Split,
|
||||
CycleMessageRole,
|
||||
QuoteSelection,
|
||||
InsertIntoEditor,
|
||||
ToggleFocus,
|
||||
ResetKey,
|
||||
InlineAssist,
|
||||
InsertActivePrompt,
|
||||
ToggleHistory,
|
||||
DeployHistory,
|
||||
DeployPromptLibrary,
|
||||
ApplyEdit,
|
||||
ConfirmCommand,
|
||||
ToggleModelSelector
|
||||
@@ -289,7 +293,8 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
|
||||
register_slash_commands(cx);
|
||||
assistant_panel::init(cx);
|
||||
inline_assistant::init(fs.clone(), client.telemetry().clone(), cx);
|
||||
RustdocStore::init_global(cx);
|
||||
terminal_inline_assistant::init(fs.clone(), client.telemetry().clone(), cx);
|
||||
IndexedDocsRegistry::init_global(cx);
|
||||
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(Assistant::NAMESPACE);
|
||||
@@ -319,8 +324,8 @@ fn register_slash_commands(cx: &mut AppContext) {
|
||||
slash_command_registry.register_command(default_command::DefaultSlashCommand, true);
|
||||
slash_command_registry.register_command(term_command::TermSlashCommand, true);
|
||||
slash_command_registry.register_command(now_command::NowSlashCommand, true);
|
||||
slash_command_registry.register_command(diagnostics_command::DiagnosticsCommand, true);
|
||||
slash_command_registry.register_command(rustdoc_command::RustdocSlashCommand, false);
|
||||
slash_command_registry.register_command(diagnostics_command::DiagnosticsSlashCommand, true);
|
||||
slash_command_registry.register_command(docs_command::DocsSlashCommand, true);
|
||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -236,7 +236,7 @@ pub fn preprocess_anthropic_request(request: &mut LanguageModelRequest) {
|
||||
}
|
||||
|
||||
if !system_message.is_empty() {
|
||||
request.messages.insert(
|
||||
new_messages.insert(
|
||||
0,
|
||||
LanguageModelRequestMessage {
|
||||
role: Role::System,
|
||||
|
||||
@@ -18,9 +18,9 @@ use editor::{
|
||||
use fs::Fs;
|
||||
use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
|
||||
use gpui::{
|
||||
point, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Global,
|
||||
HighlightStyle, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View,
|
||||
ViewContext, WeakView, WhiteSpace, WindowContext,
|
||||
point, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, Global, HighlightStyle,
|
||||
Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView,
|
||||
WhiteSpace, WindowContext,
|
||||
};
|
||||
use language::{Buffer, Point, Selection, TransactionId};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
@@ -1730,7 +1730,7 @@ impl PromptEditor {
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features.clone(),
|
||||
font_size: rems(0.875).into(),
|
||||
font_weight: FontWeight::NORMAL,
|
||||
font_weight: settings.ui_font.weight,
|
||||
font_style: FontStyle::Normal,
|
||||
line_height: relative(1.3),
|
||||
background_color: None,
|
||||
|
||||
@@ -79,6 +79,6 @@ impl RenderOnce for ModelSelector {
|
||||
Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
|
||||
}),
|
||||
)
|
||||
.anchor(gpui::AnchorCorner::BottomRight)
|
||||
.attach(gpui::AnchorCorner::BottomLeft)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::HashMap;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle};
|
||||
use futures::{
|
||||
future::{self, BoxFuture, Shared},
|
||||
@@ -34,7 +34,7 @@ use std::{
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
|
||||
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
|
||||
SharedString, Styled, Tooltip, ViewContext, VisualContext,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
@@ -42,7 +42,12 @@ use workspace::Workspace;
|
||||
|
||||
actions!(
|
||||
prompt_library,
|
||||
[NewPrompt, DeletePrompt, ToggleDefaultPrompt]
|
||||
[
|
||||
NewPrompt,
|
||||
DeletePrompt,
|
||||
DuplicatePrompt,
|
||||
ToggleDefaultPrompt
|
||||
]
|
||||
);
|
||||
|
||||
/// Init starts loading the PromptStore in the background and assigns
|
||||
@@ -411,6 +416,12 @@ impl PromptLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duplicate_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_prompt_id) = self.active_prompt_id {
|
||||
self.duplicate_prompt(active_prompt_id, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_default_for_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_prompt_id) = self.active_prompt_id {
|
||||
self.toggle_default_for_prompt(active_prompt_id, cx);
|
||||
@@ -564,6 +575,47 @@ impl PromptLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duplicate_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
|
||||
if let Some(prompt) = self.prompt_editors.get(&prompt_id) {
|
||||
const DUPLICATE_SUFFIX: &str = " copy";
|
||||
let title_to_duplicate = prompt.title_editor.read(cx).text(cx);
|
||||
let existing_titles = self
|
||||
.prompt_editors
|
||||
.iter()
|
||||
.filter(|&(&id, _)| id != prompt_id)
|
||||
.map(|(_, prompt_editor)| prompt_editor.title_editor.read(cx).text(cx))
|
||||
.filter(|title| title.starts_with(&title_to_duplicate))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let title = if existing_titles.is_empty() {
|
||||
title_to_duplicate + DUPLICATE_SUFFIX
|
||||
} else {
|
||||
let mut i = 1;
|
||||
loop {
|
||||
let new_title = format!("{title_to_duplicate}{DUPLICATE_SUFFIX} {i}");
|
||||
if !existing_titles.contains(&new_title) {
|
||||
break new_title;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
};
|
||||
|
||||
let new_id = PromptId::new();
|
||||
let body = prompt.body_editor.read(cx).text(cx);
|
||||
let save = self
|
||||
.store
|
||||
.save(new_id, Some(title.into()), false, body.into());
|
||||
self.picker.update(cx, |picker, cx| picker.refresh(cx));
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
save.await?;
|
||||
this.update(&mut cx, |prompt_library, cx| {
|
||||
prompt_library.load_prompt(new_id, true, cx)
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_active_prompt(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_prompt) = self.active_prompt_id {
|
||||
self.prompt_editors[&active_prompt]
|
||||
@@ -721,7 +773,7 @@ impl PromptLibrary {
|
||||
.child(
|
||||
h_flex()
|
||||
.p(Spacing::Small.rems(cx))
|
||||
.h(TitleBar::height(cx))
|
||||
.h_9()
|
||||
.w_full()
|
||||
.flex_none()
|
||||
.justify_end()
|
||||
@@ -897,24 +949,28 @@ impl PromptLibrary {
|
||||
cx.dispatch_action(Box::new(DeletePrompt));
|
||||
}),
|
||||
)
|
||||
// .child(
|
||||
// IconButton::new(
|
||||
// "duplicate-prompt",
|
||||
// IconName::BookCopy,
|
||||
// )
|
||||
// .size(ButtonSize::Large)
|
||||
// .style(ButtonStyle::Transparent)
|
||||
// .shape(IconButtonShape::Square)
|
||||
// .size(ButtonSize::Large)
|
||||
// .tooltip(move |cx| {
|
||||
// Tooltip::for_action(
|
||||
// "Duplicate Prompt",
|
||||
// &gpui::NoAction,
|
||||
// cx,
|
||||
// )
|
||||
// })
|
||||
// .disabled(true),
|
||||
// )
|
||||
.child(
|
||||
IconButton::new(
|
||||
"duplicate-prompt",
|
||||
IconName::BookCopy,
|
||||
)
|
||||
.size(ButtonSize::Large)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.shape(IconButtonShape::Square)
|
||||
.size(ButtonSize::Large)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(
|
||||
"Duplicate Prompt",
|
||||
&DuplicatePrompt,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
DuplicatePrompt,
|
||||
));
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new(
|
||||
"toggle-default-prompt",
|
||||
@@ -973,6 +1029,7 @@ impl Render for PromptLibrary {
|
||||
.key_context("PromptLibrary")
|
||||
.on_action(cx.listener(|this, &NewPrompt, cx| this.new_prompt(cx)))
|
||||
.on_action(cx.listener(|this, &DeletePrompt, cx| this.delete_active_prompt(cx)))
|
||||
.on_action(cx.listener(|this, &DuplicatePrompt, cx| this.duplicate_active_prompt(cx)))
|
||||
.on_action(cx.listener(|this, &ToggleDefaultPrompt, cx| {
|
||||
this.toggle_default_for_active_prompt(cx)
|
||||
}))
|
||||
|
||||
@@ -6,118 +6,130 @@ pub fn generate_content_prompt(
|
||||
language_name: Option<&str>,
|
||||
buffer: BufferSnapshot,
|
||||
range: Range<usize>,
|
||||
project_name: Option<String>,
|
||||
_project_name: Option<String>,
|
||||
) -> anyhow::Result<String> {
|
||||
let mut prompt = String::new();
|
||||
|
||||
let content_type = match language_name {
|
||||
None | Some("Markdown" | "Plain Text") => {
|
||||
writeln!(prompt, "You are an expert engineer.")?;
|
||||
"Text"
|
||||
}
|
||||
Some(language_name) => {
|
||||
writeln!(prompt, "You are an expert {language_name} engineer.")?;
|
||||
writeln!(
|
||||
prompt,
|
||||
"Your answer MUST always and only be valid {}.",
|
||||
language_name
|
||||
"Here's a file of text that I'm going to ask you to make an edit to."
|
||||
)?;
|
||||
"Code"
|
||||
"text"
|
||||
}
|
||||
Some(language_name) => {
|
||||
writeln!(
|
||||
prompt,
|
||||
"Here's a file of {language_name} that I'm going to ask you to make an edit to."
|
||||
)?;
|
||||
"code"
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(project_name) = project_name {
|
||||
writeln!(
|
||||
prompt,
|
||||
"You are currently working inside the '{project_name}' project in code editor Zed."
|
||||
)?;
|
||||
}
|
||||
|
||||
writeln!(
|
||||
prompt,
|
||||
"The user has the following file open in the editor:"
|
||||
)?;
|
||||
const MAX_CTX: usize = 50000;
|
||||
let mut is_truncated = false;
|
||||
if range.is_empty() {
|
||||
write!(prompt, "```")?;
|
||||
if let Some(language_name) = language_name {
|
||||
write!(prompt, "{language_name}")?;
|
||||
}
|
||||
|
||||
for chunk in buffer.as_rope().chunks_in_range(0..range.start) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
prompt.push_str("<|CURSOR|>");
|
||||
for chunk in buffer.as_rope().chunks_in_range(range.start..buffer.len()) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
if !prompt.ends_with('\n') {
|
||||
prompt.push('\n');
|
||||
}
|
||||
writeln!(prompt, "```")?;
|
||||
prompt.push('\n');
|
||||
|
||||
writeln!(
|
||||
prompt,
|
||||
"Assume the cursor is located where the `<|CURSOR|>` span is."
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
prompt,
|
||||
"{content_type} can't be replaced, so assume your answer will be inserted at the cursor.",
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
prompt,
|
||||
"Generate {content_type} based on the users prompt: {user_prompt}",
|
||||
)
|
||||
.unwrap();
|
||||
prompt.push_str("The point you'll need to insert at is marked with <insert_here></insert_here>.\n\n<document>");
|
||||
} else {
|
||||
write!(prompt, "```")?;
|
||||
for chunk in buffer.as_rope().chunks() {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
if !prompt.ends_with('\n') {
|
||||
prompt.push('\n');
|
||||
}
|
||||
writeln!(prompt, "```")?;
|
||||
prompt.push('\n');
|
||||
|
||||
writeln!(
|
||||
prompt,
|
||||
"In particular, the following piece of text is selected:"
|
||||
)?;
|
||||
write!(prompt, "```")?;
|
||||
if let Some(language_name) = language_name {
|
||||
write!(prompt, "{language_name}")?;
|
||||
}
|
||||
prompt.push('\n');
|
||||
prompt.push_str("The section you'll need to rewrite is marked with <rewrite_this></rewrite_this> tags.\n\n<document>");
|
||||
}
|
||||
// Include file content.
|
||||
let before_range = 0..range.start;
|
||||
let truncated_before = if before_range.len() > MAX_CTX {
|
||||
is_truncated = true;
|
||||
range.start - MAX_CTX..range.start
|
||||
} else {
|
||||
before_range
|
||||
};
|
||||
let mut non_rewrite_len = truncated_before.len();
|
||||
for chunk in buffer.text_for_range(truncated_before) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
if !range.is_empty() {
|
||||
prompt.push_str("<rewrite_this>\n");
|
||||
for chunk in buffer.text_for_range(range.clone()) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
if !prompt.ends_with('\n') {
|
||||
prompt.push('\n');
|
||||
}
|
||||
writeln!(prompt, "```")?;
|
||||
prompt.push('\n');
|
||||
|
||||
writeln!(
|
||||
prompt,
|
||||
"Modify the user's selected {content_type} based upon the users prompt: {user_prompt}"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
prompt,
|
||||
"You must reply with only the adjusted {content_type}, not the entire file."
|
||||
)
|
||||
.unwrap();
|
||||
prompt.push_str("\n<rewrite_this>");
|
||||
} else {
|
||||
prompt.push_str("<insert_here></insert_here>");
|
||||
}
|
||||
let after_range = range.end..buffer.len();
|
||||
let truncated_after = if after_range.len() > MAX_CTX {
|
||||
is_truncated = true;
|
||||
range.end..range.end + MAX_CTX
|
||||
} else {
|
||||
after_range
|
||||
};
|
||||
non_rewrite_len += truncated_after.len();
|
||||
for chunk in buffer.text_for_range(truncated_after) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
|
||||
writeln!(prompt, "Never make remarks about the output.").unwrap();
|
||||
writeln!(
|
||||
prompt,
|
||||
"Do not return anything else, except the generated {content_type}."
|
||||
)
|
||||
.unwrap();
|
||||
write!(prompt, "</document>\n\n").unwrap();
|
||||
|
||||
if is_truncated {
|
||||
writeln!(prompt, "The context around the relevant section has been truncated (possibly in the middle of a line) for brevity.\n")?;
|
||||
}
|
||||
|
||||
if range.is_empty() {
|
||||
writeln!(
|
||||
prompt,
|
||||
"You can't replace {content_type}, your answer will be inserted in place of the `<insert_here></insert_here>` tags. Don't include the insert_here tags in your output.",
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
prompt,
|
||||
"Generate {content_type} based on the following prompt:\n\n<prompt>\n{user_prompt}\n</prompt>",
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(prompt, "Match the indentation in the original file in the inserted {content_type}, don't include any indentation on blank lines.\n").unwrap();
|
||||
prompt.push_str("Immediately start with the following format with no remarks:\n\n```\n{{INSERTED_CODE}}\n```");
|
||||
} else {
|
||||
writeln!(prompt, "Edit the section of {content_type} in <rewrite_this></rewrite_this> tags based on the following prompt:'").unwrap();
|
||||
writeln!(prompt, "\n<prompt>\n{user_prompt}\n</prompt>\n").unwrap();
|
||||
let rewrite_len = range.end - range.start;
|
||||
if rewrite_len < 20000 && rewrite_len * 2 < non_rewrite_len {
|
||||
writeln!(prompt, "And here's the section to rewrite based on that prompt again for reference:\n\n<rewrite_this>\n").unwrap();
|
||||
for chunk in buffer.text_for_range(range.clone()) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
writeln!(prompt, "\n</rewrite_this>\n").unwrap();
|
||||
}
|
||||
writeln!(prompt, "Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {content_type} will be preserved.\n").unwrap();
|
||||
write!(
|
||||
prompt,
|
||||
"Start at the indentation level in the original file in the rewritten {content_type}. "
|
||||
)
|
||||
.unwrap();
|
||||
prompt.push_str("Don't stop until you've rewritten the entire section, even if you have no more changes to make, always write out the whole section with no unnecessary elisions.");
|
||||
prompt.push_str("\n\nImmediately start with the following format with no remarks:\n\n```\n{{REWRITTEN_CODE}}\n```");
|
||||
}
|
||||
|
||||
Ok(prompt)
|
||||
}
|
||||
|
||||
pub fn generate_terminal_assistant_prompt(
|
||||
user_prompt: &str,
|
||||
shell: Option<&str>,
|
||||
working_directory: Option<&str>,
|
||||
) -> String {
|
||||
let mut prompt = String::new();
|
||||
writeln!(&mut prompt, "You are an expert terminal user.").unwrap();
|
||||
writeln!(&mut prompt, "You will be given a description of a command and you need to respond with a command that matches the description.").unwrap();
|
||||
writeln!(&mut prompt, "Do not include markdown blocks or any other text formatting in your response, always respond with a single command that can be executed in the given shell.").unwrap();
|
||||
if let Some(shell) = shell {
|
||||
writeln!(&mut prompt, "Current shell is '{shell}'.").unwrap();
|
||||
}
|
||||
if let Some(working_directory) = working_directory {
|
||||
writeln!(
|
||||
&mut prompt,
|
||||
"Current working directory is '{working_directory}'."
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
writeln!(&mut prompt, "Here is the description of the command:").unwrap();
|
||||
prompt.push_str(user_prompt);
|
||||
prompt
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ use workspace::Workspace;
|
||||
pub mod active_command;
|
||||
pub mod default_command;
|
||||
pub mod diagnostics_command;
|
||||
pub mod docs_command;
|
||||
pub mod fetch_command;
|
||||
pub mod file_command;
|
||||
pub mod now_command;
|
||||
pub mod project_command;
|
||||
pub mod prompt_command;
|
||||
pub mod rustdoc_command;
|
||||
pub mod search_command;
|
||||
pub mod tabs_command;
|
||||
pub mod term_command;
|
||||
|
||||
@@ -20,9 +20,9 @@ use util::paths::PathMatcher;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct DiagnosticsCommand;
|
||||
pub(crate) struct DiagnosticsSlashCommand;
|
||||
|
||||
impl DiagnosticsCommand {
|
||||
impl DiagnosticsSlashCommand {
|
||||
fn search_paths(
|
||||
&self,
|
||||
query: String,
|
||||
@@ -81,7 +81,7 @@ impl DiagnosticsCommand {
|
||||
}
|
||||
}
|
||||
|
||||
impl SlashCommand for DiagnosticsCommand {
|
||||
impl SlashCommand for DiagnosticsSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
"diagnostics".into()
|
||||
}
|
||||
|
||||
365
crates/assistant/src/slash_command/docs_command.rs
Normal file
@@ -0,0 +1,365 @@
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||
use gpui::{AppContext, Model, Task, WeakView};
|
||||
use indexed_docs::{
|
||||
IndexedDocsRegistry, IndexedDocsStore, LocalProvider, PackageName, ProviderId, RustdocIndexer,
|
||||
};
|
||||
use language::LspAdapterDelegate;
|
||||
use project::{Project, ProjectPath};
|
||||
use ui::prelude::*;
|
||||
use util::{maybe, ResultExt};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct DocsSlashCommand;
|
||||
|
||||
impl DocsSlashCommand {
|
||||
pub const NAME: &'static str = "docs";
|
||||
|
||||
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
|
||||
let worktree = project.read(cx).worktrees().next()?;
|
||||
let worktree = worktree.read(cx);
|
||||
let entry = worktree.entry_for_path("Cargo.toml")?;
|
||||
let path = ProjectPath {
|
||||
worktree_id: worktree.id(),
|
||||
path: entry.path.clone(),
|
||||
};
|
||||
Some(Arc::from(
|
||||
project.read(cx).absolute_path(&path, cx)?.as_path(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Ensures that the rustdoc provider is registered.
|
||||
///
|
||||
/// Ideally we would do this sooner, but we need to wait until we're able to
|
||||
/// access the workspace so we can read the project.
|
||||
fn ensure_rustdoc_provider_is_registered(
|
||||
&self,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
|
||||
if indexed_docs_registry
|
||||
.get_provider_store(ProviderId::rustdoc())
|
||||
.is_none()
|
||||
{
|
||||
let index_provider_deps = maybe!({
|
||||
let workspace = workspace.ok_or_else(|| anyhow!("no workspace"))?;
|
||||
let workspace = workspace
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("workspace was dropped"))?;
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let fs = project.read(cx).fs().clone();
|
||||
let cargo_workspace_root = Self::path_to_cargo_toml(project, cx)
|
||||
.and_then(|path| path.parent().map(|path| path.to_path_buf()))
|
||||
.ok_or_else(|| anyhow!("no Cargo workspace root found"))?;
|
||||
|
||||
anyhow::Ok((fs, cargo_workspace_root))
|
||||
});
|
||||
|
||||
if let Some((fs, cargo_workspace_root)) = index_provider_deps.log_err() {
|
||||
indexed_docs_registry.register_provider(Box::new(RustdocIndexer::new(Box::new(
|
||||
LocalProvider::new(fs, cargo_workspace_root),
|
||||
))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SlashCommand for DocsSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
Self::NAME.into()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"insert docs".into()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
"Insert Documentation".into()
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
query: String,
|
||||
_cancel: Arc<AtomicBool>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<Vec<String>>> {
|
||||
self.ensure_rustdoc_provider_is_registered(workspace, cx);
|
||||
|
||||
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
|
||||
let args = DocsSlashCommandArgs::parse(&query);
|
||||
let store = args
|
||||
.provider()
|
||||
.ok_or_else(|| anyhow!("no docs provider specified"))
|
||||
.and_then(|provider| IndexedDocsStore::try_global(provider, cx));
|
||||
cx.background_executor().spawn(async move {
|
||||
/// HACK: Prefixes the completions with the provider ID so that it doesn't get deleted
|
||||
/// when a completion is accepted.
|
||||
///
|
||||
/// We will likely want to extend `complete_argument` with support for replacing just
|
||||
/// a particular range of the argument when a completion is accepted.
|
||||
fn prefix_with_provider(provider: ProviderId, items: Vec<String>) -> Vec<String> {
|
||||
items
|
||||
.into_iter()
|
||||
.map(|item| format!("{provider} {item}"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
match args {
|
||||
DocsSlashCommandArgs::NoProvider => {
|
||||
let providers = indexed_docs_registry.list_providers();
|
||||
Ok(providers
|
||||
.into_iter()
|
||||
.map(|provider| provider.to_string())
|
||||
.collect())
|
||||
}
|
||||
DocsSlashCommandArgs::SearchPackageDocs {
|
||||
provider,
|
||||
package,
|
||||
index,
|
||||
} => {
|
||||
let store = store?;
|
||||
|
||||
if index {
|
||||
// We don't need to hold onto this task, as the `IndexedDocsStore` will hold it
|
||||
// until it completes.
|
||||
let _ = store.clone().index(package.as_str().into());
|
||||
}
|
||||
|
||||
let items = store.search(package).await;
|
||||
Ok(prefix_with_provider(provider, items))
|
||||
}
|
||||
DocsSlashCommandArgs::SearchItemDocs {
|
||||
provider,
|
||||
item_path,
|
||||
..
|
||||
} => {
|
||||
let store = store?;
|
||||
let items = store.search(item_path).await;
|
||||
Ok(prefix_with_provider(provider, items))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
argument: Option<&str>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<SlashCommandOutput>> {
|
||||
let Some(argument) = argument else {
|
||||
return Task::ready(Err(anyhow!("missing argument")));
|
||||
};
|
||||
|
||||
let args = DocsSlashCommandArgs::parse(argument);
|
||||
let text = cx.background_executor().spawn({
|
||||
let store = args
|
||||
.provider()
|
||||
.ok_or_else(|| anyhow!("no docs provider specified"))
|
||||
.and_then(|provider| IndexedDocsStore::try_global(provider, cx));
|
||||
async move {
|
||||
match args {
|
||||
DocsSlashCommandArgs::NoProvider => bail!("no docs provider specified"),
|
||||
DocsSlashCommandArgs::SearchPackageDocs {
|
||||
provider, package, ..
|
||||
} => {
|
||||
let store = store?;
|
||||
let item_docs = store.load(package.clone()).await?;
|
||||
|
||||
anyhow::Ok((provider, package, item_docs.to_string()))
|
||||
}
|
||||
DocsSlashCommandArgs::SearchItemDocs {
|
||||
provider,
|
||||
item_path,
|
||||
..
|
||||
} => {
|
||||
let store = store?;
|
||||
let item_docs = store.load(item_path.clone()).await?;
|
||||
|
||||
anyhow::Ok((provider, item_path, item_docs.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let (provider, path, text) = text.await?;
|
||||
let range = 0..text.len();
|
||||
Ok(SlashCommandOutput {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
icon: IconName::FileRust,
|
||||
label: format!("docs ({provider}): {path}",).into(),
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn is_item_path_delimiter(char: char) -> bool {
|
||||
!char.is_alphanumeric() && char != '-' && char != '_'
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum DocsSlashCommandArgs {
|
||||
NoProvider,
|
||||
SearchPackageDocs {
|
||||
provider: ProviderId,
|
||||
package: String,
|
||||
index: bool,
|
||||
},
|
||||
SearchItemDocs {
|
||||
provider: ProviderId,
|
||||
package: String,
|
||||
item_path: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl DocsSlashCommandArgs {
|
||||
pub fn parse(argument: &str) -> Self {
|
||||
let Some((provider, argument)) = argument.split_once(' ') else {
|
||||
return Self::NoProvider;
|
||||
};
|
||||
|
||||
let provider = ProviderId(provider.into());
|
||||
|
||||
if let Some((package, rest)) = argument.split_once(is_item_path_delimiter) {
|
||||
if rest.trim().is_empty() {
|
||||
Self::SearchPackageDocs {
|
||||
provider,
|
||||
package: package.to_owned(),
|
||||
index: true,
|
||||
}
|
||||
} else {
|
||||
Self::SearchItemDocs {
|
||||
provider,
|
||||
package: package.to_owned(),
|
||||
item_path: argument.to_owned(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Self::SearchPackageDocs {
|
||||
provider,
|
||||
package: argument.to_owned(),
|
||||
index: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn provider(&self) -> Option<ProviderId> {
|
||||
match self {
|
||||
Self::NoProvider => None,
|
||||
Self::SearchPackageDocs { provider, .. } | Self::SearchItemDocs { provider, .. } => {
|
||||
Some(provider.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn package(&self) -> Option<PackageName> {
|
||||
match self {
|
||||
Self::NoProvider => None,
|
||||
Self::SearchPackageDocs { package, .. } | Self::SearchItemDocs { package, .. } => {
|
||||
Some(package.as_str().into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_docs_slash_command_args() {
|
||||
assert_eq!(
|
||||
DocsSlashCommandArgs::parse(""),
|
||||
DocsSlashCommandArgs::NoProvider
|
||||
);
|
||||
assert_eq!(
|
||||
DocsSlashCommandArgs::parse("rustdoc"),
|
||||
DocsSlashCommandArgs::NoProvider
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
DocsSlashCommandArgs::parse("rustdoc "),
|
||||
DocsSlashCommandArgs::SearchPackageDocs {
|
||||
provider: ProviderId("rustdoc".into()),
|
||||
package: "".into(),
|
||||
index: false
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
DocsSlashCommandArgs::parse("gleam "),
|
||||
DocsSlashCommandArgs::SearchPackageDocs {
|
||||
provider: ProviderId("gleam".into()),
|
||||
package: "".into(),
|
||||
index: false
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
DocsSlashCommandArgs::parse("rustdoc gpui"),
|
||||
DocsSlashCommandArgs::SearchPackageDocs {
|
||||
provider: ProviderId("rustdoc".into()),
|
||||
package: "gpui".into(),
|
||||
index: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
DocsSlashCommandArgs::parse("gleam gleam_stdlib"),
|
||||
DocsSlashCommandArgs::SearchPackageDocs {
|
||||
provider: ProviderId("gleam".into()),
|
||||
package: "gleam_stdlib".into(),
|
||||
index: false
|
||||
}
|
||||
);
|
||||
|
||||
// Adding an item path delimiter indicates we can start indexing.
|
||||
assert_eq!(
|
||||
DocsSlashCommandArgs::parse("rustdoc gpui:"),
|
||||
DocsSlashCommandArgs::SearchPackageDocs {
|
||||
provider: ProviderId("rustdoc".into()),
|
||||
package: "gpui".into(),
|
||||
index: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
DocsSlashCommandArgs::parse("gleam gleam_stdlib/"),
|
||||
DocsSlashCommandArgs::SearchPackageDocs {
|
||||
provider: ProviderId("gleam".into()),
|
||||
package: "gleam_stdlib".into(),
|
||||
index: true
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
DocsSlashCommandArgs::parse("rustdoc gpui::foo::bar::Baz"),
|
||||
DocsSlashCommandArgs::SearchItemDocs {
|
||||
provider: ProviderId("rustdoc".into()),
|
||||
package: "gpui".into(),
|
||||
item_path: "gpui::foo::bar::Baz".into()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
DocsSlashCommandArgs::parse("gleam gleam_stdlib/gleam/int"),
|
||||
DocsSlashCommandArgs::SearchItemDocs {
|
||||
provider: ProviderId("gleam".into()),
|
||||
package: "gleam_stdlib".into(),
|
||||
item_path: "gleam_stdlib/gleam/int".into()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||
use chrono::{DateTime, Local};
|
||||
use chrono::Local;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use language::LspAdapterDelegate;
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct NowSlashCommand;
|
||||
@@ -46,7 +46,7 @@ impl SlashCommand for NowSlashCommand {
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<SlashCommandOutput>> {
|
||||
let now = Local::now();
|
||||
let text = format!("Today is {now}.", now = now.to_rfc3339());
|
||||
let text = format!("Today is {now}.", now = now.to_rfc2822());
|
||||
let range = 0..text.len();
|
||||
|
||||
Task::ready(Ok(SlashCommandOutput {
|
||||
@@ -54,29 +54,9 @@ impl SlashCommand for NowSlashCommand {
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
icon: IconName::CountdownTimer,
|
||||
label: now.to_rfc3339().into(),
|
||||
label: now.to_rfc2822().into(),
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
struct NowPlaceholder {
|
||||
pub id: ElementId,
|
||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
||||
pub now: DateTime<Local>,
|
||||
}
|
||||
|
||||
impl RenderOnce for NowPlaceholder {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
let unfold = self.unfold;
|
||||
|
||||
ButtonLike::new(self.id)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ElevatedSurface)
|
||||
.child(Icon::new(IconName::CountdownTimer))
|
||||
.child(Label::new(self.now.to_rfc3339()))
|
||||
.on_click(move |_, cx| unfold(cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||
use fs::Fs;
|
||||
use futures::AsyncReadExt;
|
||||
use gpui::{AppContext, Model, Task, WeakView};
|
||||
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use language::LspAdapterDelegate;
|
||||
use project::{Project, ProjectPath};
|
||||
use rustdoc::{convert_rustdoc_to_markdown, CrateName, LocalProvider, RustdocSource, RustdocStore};
|
||||
use ui::prelude::*;
|
||||
use util::{maybe, ResultExt};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct RustdocSlashCommand;
|
||||
|
||||
impl RustdocSlashCommand {
|
||||
async fn build_message(
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
crate_name: CrateName,
|
||||
module_path: Vec<String>,
|
||||
path_to_cargo_toml: Option<&Path>,
|
||||
) -> Result<(RustdocSource, String)> {
|
||||
let cargo_workspace_root = path_to_cargo_toml.and_then(|path| path.parent());
|
||||
if let Some(cargo_workspace_root) = cargo_workspace_root {
|
||||
let mut local_cargo_doc_path = cargo_workspace_root.join("target/doc");
|
||||
local_cargo_doc_path.push(crate_name.as_ref());
|
||||
if !module_path.is_empty() {
|
||||
local_cargo_doc_path.push(module_path.join("/"));
|
||||
}
|
||||
local_cargo_doc_path.push("index.html");
|
||||
|
||||
if let Ok(contents) = fs.load(&local_cargo_doc_path).await {
|
||||
let (markdown, _items) = convert_rustdoc_to_markdown(contents.as_bytes())?;
|
||||
|
||||
return Ok((RustdocSource::Local, markdown));
|
||||
}
|
||||
}
|
||||
|
||||
let version = "latest";
|
||||
let path = format!(
|
||||
"{crate_name}/{version}/{crate_name}/{module_path}",
|
||||
module_path = module_path.join("/")
|
||||
);
|
||||
|
||||
let mut response = http_client
|
||||
.get(
|
||||
&format!("https://docs.rs/{path}"),
|
||||
AsyncBody::default(),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut body)
|
||||
.await
|
||||
.context("error reading docs.rs response body")?;
|
||||
|
||||
if response.status().is_client_error() {
|
||||
let text = String::from_utf8_lossy(body.as_slice());
|
||||
bail!(
|
||||
"status error {}, response: {text:?}",
|
||||
response.status().as_u16()
|
||||
);
|
||||
}
|
||||
|
||||
let (markdown, _items) = convert_rustdoc_to_markdown(&body[..])?;
|
||||
|
||||
Ok((RustdocSource::DocsDotRs, markdown))
|
||||
}
|
||||
|
||||
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
|
||||
let worktree = project.read(cx).worktrees().next()?;
|
||||
let worktree = worktree.read(cx);
|
||||
let entry = worktree.entry_for_path("Cargo.toml")?;
|
||||
let path = ProjectPath {
|
||||
worktree_id: worktree.id(),
|
||||
path: entry.path.clone(),
|
||||
};
|
||||
Some(Arc::from(
|
||||
project.read(cx).absolute_path(&path, cx)?.as_path(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl SlashCommand for RustdocSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
"rustdoc".into()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"insert Rust docs".into()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
"Insert Rust Documentation".into()
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
query: String,
|
||||
_cancel: Arc<AtomicBool>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<Vec<String>>> {
|
||||
let index_provider_deps = maybe!({
|
||||
let workspace = workspace.ok_or_else(|| anyhow!("no workspace"))?;
|
||||
let workspace = workspace
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("workspace was dropped"))?;
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let fs = project.read(cx).fs().clone();
|
||||
let cargo_workspace_root = Self::path_to_cargo_toml(project, cx)
|
||||
.and_then(|path| path.parent().map(|path| path.to_path_buf()))
|
||||
.ok_or_else(|| anyhow!("no Cargo workspace root found"))?;
|
||||
|
||||
anyhow::Ok((fs, cargo_workspace_root))
|
||||
});
|
||||
|
||||
let store = RustdocStore::global(cx);
|
||||
cx.background_executor().spawn(async move {
|
||||
if let Some((crate_name, rest)) = query.split_once(':') {
|
||||
if rest.is_empty() {
|
||||
if let Some((fs, cargo_workspace_root)) = index_provider_deps.log_err() {
|
||||
let provider = Box::new(LocalProvider::new(fs, cargo_workspace_root));
|
||||
// We don't need to hold onto this task, as the `RustdocStore` will hold it
|
||||
// until it completes.
|
||||
let _ = store.clone().index(crate_name.into(), provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let items = store.search(query).await;
|
||||
Ok(items)
|
||||
})
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
argument: Option<&str>,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<SlashCommandOutput>> {
|
||||
let Some(argument) = argument else {
|
||||
return Task::ready(Err(anyhow!("missing crate name")));
|
||||
};
|
||||
let Some(workspace) = workspace.upgrade() else {
|
||||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
||||
};
|
||||
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let fs = project.read(cx).fs().clone();
|
||||
let http_client = workspace.read(cx).client().http_client();
|
||||
let path_to_cargo_toml = Self::path_to_cargo_toml(project, cx);
|
||||
|
||||
let mut path_components = argument.split("::");
|
||||
let crate_name = match path_components
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("missing crate name"))
|
||||
{
|
||||
Ok(crate_name) => CrateName::from(crate_name),
|
||||
Err(err) => return Task::ready(Err(err)),
|
||||
};
|
||||
let item_path = path_components.map(ToString::to_string).collect::<Vec<_>>();
|
||||
|
||||
let text = cx.background_executor().spawn({
|
||||
let rustdoc_store = RustdocStore::global(cx);
|
||||
let crate_name = crate_name.clone();
|
||||
let item_path = item_path.clone();
|
||||
async move {
|
||||
let item_docs = rustdoc_store
|
||||
.load(
|
||||
crate_name.clone(),
|
||||
if item_path.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(item_path.join("::"))
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Ok(item_docs) = item_docs {
|
||||
anyhow::Ok((RustdocSource::Index, item_docs.docs().to_owned()))
|
||||
} else {
|
||||
Self::build_message(
|
||||
fs,
|
||||
http_client,
|
||||
crate_name,
|
||||
item_path,
|
||||
path_to_cargo_toml.as_deref(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let module_path = if item_path.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(SharedString::from(item_path.join("::")))
|
||||
};
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let (source, text) = text.await?;
|
||||
let range = 0..text.len();
|
||||
let crate_path = module_path
|
||||
.map(|module_path| format!("{}::{}", crate_name, module_path))
|
||||
.unwrap_or_else(|| crate_name.to_string());
|
||||
Ok(SlashCommandOutput {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
icon: IconName::FileRust,
|
||||
label: format!(
|
||||
"rustdoc ({source}): {crate_path}",
|
||||
source = match source {
|
||||
RustdocSource::Index => "index",
|
||||
RustdocSource::Local => "local",
|
||||
RustdocSource::DocsDotRs => "docs.rs",
|
||||
}
|
||||
)
|
||||
.into(),
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
1122
crates/assistant/src/terminal_inline_assistant.rs
Normal file
@@ -196,23 +196,24 @@ mod linux {
|
||||
impl Detect {
|
||||
pub fn detect(path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
|
||||
let path = if let Some(path) = path {
|
||||
path.to_path_buf().canonicalize()
|
||||
path.to_path_buf().canonicalize()?
|
||||
} else {
|
||||
let cli = env::current_exe()?;
|
||||
let dir = cli
|
||||
.parent()
|
||||
.and_then(Path::parent)
|
||||
.ok_or_else(|| anyhow!("no parent path for cli"))?;
|
||||
|
||||
match dir.join("libexec").join("zed-editor").canonicalize() {
|
||||
Ok(path) => Ok(path),
|
||||
// In development cli and zed are in the ./target/ directory together
|
||||
Err(e) => match cli.parent().unwrap().join("zed").canonicalize() {
|
||||
Ok(path) if path != cli => Ok(path),
|
||||
_ => Err(e),
|
||||
},
|
||||
}
|
||||
}?;
|
||||
// libexec is the standard, lib/zed is for Arch (and other non-libexec distros),
|
||||
// ./zed is for the target directory in development builds.
|
||||
let possible_locations =
|
||||
["../libexec/zed-editor", "../lib/zed/zed-editor", "./zed"];
|
||||
possible_locations
|
||||
.iter()
|
||||
.find_map(|p| dir.join(p).canonicalize().ok().filter(|path| path != &cli))
|
||||
.ok_or_else(|| {
|
||||
anyhow!("could not find any of: {}", possible_locations.join(", "))
|
||||
})?
|
||||
};
|
||||
|
||||
Ok(App(path))
|
||||
}
|
||||
|
||||
@@ -202,6 +202,10 @@ impl Telemetry {
|
||||
event_coalescer: EventCoalescer::new(clock.clone()),
|
||||
max_queue_size: MAX_QUEUE_LEN,
|
||||
worktree_id_map: WorktreeIdMap(HashMap::from_iter([
|
||||
(
|
||||
"pnpm-lock.yaml".to_string(),
|
||||
ProjectCache::new("pnpm".to_string()),
|
||||
),
|
||||
(
|
||||
"yarn.lock".to_string(),
|
||||
ProjectCache::new("yarn".to_string()),
|
||||
|
||||
@@ -87,51 +87,27 @@ impl Global {
|
||||
}
|
||||
|
||||
pub fn observed_any(&self, other: &Self) -> bool {
|
||||
let mut lhs = self.0.iter();
|
||||
let mut rhs = other.0.iter();
|
||||
loop {
|
||||
if let Some(left) = lhs.next() {
|
||||
if let Some(right) = rhs.next() {
|
||||
if *right > 0 && left >= right {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
self.0
|
||||
.iter()
|
||||
.zip(other.0.iter())
|
||||
.any(|(left, right)| *right > 0 && left >= right)
|
||||
}
|
||||
|
||||
pub fn observed_all(&self, other: &Self) -> bool {
|
||||
let mut lhs = self.0.iter();
|
||||
let mut rhs = other.0.iter();
|
||||
loop {
|
||||
if let Some(left) = lhs.next() {
|
||||
if let Some(right) = rhs.next() {
|
||||
if left < right {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return rhs.next().is_none();
|
||||
}
|
||||
}
|
||||
self.0.iter().all(|left| match rhs.next() {
|
||||
Some(right) => left >= right,
|
||||
None => true,
|
||||
}) && rhs.next().is_none()
|
||||
}
|
||||
|
||||
pub fn changed_since(&self, other: &Self) -> bool {
|
||||
if self.0.len() > other.0.len() {
|
||||
return true;
|
||||
}
|
||||
for (left, right) in self.0.iter().zip(other.0.iter()) {
|
||||
if left > right {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
self.0.len() > other.0.len()
|
||||
|| self
|
||||
.0
|
||||
.iter()
|
||||
.zip(other.0.iter())
|
||||
.any(|(left, right)| left > right)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = Lamport> + '_ {
|
||||
|
||||
@@ -36,8 +36,6 @@ pub fn router() -> Router {
|
||||
struct GetExtensionsParams {
|
||||
filter: Option<String>,
|
||||
#[serde(default)]
|
||||
ids: Option<String>,
|
||||
#[serde(default)]
|
||||
max_schema_version: i32,
|
||||
}
|
||||
|
||||
@@ -45,18 +43,15 @@ async fn get_extensions(
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
Query(params): Query<GetExtensionsParams>,
|
||||
) -> Result<Json<GetExtensionsResponse>> {
|
||||
let extension_ids = params
|
||||
.ids
|
||||
.as_ref()
|
||||
.map(|s| s.split(',').map(|s| s.trim()).collect::<Vec<_>>());
|
||||
let extensions = app
|
||||
.db
|
||||
.get_extensions(params.filter.as_deref(), params.max_schema_version, 500)
|
||||
.await?;
|
||||
|
||||
let extensions = if let Some(extension_ids) = extension_ids {
|
||||
app.db.get_extensions_by_ids(&extension_ids, None).await?
|
||||
} else {
|
||||
app.db
|
||||
.get_extensions(params.filter.as_deref(), params.max_schema_version, 500)
|
||||
.await?
|
||||
};
|
||||
if let Some(query) = params.filter.as_deref() {
|
||||
let count = extensions.len();
|
||||
tracing::info!(query, count, "extension_search")
|
||||
}
|
||||
|
||||
Ok(Json(GetExtensionsResponse { data: extensions }))
|
||||
}
|
||||
|
||||
@@ -4462,6 +4462,7 @@ async fn complete_with_open_ai(
|
||||
tool_calls: choice
|
||||
.delta
|
||||
.tool_calls
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|delta| proto::ToolCallDelta {
|
||||
index: delta.index as u32,
|
||||
|
||||
@@ -30,17 +30,13 @@ test-support = [
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
auto_update.workspace = true
|
||||
call.workspace = true
|
||||
channel.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
emojis.workspace = true
|
||||
extensions_ui.workspace = true
|
||||
feedback.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
@@ -51,8 +47,6 @@ notifications.workspace = true
|
||||
parking_lot.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
recent_projects.workspace = true
|
||||
dev_server_projects.workspace = true
|
||||
release_channel.workspace = true
|
||||
rich_text.workspace = true
|
||||
rpc.workspace = true
|
||||
@@ -64,14 +58,13 @@ settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
story = { workspace = true, optional = true }
|
||||
theme.workspace = true
|
||||
theme_selector.workspace = true
|
||||
time_format.workspace = true
|
||||
time.workspace = true
|
||||
title_bar.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
vcs_menu.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -2,10 +2,7 @@ mod channel_modal;
|
||||
mod contact_finder;
|
||||
|
||||
use self::channel_modal::ChannelModal;
|
||||
use crate::{
|
||||
channel_view::ChannelView, chat_panel::ChatPanel, face_pile::FacePile,
|
||||
CollaborationPanelSettings,
|
||||
};
|
||||
use crate::{channel_view::ChannelView, chat_panel::ChatPanel, CollaborationPanelSettings};
|
||||
use call::ActiveCall;
|
||||
use channel::{Channel, ChannelEvent, ChannelStore};
|
||||
use client::{ChannelId, Client, Contact, ProjectId, User, UserStore};
|
||||
@@ -34,7 +31,8 @@ use std::{mem, sync::Arc};
|
||||
use theme::{ActiveTheme, ThemeSettings};
|
||||
use ui::{
|
||||
prelude::*, tooltip_container, Avatar, AvatarAvailabilityIndicator, Button, Color, ContextMenu,
|
||||
Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem, Tooltip,
|
||||
Facepile, Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem,
|
||||
Tooltip,
|
||||
};
|
||||
use util::{maybe, ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
@@ -2542,7 +2540,7 @@ impl CollabPanel {
|
||||
None
|
||||
} else {
|
||||
let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
|
||||
let result = FacePile::new(
|
||||
let result = Facepile::new(
|
||||
participants
|
||||
.iter()
|
||||
.map(|user| Avatar::new(user.avatar_uri.clone()).into_any_element())
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
pub mod channel_view;
|
||||
pub mod chat_panel;
|
||||
pub mod collab_panel;
|
||||
mod collab_titlebar_item;
|
||||
mod face_pile;
|
||||
pub mod notification_panel;
|
||||
pub mod notifications;
|
||||
mod panel_settings;
|
||||
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use call::{report_call_event_for_room, ActiveCall};
|
||||
pub use collab_panel::CollabPanel;
|
||||
pub use collab_titlebar_item::CollabTitlebarItem;
|
||||
use gpui::{
|
||||
actions, point, AppContext, Pixels, PlatformDisplay, Size, Task, WindowBackgroundAppearance,
|
||||
WindowBounds, WindowContext, WindowKind, WindowOptions,
|
||||
point, AppContext, Pixels, PlatformDisplay, Size, WindowBackgroundAppearance, WindowBounds,
|
||||
WindowDecorations, WindowKind, WindowOptions,
|
||||
};
|
||||
use panel_settings::MessageEditorSettings;
|
||||
pub use panel_settings::{
|
||||
@@ -23,12 +19,7 @@ pub use panel_settings::{
|
||||
use release_channel::ReleaseChannel;
|
||||
use settings::Settings;
|
||||
use ui::px;
|
||||
use workspace::{notifications::DetachAndPromptErr, AppState};
|
||||
|
||||
actions!(
|
||||
collab,
|
||||
[ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
|
||||
);
|
||||
use workspace::AppState;
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
CollaborationPanelSettings::register(cx);
|
||||
@@ -36,63 +27,13 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
NotificationPanelSettings::register(cx);
|
||||
MessageEditorSettings::register(cx);
|
||||
|
||||
vcs_menu::init(cx);
|
||||
collab_titlebar_item::init(cx);
|
||||
collab_panel::init(cx);
|
||||
channel_view::init(cx);
|
||||
chat_panel::init(cx);
|
||||
collab_panel::init(cx);
|
||||
notification_panel::init(cx);
|
||||
notifications::init(&app_state, cx);
|
||||
}
|
||||
|
||||
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut WindowContext) {
|
||||
let call = ActiveCall::global(cx).read(cx);
|
||||
if let Some(room) = call.room().cloned() {
|
||||
let client = call.client();
|
||||
let toggle_screen_sharing = room.update(cx, |room, cx| {
|
||||
if room.is_screen_sharing() {
|
||||
report_call_event_for_room(
|
||||
"disable screen share",
|
||||
room.id(),
|
||||
room.channel_id(),
|
||||
&client,
|
||||
);
|
||||
Task::ready(room.unshare_screen(cx))
|
||||
} else {
|
||||
report_call_event_for_room(
|
||||
"enable screen share",
|
||||
room.id(),
|
||||
room.channel_id(),
|
||||
&client,
|
||||
);
|
||||
room.share_screen(cx)
|
||||
}
|
||||
});
|
||||
toggle_screen_sharing.detach_and_prompt_err("Sharing Screen Failed", cx, |e, _| Some(format!("{:?}\n\nPlease check that you have given Zed permissions to record your screen in Settings.", e)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
|
||||
let call = ActiveCall::global(cx).read(cx);
|
||||
if let Some(room) = call.room().cloned() {
|
||||
let client = call.client();
|
||||
room.update(cx, |room, cx| {
|
||||
let operation = if room.is_muted() {
|
||||
"enable microphone"
|
||||
} else {
|
||||
"disable microphone"
|
||||
};
|
||||
report_call_event_for_room(operation, room.id(), room.channel_id(), &client);
|
||||
|
||||
room.toggle_mute(cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
|
||||
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||
room.update(cx, |room, cx| room.toggle_deafen(cx));
|
||||
}
|
||||
title_bar::init(cx);
|
||||
vcs_menu::init(cx);
|
||||
}
|
||||
|
||||
fn notification_window_options(
|
||||
@@ -122,8 +63,9 @@ fn notification_window_options(
|
||||
kind: WindowKind::PopUp,
|
||||
is_movable: false,
|
||||
display_id: Some(screen.id()),
|
||||
window_background: WindowBackgroundAppearance::default(),
|
||||
window_background: WindowBackgroundAppearance::Transparent,
|
||||
app_id: Some(app_id.to_owned()),
|
||||
window_min_size: None,
|
||||
window_decorations: Some(WindowDecorations::Client),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,9 @@ impl Render for ProjectDiagnosticsEditor {
|
||||
|
||||
div()
|
||||
.track_focus(&self.focus_handle)
|
||||
.when(self.path_states.is_empty(), |el| {
|
||||
el.key_context("EmptyPane")
|
||||
})
|
||||
.size_full()
|
||||
.on_action(cx.listener(Self::toggle_warnings))
|
||||
.child(child)
|
||||
|
||||
@@ -720,8 +720,7 @@ impl DisplaySnapshot {
|
||||
if let Some(severity) = chunk.diagnostic_severity {
|
||||
// Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
|
||||
if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
|
||||
let diagnostic_color =
|
||||
super::diagnostic_style(severity, true, &editor_style.status);
|
||||
let diagnostic_color = super::diagnostic_style(severity, &editor_style.status);
|
||||
diagnostic_highlight.underline = Some(UnderlineStyle {
|
||||
color: Some(diagnostic_color),
|
||||
thickness: 1.0.into(),
|
||||
@@ -957,16 +956,18 @@ impl DisplaySnapshot {
|
||||
return false;
|
||||
}
|
||||
|
||||
for next_row in (buffer_row.0 + 1)..=max_row.0 {
|
||||
let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
|
||||
if next_line_indent.raw_len() > line_indent.raw_len() {
|
||||
return true;
|
||||
} else if !next_line_indent.is_line_blank() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
(buffer_row.0 + 1..=max_row.0)
|
||||
.find_map(|next_row| {
|
||||
let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
|
||||
if next_line_indent.raw_len() > line_indent.raw_len() {
|
||||
Some(true)
|
||||
} else if !next_line_indent.is_line_blank() {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn foldable_range(
|
||||
|
||||
@@ -1221,7 +1221,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::display_map::inlay_map::InlayMap;
|
||||
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
|
||||
use gpui::{div, font, px, AssetSource, Element};
|
||||
use gpui::{div, font, px, Element};
|
||||
use multi_buffer::MultiBuffer;
|
||||
use rand::prelude::*;
|
||||
use settings::SettingsStore;
|
||||
@@ -2014,12 +2014,7 @@ mod tests {
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
cx.text_system()
|
||||
.add_fonts(vec![assets::Assets
|
||||
.load("fonts/zed-mono/zed-mono-extended.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
assets::Assets.load_test_fonts(cx);
|
||||
}
|
||||
|
||||
impl TransformBlock {
|
||||
|
||||
@@ -2131,7 +2131,7 @@ impl Editor {
|
||||
self.refresh_inline_completion(false, cx);
|
||||
}
|
||||
|
||||
pub fn placeholder_text(&self, _cx: &mut WindowContext) -> Option<&str> {
|
||||
pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
|
||||
self.placeholder_text.as_deref()
|
||||
}
|
||||
|
||||
@@ -2914,6 +2914,9 @@ impl Editor {
|
||||
let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
|
||||
let end_offset = start_offset + end_difference;
|
||||
let start_offset = start_offset + start_difference;
|
||||
if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
|
||||
continue;
|
||||
}
|
||||
let start = buffer_snapshot.anchor_after(start_offset);
|
||||
let end = buffer_snapshot.anchor_after(end_offset);
|
||||
linked_edits
|
||||
@@ -3120,14 +3123,24 @@ impl Editor {
|
||||
let anchor = snapshot.anchor_after(selection.end);
|
||||
if !self.linked_edit_ranges.is_empty() {
|
||||
let start_anchor = snapshot.anchor_before(selection.start);
|
||||
if let Some(ranges) =
|
||||
self.linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
|
||||
{
|
||||
for (buffer, edits) in ranges {
|
||||
linked_edits
|
||||
.entry(buffer.clone())
|
||||
.or_default()
|
||||
.extend(edits.into_iter().map(|range| (range, text.clone())));
|
||||
|
||||
let is_word_char = text.chars().next().map_or(true, |char| {
|
||||
let scope = snapshot.language_scope_at(start_anchor.to_offset(&snapshot));
|
||||
let kind = char_kind(&scope, char);
|
||||
|
||||
kind == CharKind::Word
|
||||
});
|
||||
|
||||
if is_word_char {
|
||||
if let Some(ranges) = self
|
||||
.linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
|
||||
{
|
||||
for (buffer, edits) in ranges {
|
||||
linked_edits
|
||||
.entry(buffer.clone())
|
||||
.or_default()
|
||||
.extend(edits.into_iter().map(|range| (range, text.clone())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6809,6 +6822,16 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
if self
|
||||
.context_menu
|
||||
.write()
|
||||
.as_mut()
|
||||
.map(|menu| menu.select_first(self.project.as_ref(), cx))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
cx.propagate();
|
||||
return;
|
||||
@@ -8456,13 +8479,14 @@ impl Editor {
|
||||
runnable: &mut Runnable,
|
||||
cx: &WindowContext<'_>,
|
||||
) -> Vec<(TaskSourceKind, TaskTemplate)> {
|
||||
let (inventory, worktree_id) = project.read_with(cx, |project, cx| {
|
||||
let worktree_id = project
|
||||
let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
|
||||
let (worktree_id, file) = project
|
||||
.buffer_for_id(runnable.buffer)
|
||||
.and_then(|buffer| buffer.read(cx).file())
|
||||
.map(|file| WorktreeId::from_usize(file.worktree_id()));
|
||||
.map(|file| (WorktreeId::from_usize(file.worktree_id()), file.clone()))
|
||||
.unzip();
|
||||
|
||||
(project.task_inventory().clone(), worktree_id)
|
||||
(project.task_inventory().clone(), worktree_id, file)
|
||||
});
|
||||
|
||||
let inventory = inventory.read(cx);
|
||||
@@ -8472,7 +8496,12 @@ impl Editor {
|
||||
.flat_map(|tag| {
|
||||
let tag = tag.0.clone();
|
||||
inventory
|
||||
.list_tasks(Some(runnable.language.clone()), worktree_id)
|
||||
.list_tasks(
|
||||
file.clone(),
|
||||
Some(runnable.language.clone()),
|
||||
worktree_id,
|
||||
cx,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(move |(_, template)| {
|
||||
template.tags.iter().any(|source_tag| source_tag == &tag)
|
||||
@@ -8813,13 +8842,7 @@ impl Editor {
|
||||
let display_point = initial_point.to_display_point(snapshot);
|
||||
let mut hunks = hunks
|
||||
.map(|hunk| diff_hunk_to_display(&hunk, &snapshot))
|
||||
.filter(|hunk| {
|
||||
if is_wrapped {
|
||||
true
|
||||
} else {
|
||||
!hunk.contains_display_row(display_point.row())
|
||||
}
|
||||
})
|
||||
.filter(|hunk| is_wrapped || !hunk.contains_display_row(display_point.row()))
|
||||
.dedup();
|
||||
|
||||
if let Some(hunk) = hunks.next() {
|
||||
@@ -12518,7 +12541,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
|
||||
let group_id: SharedString = cx.block_id.to_string().into();
|
||||
|
||||
let mut text_style = cx.text_style().clone();
|
||||
text_style.color = diagnostic_style(diagnostic.severity, true, cx.theme().status());
|
||||
text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
text_style.font_family = theme_settings.buffer_font.family.clone();
|
||||
text_style.font_style = theme_settings.buffer_font.style;
|
||||
@@ -12614,25 +12637,19 @@ pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, V
|
||||
prev_offset = ix + 1;
|
||||
if in_code_block {
|
||||
code_ranges.push(prev_len..text_without_backticks.len());
|
||||
in_code_block = false;
|
||||
} else {
|
||||
in_code_block = true;
|
||||
}
|
||||
in_code_block = !in_code_block;
|
||||
}
|
||||
|
||||
(text_without_backticks.into(), code_ranges)
|
||||
}
|
||||
|
||||
fn diagnostic_style(severity: DiagnosticSeverity, valid: bool, colors: &StatusColors) -> Hsla {
|
||||
match (severity, valid) {
|
||||
(DiagnosticSeverity::ERROR, true) => colors.error,
|
||||
(DiagnosticSeverity::ERROR, false) => colors.error,
|
||||
(DiagnosticSeverity::WARNING, true) => colors.warning,
|
||||
(DiagnosticSeverity::WARNING, false) => colors.warning,
|
||||
(DiagnosticSeverity::INFORMATION, true) => colors.info,
|
||||
(DiagnosticSeverity::INFORMATION, false) => colors.info,
|
||||
(DiagnosticSeverity::HINT, true) => colors.info,
|
||||
(DiagnosticSeverity::HINT, false) => colors.info,
|
||||
fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
|
||||
match severity {
|
||||
DiagnosticSeverity::ERROR => colors.error,
|
||||
DiagnosticSeverity::WARNING => colors.warning,
|
||||
DiagnosticSeverity::INFORMATION => colors.info,
|
||||
DiagnosticSeverity::HINT => colors.info,
|
||||
_ => colors.ignored,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ use crate::{
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
div, AssetSource, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
|
||||
WindowBounds, WindowOptions,
|
||||
div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
|
||||
WindowOptions,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
@@ -7019,6 +7019,73 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
|
||||
apply_additional_edits.await.unwrap();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![".".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
|
||||
Ok(Some(lsp::CompletionResponse::Array(vec![
|
||||
lsp::CompletionItem {
|
||||
label: "first".into(),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "last".into(),
|
||||
..Default::default()
|
||||
},
|
||||
])))
|
||||
});
|
||||
cx.set_state("variableˇ");
|
||||
cx.simulate_keystroke(".");
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, _| {
|
||||
if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
|
||||
assert_eq!(
|
||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||
&["first", "last"]
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.move_page_down(&MovePageDown::default(), cx);
|
||||
if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
|
||||
assert!(
|
||||
menu.selected_item == 1,
|
||||
"expected PageDown to select the last item from the context menu"
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to stay open after PageDown");
|
||||
}
|
||||
});
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.move_page_up(&MovePageUp::default(), cx);
|
||||
if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
|
||||
assert!(
|
||||
menu.selected_item == 0,
|
||||
"expected PageUp to select the first item from the context menu"
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to stay open after PageUp");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -12489,12 +12556,7 @@ pub(crate) fn update_test_project_settings(
|
||||
|
||||
pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
|
||||
_ = cx.update(|cx| {
|
||||
cx.text_system()
|
||||
.add_fonts(vec![assets::Assets
|
||||
.load("fonts/zed-mono/zed-mono-extended.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
assets::Assets.load_test_fonts(cx);
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
|
||||
@@ -1119,11 +1119,12 @@ impl EditorElement {
|
||||
ScrollBeyondLastLine::Off => 1.0,
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => 1.0 + settings.vertical_scroll_margin,
|
||||
};
|
||||
let total_rows = snapshot.max_point().row().as_f32() + scroll_beyond_last_line;
|
||||
let total_rows =
|
||||
(snapshot.max_point().row().as_f32() + scroll_beyond_last_line).max(rows_per_page);
|
||||
let height = bounds.size.height;
|
||||
let px_per_row = height / total_rows;
|
||||
let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT);
|
||||
let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.0);
|
||||
let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.);
|
||||
|
||||
Some(ScrollbarLayout {
|
||||
hitbox: cx.insert_hitbox(track_bounds, false),
|
||||
@@ -4676,17 +4677,17 @@ impl Element for EditorElement {
|
||||
text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
|
||||
|
||||
let height_in_lines = bounds.size.height / line_height;
|
||||
let max_row = snapshot.max_point().row().as_f32();
|
||||
let max_scroll_top = if matches!(snapshot.mode, EditorMode::AutoHeight { .. }) {
|
||||
(snapshot.max_point().row().as_f32() - height_in_lines + 1.).max(0.)
|
||||
(max_row - height_in_lines + 1.).max(0.)
|
||||
} else {
|
||||
let settings = EditorSettings::get_global(cx);
|
||||
let max_row = snapshot.max_point().row().as_f32();
|
||||
match settings.scroll_beyond_last_line {
|
||||
ScrollBeyondLastLine::OnePage => max_row,
|
||||
ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.0).max(0.0),
|
||||
ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.).max(0.),
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => {
|
||||
(max_row - height_in_lines + 1.0 + settings.vertical_scroll_margin)
|
||||
.max(0.0)
|
||||
(max_row - height_in_lines + 1. + settings.vertical_scroll_margin)
|
||||
.max(0.)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1102,6 +1102,35 @@ impl SearchableItem for Editor {
|
||||
});
|
||||
}
|
||||
}
|
||||
fn replace_all(
|
||||
&mut self,
|
||||
matches: &mut dyn Iterator<Item = &Self::Match>,
|
||||
query: &SearchQuery,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let text = self.buffer.read(cx);
|
||||
let text = text.snapshot(cx);
|
||||
let mut edits = vec![];
|
||||
for m in matches {
|
||||
let text = text.text_for_range(m.clone()).collect::<Vec<_>>();
|
||||
let text: Cow<_> = if text.len() == 1 {
|
||||
text.first().cloned().unwrap().into()
|
||||
} else {
|
||||
let joined_chunks = text.join("");
|
||||
joined_chunks.into()
|
||||
};
|
||||
|
||||
if let Some(replacement) = query.replacement_for(&text) {
|
||||
edits.push((m.clone(), Arc::from(&*replacement)));
|
||||
}
|
||||
}
|
||||
|
||||
if !edits.is_empty() {
|
||||
self.transact(cx, |this, cx| {
|
||||
this.edit(edits, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
fn match_index_for_direction(
|
||||
&mut self,
|
||||
matches: &[Range<Anchor>],
|
||||
|
||||
@@ -203,13 +203,24 @@ impl ScrollManager {
|
||||
let scroll_top = scroll_position.y;
|
||||
let scroll_top = match EditorSettings::get_global(cx).scroll_beyond_last_line {
|
||||
ScrollBeyondLastLine::OnePage => scroll_top,
|
||||
ScrollBeyondLastLine::Off => scroll_top
|
||||
.min((map.max_buffer_row().as_f32()) - self.visible_line_count.unwrap() + 1.0),
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => scroll_top.min(
|
||||
(map.max_buffer_row().as_f32()) - self.visible_line_count.unwrap()
|
||||
+ 1.0
|
||||
+ self.vertical_scroll_margin,
|
||||
),
|
||||
ScrollBeyondLastLine::Off => {
|
||||
if let Some(height_in_lines) = self.visible_line_count {
|
||||
let max_row = map.max_point().row().0 as f32;
|
||||
scroll_top.min(max_row - height_in_lines + 1.).max(0.)
|
||||
} else {
|
||||
scroll_top
|
||||
}
|
||||
}
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => {
|
||||
if let Some(height_in_lines) = self.visible_line_count {
|
||||
let max_row = map.max_point().row().0 as f32;
|
||||
scroll_top
|
||||
.min(max_row - height_in_lines + 1. + self.vertical_scroll_margin)
|
||||
.max(0.)
|
||||
} else {
|
||||
scroll_top
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let scroll_top_buffer_point =
|
||||
|
||||
@@ -25,7 +25,7 @@ pub fn marked_display_snapshot(
|
||||
let (unmarked_text, markers) = marked_text_offsets(text);
|
||||
|
||||
let font = Font {
|
||||
family: "Zed Mono".into(),
|
||||
family: "Zed Plex Mono".into(),
|
||||
features: FontFeatures::default(),
|
||||
weight: FontWeight::default(),
|
||||
style: FontStyle::default(),
|
||||
|
||||
@@ -10,7 +10,7 @@ use serde_json::json;
|
||||
use crate::{Editor, ToPoint};
|
||||
use collections::HashSet;
|
||||
use futures::Future;
|
||||
use gpui::{AssetSource, View, ViewContext, VisualTestContext};
|
||||
use gpui::{View, ViewContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageQueries,
|
||||
@@ -39,12 +39,7 @@ impl EditorLspTestContext {
|
||||
let app_state = cx.update(AppState::test);
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.text_system()
|
||||
.add_fonts(vec![assets::Assets
|
||||
.load("fonts/zed-mono/zed-mono-extended.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
assets::Assets.load_test_fonts(cx);
|
||||
language::init(cx);
|
||||
crate::init(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
|
||||
@@ -28,6 +28,7 @@ fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
isahc.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
57
crates/extension/src/extension_indexed_docs_provider.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::FutureExt;
|
||||
use indexed_docs::{IndexedDocsDatabase, IndexedDocsProvider, PackageName, ProviderId};
|
||||
use wasmtime_wasi::WasiView;
|
||||
|
||||
use crate::wasm_host::{WasmExtension, WasmHost};
|
||||
|
||||
pub struct ExtensionIndexedDocsProvider {
|
||||
pub(crate) extension: WasmExtension,
|
||||
pub(crate) host: Arc<WasmHost>,
|
||||
pub(crate) id: ProviderId,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl IndexedDocsProvider for ExtensionIndexedDocsProvider {
|
||||
fn id(&self) -> ProviderId {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn database_path(&self) -> PathBuf {
|
||||
let mut database_path = self.host.work_dir.clone();
|
||||
database_path.push(self.extension.manifest.id.as_ref());
|
||||
database_path.push("docs");
|
||||
database_path.push(format!("{}.0.mdb", self.id));
|
||||
|
||||
database_path
|
||||
}
|
||||
|
||||
async fn index(&self, package: PackageName, database: Arc<IndexedDocsDatabase>) -> Result<()> {
|
||||
self.extension
|
||||
.call({
|
||||
let id = self.id.clone();
|
||||
|extension, store| {
|
||||
async move {
|
||||
let database_resource = store.data_mut().table().push(database)?;
|
||||
extension
|
||||
.call_index_docs(
|
||||
store,
|
||||
id.as_ref(),
|
||||
package.as_ref(),
|
||||
database_resource,
|
||||
)
|
||||
.await?
|
||||
.map_err(|err| anyhow!("{err:?}"))?;
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,8 @@ pub struct ExtensionManifest {
|
||||
pub language_servers: BTreeMap<LanguageServerName, LanguageServerManifestEntry>,
|
||||
#[serde(default)]
|
||||
pub slash_commands: BTreeMap<Arc<str>, SlashCommandManifestEntry>,
|
||||
#[serde(default)]
|
||||
pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||
@@ -137,6 +139,9 @@ pub struct SlashCommandManifestEntry {
|
||||
pub requires_argument: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||
pub struct IndexedDocsProviderEntry {}
|
||||
|
||||
impl ExtensionManifest {
|
||||
pub async fn load(fs: Arc<dyn Fs>, extension_dir: &Path) -> Result<Self> {
|
||||
let extension_name = extension_dir
|
||||
@@ -200,5 +205,6 @@ fn manifest_from_old_manifest(
|
||||
.collect(),
|
||||
language_servers: Default::default(),
|
||||
slash_commands: BTreeMap::default(),
|
||||
indexed_docs_providers: BTreeMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod extension_builder;
|
||||
mod extension_indexed_docs_provider;
|
||||
mod extension_lsp_adapter;
|
||||
mod extension_manifest;
|
||||
mod extension_settings;
|
||||
@@ -8,6 +9,7 @@ mod wasm_host;
|
||||
#[cfg(test)]
|
||||
mod extension_store_test;
|
||||
|
||||
use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider;
|
||||
use crate::extension_manifest::SchemaVersion;
|
||||
use crate::extension_slash_command::ExtensionSlashCommand;
|
||||
use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
|
||||
@@ -32,6 +34,7 @@ use gpui::{
|
||||
WeakModel,
|
||||
};
|
||||
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use indexed_docs::{IndexedDocsRegistry, ProviderId};
|
||||
use language::{
|
||||
LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
|
||||
};
|
||||
@@ -111,6 +114,7 @@ pub struct ExtensionStore {
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
modified_extensions: HashSet<Arc<str>>,
|
||||
wasm_host: Arc<WasmHost>,
|
||||
wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
|
||||
@@ -188,6 +192,7 @@ pub fn init(
|
||||
language_registry,
|
||||
theme_registry,
|
||||
SlashCommandRegistry::global(cx),
|
||||
IndexedDocsRegistry::global(cx),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -221,6 +226,7 @@ impl ExtensionStore {
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let work_dir = extensions_dir.join("work");
|
||||
@@ -252,6 +258,7 @@ impl ExtensionStore {
|
||||
language_registry,
|
||||
theme_registry,
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
reload_tx,
|
||||
tasks: Vec::new(),
|
||||
};
|
||||
@@ -1192,7 +1199,18 @@ impl ExtensionStore {
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
for (provider_id, _provider) in &manifest.indexed_docs_providers {
|
||||
this.indexed_docs_registry.register_provider(Box::new(
|
||||
ExtensionIndexedDocsProvider {
|
||||
extension: wasm_extension.clone(),
|
||||
host: this.wasm_host.clone(),
|
||||
id: ProviderId(provider_id.clone()),
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
this.wasm_extensions.extend(wasm_extensions);
|
||||
ThemeSettings::reload_current_theme(cx)
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ use fs::{FakeFs, Fs, RealFs};
|
||||
use futures::{io::BufReader, AsyncReadExt, StreamExt};
|
||||
use gpui::{Context, SemanticVersion, TestAppContext};
|
||||
use http::{FakeHttpClient, Response};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
use parking_lot::Mutex;
|
||||
@@ -158,6 +159,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
.collect(),
|
||||
language_servers: BTreeMap::default(),
|
||||
slash_commands: BTreeMap::default(),
|
||||
indexed_docs_providers: BTreeMap::default(),
|
||||
}),
|
||||
dev: false,
|
||||
},
|
||||
@@ -182,6 +184,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
grammars: BTreeMap::default(),
|
||||
language_servers: BTreeMap::default(),
|
||||
slash_commands: BTreeMap::default(),
|
||||
indexed_docs_providers: BTreeMap::default(),
|
||||
}),
|
||||
dev: false,
|
||||
},
|
||||
@@ -254,6 +257,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
|
||||
let slash_command_registry = SlashCommandRegistry::new();
|
||||
let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
|
||||
let node_runtime = FakeNodeRuntime::new();
|
||||
|
||||
let store = cx.new_model(|cx| {
|
||||
@@ -267,6 +271,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
slash_command_registry.clone(),
|
||||
indexed_docs_registry.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -339,6 +344,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
grammars: BTreeMap::default(),
|
||||
language_servers: BTreeMap::default(),
|
||||
slash_commands: BTreeMap::default(),
|
||||
indexed_docs_providers: BTreeMap::default(),
|
||||
}),
|
||||
dev: false,
|
||||
},
|
||||
@@ -389,6 +395,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -439,7 +446,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
|
||||
async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
cx.executor().allow_parking();
|
||||
|
||||
@@ -449,7 +456,8 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
|
||||
.parent()
|
||||
.unwrap();
|
||||
let cache_dir = root_dir.join("target");
|
||||
let gleam_extension_dir = root_dir.join("extensions").join("gleam");
|
||||
let test_extension_id = "test-extension";
|
||||
let test_extension_dir = root_dir.join("extensions").join(test_extension_id);
|
||||
|
||||
let fs = Arc::new(RealFs::default());
|
||||
let extensions_dir = temp_tree(json!({
|
||||
@@ -468,6 +476,7 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
|
||||
let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
|
||||
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
|
||||
let slash_command_registry = SlashCommandRegistry::new();
|
||||
let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
|
||||
let node_runtime = FakeNodeRuntime::new();
|
||||
|
||||
let mut status_updates = language_registry.language_server_binary_statuses();
|
||||
@@ -558,6 +567,7 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -587,7 +597,7 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
|
||||
|
||||
extension_store
|
||||
.update(cx, |store, cx| {
|
||||
store.install_dev_extension(gleam_extension_dir.clone(), cx)
|
||||
store.install_dev_extension(test_extension_dir.clone(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -602,7 +612,8 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
|
||||
.unwrap();
|
||||
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
let expected_server_path = extensions_dir.join("work/gleam/gleam-v1.2.3/gleam");
|
||||
let expected_server_path =
|
||||
extensions_dir.join(format!("work/{test_extension_id}/gleam-v1.2.3/gleam"));
|
||||
let expected_binary_contents = language_server_version.lock().binary_contents.clone();
|
||||
|
||||
assert_eq!(fake_server.binary.path, expected_server_path);
|
||||
@@ -716,7 +727,8 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
|
||||
|
||||
// The extension re-fetches the latest version of the language server.
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
let new_expected_server_path = extensions_dir.join("work/gleam/gleam-v2.0.0/gleam");
|
||||
let new_expected_server_path =
|
||||
extensions_dir.join(format!("work/{test_extension_id}/gleam-v2.0.0/gleam"));
|
||||
let expected_binary_contents = language_server_version.lock().binary_contents.clone();
|
||||
assert_eq!(fake_server.binary.path, new_expected_server_path);
|
||||
assert_eq!(fake_server.binary.arguments, [OsString::from("lsp")]);
|
||||
|
||||
@@ -2,6 +2,7 @@ mod since_v0_0_1;
|
||||
mod since_v0_0_4;
|
||||
mod since_v0_0_6;
|
||||
mod since_v0_0_7;
|
||||
use indexed_docs::IndexedDocsDatabase;
|
||||
use release_channel::ReleaseChannel;
|
||||
use since_v0_0_7 as latest;
|
||||
|
||||
@@ -289,6 +290,24 @@ impl Extension {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call_index_docs(
|
||||
&self,
|
||||
store: &mut Store<WasmState>,
|
||||
provider: &str,
|
||||
package_name: &str,
|
||||
database: Resource<Arc<IndexedDocsDatabase>>,
|
||||
) -> Result<Result<(), String>> {
|
||||
match self {
|
||||
Extension::V007(ext) => {
|
||||
ext.call_index_docs(store, provider, package_name, database)
|
||||
.await
|
||||
}
|
||||
Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
|
||||
Err(anyhow!("`index_docs` not available prior to v0.0.7"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait ToWasmtimeResult<T> {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
|
||||
use ::settings::Settings;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use futures::{io::BufReader, FutureExt as _};
|
||||
use http::AsyncBody;
|
||||
use indexed_docs::IndexedDocsDatabase;
|
||||
use language::{
|
||||
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
|
||||
};
|
||||
@@ -26,6 +29,7 @@ wasmtime::component::bindgen!({
|
||||
path: "../extension_api/wit/since_v0.0.7",
|
||||
with: {
|
||||
"worktree": ExtensionWorktree,
|
||||
"key-value-store": ExtensionKeyValueStore
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,11 +41,31 @@ mod settings {
|
||||
|
||||
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
|
||||
|
||||
pub type ExtensionKeyValueStore = Arc<IndexedDocsDatabase>;
|
||||
|
||||
pub fn linker() -> &'static Linker<WasmState> {
|
||||
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
|
||||
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HostKeyValueStore for WasmState {
|
||||
async fn insert(
|
||||
&mut self,
|
||||
kv_store: Resource<ExtensionKeyValueStore>,
|
||||
key: String,
|
||||
value: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
let kv_store = self.table.get(&kv_store)?;
|
||||
kv_store.insert(key, value).await.to_wasmtime_result()
|
||||
}
|
||||
|
||||
fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
|
||||
// We only ever hand out borrows of key-value stores.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HostWorktree for WasmState {
|
||||
async fn id(
|
||||
@@ -101,6 +125,41 @@ impl HostWorktree for WasmState {
|
||||
#[async_trait]
|
||||
impl common::Host for WasmState {}
|
||||
|
||||
#[async_trait]
|
||||
impl http_client::Host for WasmState {
|
||||
async fn fetch(
|
||||
&mut self,
|
||||
req: http_client::HttpRequest,
|
||||
) -> wasmtime::Result<Result<http_client::HttpResponse, String>> {
|
||||
maybe!(async {
|
||||
let url = &req.url;
|
||||
|
||||
let mut response = self
|
||||
.host
|
||||
.http_client
|
||||
.get(url, AsyncBody::default(), true)
|
||||
.await?;
|
||||
|
||||
if response.status().is_client_error() || response.status().is_server_error() {
|
||||
bail!("failed to fetch '{url}': status code {}", response.status())
|
||||
}
|
||||
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut body)
|
||||
.await
|
||||
.with_context(|| format!("failed to read response body from '{url}'"))?;
|
||||
|
||||
Ok(http_client::HttpResponse {
|
||||
body: String::from_utf8(body)?,
|
||||
})
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl nodejs::Host for WasmState {
|
||||
async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
|
||||
|
||||
@@ -19,6 +19,7 @@ pub use wit::{
|
||||
github_release_by_tag_name, latest_github_release, GithubRelease, GithubReleaseAsset,
|
||||
GithubReleaseOptions,
|
||||
},
|
||||
zed::extension::http_client::{fetch, HttpRequest, HttpResponse},
|
||||
zed::extension::nodejs::{
|
||||
node_binary_path, npm_install_package, npm_package_installed_version,
|
||||
npm_package_latest_version,
|
||||
@@ -26,7 +27,7 @@ pub use wit::{
|
||||
zed::extension::platform::{current_platform, Architecture, Os},
|
||||
zed::extension::slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection},
|
||||
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
|
||||
LanguageServerInstallationStatus, Range, Worktree,
|
||||
KeyValueStore, LanguageServerInstallationStatus, Range, Worktree,
|
||||
};
|
||||
|
||||
// Undocumented WIT re-exports.
|
||||
@@ -126,6 +127,15 @@ pub trait Extension: Send + Sync {
|
||||
) -> Result<SlashCommandOutput, String> {
|
||||
Err("`run_slash_command` not implemented".to_string())
|
||||
}
|
||||
|
||||
fn index_docs(
|
||||
&self,
|
||||
_provider: String,
|
||||
_package: String,
|
||||
_database: &KeyValueStore,
|
||||
) -> Result<(), String> {
|
||||
Err("`index_docs` not implemented".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers the provided type as a Zed extension.
|
||||
@@ -248,6 +258,14 @@ impl wit::Guest for Component {
|
||||
) -> Result<SlashCommandOutput, String> {
|
||||
extension().run_slash_command(command, argument, worktree)
|
||||
}
|
||||
|
||||
fn index_docs(
|
||||
provider: String,
|
||||
package: String,
|
||||
database: &KeyValueStore,
|
||||
) -> Result<(), String> {
|
||||
extension().index_docs(provider, package, database)
|
||||
}
|
||||
}
|
||||
|
||||
/// The ID of a language server.
|
||||
|
||||
@@ -2,6 +2,7 @@ package zed:extension;
|
||||
|
||||
world extension {
|
||||
import github;
|
||||
import http-client;
|
||||
import platform;
|
||||
import nodejs;
|
||||
|
||||
@@ -82,6 +83,12 @@ world extension {
|
||||
shell-env: func() -> env-vars;
|
||||
}
|
||||
|
||||
/// A key-value store.
|
||||
resource key-value-store {
|
||||
/// Inserts an entry under the specified key.
|
||||
insert: func(key: string, value: string) -> result<_, string>;
|
||||
}
|
||||
|
||||
/// Returns the command used to start up the language server.
|
||||
export language-server-command: func(language-server-id: string, worktree: borrow<worktree>) -> result<command, string>;
|
||||
|
||||
@@ -127,4 +134,7 @@ world extension {
|
||||
|
||||
/// Returns the output from running the provided slash command.
|
||||
export run-slash-command: func(command: slash-command, argument: option<string>, worktree: borrow<worktree>) -> result<slash-command-output, string>;
|
||||
|
||||
/// Indexes the docs for the specified package.
|
||||
export index-docs: func(provider-name: string, package-name: string, database: borrow<key-value-store>) -> result<_, string>;
|
||||
}
|
||||
|
||||
16
crates/extension_api/wit/since_v0.0.7/http-client.wit
Normal file
@@ -0,0 +1,16 @@
|
||||
interface http-client {
|
||||
/// An HTTP request.
|
||||
record http-request {
|
||||
/// The URL to which the request should be made.
|
||||
url: string,
|
||||
}
|
||||
|
||||
/// An HTTP response.
|
||||
record http-response {
|
||||
/// The response body.
|
||||
body: string,
|
||||
}
|
||||
|
||||
/// Performs an HTTP request and returns the response.
|
||||
fetch: func(req: http-request) -> result<http-response, string>;
|
||||
}
|
||||
@@ -29,6 +29,11 @@ impl FeatureFlag for Remoting {
|
||||
const NAME: &'static str = "remoting";
|
||||
}
|
||||
|
||||
pub struct TerminalInlineAssist {}
|
||||
impl FeatureFlag for TerminalInlineAssist {
|
||||
const NAME: &'static str = "terminal-inline-assist";
|
||||
}
|
||||
|
||||
pub trait FeatureFlagViewExt<V: 'static> {
|
||||
fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
|
||||
where
|
||||
|
||||
@@ -133,6 +133,7 @@ x11rb = { version = "0.13.0", features = [
|
||||
"xinput",
|
||||
"cursor",
|
||||
"resource_manager",
|
||||
"sync",
|
||||
] }
|
||||
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
|
||||
xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca4afad184ab6e7c16af", features = [
|
||||
@@ -141,6 +142,7 @@ xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca
|
||||
] }
|
||||
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = ["source-fontconfig-dlopen"] }
|
||||
x11-clipboard = "0.9.2"
|
||||
mio = { version = "1.0.0", features = ["os-poll", "os-ext"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
@@ -159,6 +161,10 @@ path = "examples/image/image.rs"
|
||||
name = "set_menus"
|
||||
path = "examples/set_menus.rs"
|
||||
|
||||
[[example]]
|
||||
name = "window_shadow"
|
||||
path = "examples/window_shadow.rs"
|
||||
|
||||
[[example]]
|
||||
name = "input"
|
||||
path = "examples/input.rs"
|
||||
|
||||
@@ -23,6 +23,11 @@ impl AssetSource for Assets {
|
||||
}
|
||||
}
|
||||
|
||||
const ARROW_CIRCLE_SVG: &'static str = concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/examples/image/arrow_circle.svg"
|
||||
);
|
||||
|
||||
struct AnimationExample {}
|
||||
|
||||
impl Render for AnimationExample {
|
||||
@@ -42,7 +47,7 @@ impl Render for AnimationExample {
|
||||
.child(
|
||||
svg()
|
||||
.size_8()
|
||||
.path("examples/image/arrow_circle.svg")
|
||||
.path(ARROW_CIRCLE_SVG)
|
||||
.text_color(black())
|
||||
.with_animation(
|
||||
"image_circle",
|
||||
|
||||
@@ -23,7 +23,7 @@ impl Render for HelloWorld {
|
||||
|
||||
fn main() {
|
||||
App::new().run(|cx: &mut AppContext| {
|
||||
let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
|
||||
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
|
||||
@@ -15,9 +15,7 @@ actions!(
|
||||
SelectAll,
|
||||
Home,
|
||||
End,
|
||||
ShowCharacterPalette,
|
||||
Copy,
|
||||
Paste
|
||||
ShowCharacterPalette
|
||||
]
|
||||
);
|
||||
|
||||
@@ -35,7 +33,7 @@ impl TextInput {
|
||||
if self.selected_range.is_empty() {
|
||||
self.move_to(self.previous_boundary(self.cursor_offset()), cx);
|
||||
} else {
|
||||
self.move_to(self.selected_range.end, cx)
|
||||
self.move_to(self.selected_range.start, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +41,7 @@ impl TextInput {
|
||||
if self.selected_range.is_empty() {
|
||||
self.move_to(self.next_boundary(self.selected_range.end), cx);
|
||||
} else {
|
||||
self.move_to(self.selected_range.start, cx)
|
||||
self.move_to(self.selected_range.end, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +53,7 @@ impl TextInput {
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), cx);
|
||||
}
|
||||
|
||||
fn select_all(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
|
||||
fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
|
||||
self.move_to(0, cx);
|
||||
self.select_to(self.content.len(), cx)
|
||||
}
|
||||
@@ -82,20 +80,6 @@ impl TextInput {
|
||||
self.replace_text_in_range(None, "", cx)
|
||||
}
|
||||
|
||||
fn copy(&mut self, _: &crate::Copy, cx: &mut ViewContext<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
return;
|
||||
}
|
||||
let selected_text = &self.content[self.selected_range.clone()];
|
||||
cx.write_to_clipboard(ClipboardItem::new(selected_text.to_string()));
|
||||
}
|
||||
|
||||
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||
if let Some(item) = cx.read_from_clipboard() {
|
||||
self.replace_text_in_range(None, &item.text().replace('\n', ""), cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
|
||||
cx.show_character_palette();
|
||||
}
|
||||
@@ -445,9 +429,6 @@ impl Render for TextInput {
|
||||
.on_action(cx.listener(Self::home))
|
||||
.on_action(cx.listener(Self::end))
|
||||
.on_action(cx.listener(Self::show_character_palette))
|
||||
.on_action(cx.listener(Self::paste))
|
||||
.on_action(cx.listener(Self::copy))
|
||||
.font_family("Zed Mono")
|
||||
.bg(rgb(0xeeeeee))
|
||||
.size_full()
|
||||
.line_height(px(30.))
|
||||
@@ -466,19 +447,8 @@ impl Render for TextInput {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::Builder::new().parse_default_env().init();
|
||||
App::new().run(|cx: &mut AppContext| {
|
||||
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
|
||||
let fonts = vec![
|
||||
std::fs::read("assets/fonts/zed-mono/zed-mono-extended.ttf")
|
||||
.unwrap()
|
||||
.into(),
|
||||
std::fs::read("assets/fonts/zed-sans/zed-sans-extended.ttf")
|
||||
.unwrap()
|
||||
.into(),
|
||||
];
|
||||
|
||||
cx.text_system().add_fonts(fonts).unwrap();
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("backspace", Backspace, None),
|
||||
KeyBinding::new("delete", Delete, None),
|
||||
@@ -487,15 +457,9 @@ fn main() {
|
||||
KeyBinding::new("shift-left", SelectLeft, None),
|
||||
KeyBinding::new("shift-right", SelectRight, None),
|
||||
KeyBinding::new("cmd-a", SelectAll, None),
|
||||
KeyBinding::new("cmd-c", Copy, None),
|
||||
KeyBinding::new("cmd-v", Paste, None),
|
||||
KeyBinding::new("home", Home, None),
|
||||
KeyBinding::new("end", End, None),
|
||||
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None),
|
||||
KeyBinding::new("ctrl-a", SelectAll, None),
|
||||
KeyBinding::new("ctrl-c", Copy, None),
|
||||
KeyBinding::new("ctrl-v", Paste, None),
|
||||
KeyBinding::new("ctrl-.", ShowCharacterPalette, None),
|
||||
]);
|
||||
let window = cx
|
||||
.open_window(
|
||||
@@ -506,7 +470,7 @@ fn main() {
|
||||
|cx| {
|
||||
cx.new_view(|cx| TextInput {
|
||||
focus_handle: cx.focus_handle(),
|
||||
content: "aチ".into(),
|
||||
content: "".into(),
|
||||
selected_range: 0..0,
|
||||
selection_reversed: false,
|
||||
marked_range: None,
|
||||
|
||||
@@ -52,6 +52,7 @@ fn main() {
|
||||
is_movable: false,
|
||||
app_id: None,
|
||||
window_min_size: None,
|
||||
window_decorations: None,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
222
crates/gpui/examples/window_shadow.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
use gpui::*;
|
||||
use prelude::FluentBuilder;
|
||||
|
||||
struct WindowShadow {}
|
||||
|
||||
/*
|
||||
Things to do:
|
||||
1. We need a way of calculating which edge or corner the mouse is on,
|
||||
and then dispatch on that
|
||||
2. We need to improve the shadow rendering significantly
|
||||
3. We need to implement the techniques in here in Zed
|
||||
*/
|
||||
|
||||
impl Render for WindowShadow {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let decorations = cx.window_decorations();
|
||||
let rounding = px(10.0);
|
||||
let shadow_size = px(10.0);
|
||||
let border_size = px(1.0);
|
||||
let grey = rgb(0x808080);
|
||||
cx.set_client_inset(shadow_size);
|
||||
|
||||
div()
|
||||
.id("window-backdrop")
|
||||
.bg(transparent_black())
|
||||
.map(|div| match decorations {
|
||||
Decorations::Server => div,
|
||||
Decorations::Client { tiling, .. } => div
|
||||
.bg(gpui::transparent_black())
|
||||
.child(
|
||||
canvas(
|
||||
|_bounds, cx| {
|
||||
cx.insert_hitbox(
|
||||
Bounds::new(
|
||||
point(px(0.0), px(0.0)),
|
||||
cx.window_bounds().get_bounds().size,
|
||||
),
|
||||
false,
|
||||
)
|
||||
},
|
||||
move |_bounds, hitbox, cx| {
|
||||
let mouse = cx.mouse_position();
|
||||
let size = cx.window_bounds().get_bounds().size;
|
||||
let Some(edge) = resize_edge(mouse, shadow_size, size) else {
|
||||
return;
|
||||
};
|
||||
cx.set_cursor_style(
|
||||
match edge {
|
||||
ResizeEdge::Top | ResizeEdge::Bottom => {
|
||||
CursorStyle::ResizeUpDown
|
||||
}
|
||||
ResizeEdge::Left | ResizeEdge::Right => {
|
||||
CursorStyle::ResizeLeftRight
|
||||
}
|
||||
ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
|
||||
CursorStyle::ResizeUpLeftDownRight
|
||||
}
|
||||
ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
|
||||
CursorStyle::ResizeUpRightDownLeft
|
||||
}
|
||||
},
|
||||
&hitbox,
|
||||
);
|
||||
},
|
||||
)
|
||||
.size_full()
|
||||
.absolute(),
|
||||
)
|
||||
.when(!(tiling.top || tiling.right), |div| {
|
||||
div.rounded_tr(rounding)
|
||||
})
|
||||
.when(!(tiling.top || tiling.left), |div| div.rounded_tl(rounding))
|
||||
.when(!tiling.top, |div| div.pt(shadow_size))
|
||||
.when(!tiling.bottom, |div| div.pb(shadow_size))
|
||||
.when(!tiling.left, |div| div.pl(shadow_size))
|
||||
.when(!tiling.right, |div| div.pr(shadow_size))
|
||||
.on_mouse_move(|_e, cx| cx.refresh())
|
||||
.on_mouse_down(MouseButton::Left, move |e, cx| {
|
||||
let size = cx.window_bounds().get_bounds().size;
|
||||
let pos = e.position;
|
||||
|
||||
match resize_edge(pos, shadow_size, size) {
|
||||
Some(edge) => cx.start_window_resize(edge),
|
||||
None => cx.start_window_move(),
|
||||
};
|
||||
}),
|
||||
})
|
||||
.size_full()
|
||||
.child(
|
||||
div()
|
||||
.cursor(CursorStyle::Arrow)
|
||||
.map(|div| match decorations {
|
||||
Decorations::Server => div,
|
||||
Decorations::Client { tiling } => div
|
||||
.border_color(grey)
|
||||
.when(!(tiling.top || tiling.right), |div| {
|
||||
div.rounded_tr(rounding)
|
||||
})
|
||||
.when(!(tiling.top || tiling.left), |div| div.rounded_tl(rounding))
|
||||
.when(!tiling.top, |div| div.border_t(border_size))
|
||||
.when(!tiling.bottom, |div| div.border_b(border_size))
|
||||
.when(!tiling.left, |div| div.border_l(border_size))
|
||||
.when(!tiling.right, |div| div.border_r(border_size))
|
||||
.when(!tiling.is_tiled(), |div| {
|
||||
div.shadow(smallvec::smallvec![gpui::BoxShadow {
|
||||
color: Hsla {
|
||||
h: 0.,
|
||||
s: 0.,
|
||||
l: 0.,
|
||||
a: 0.4,
|
||||
},
|
||||
blur_radius: shadow_size / 2.,
|
||||
spread_radius: px(0.),
|
||||
offset: point(px(0.0), px(0.0)),
|
||||
}])
|
||||
}),
|
||||
})
|
||||
.on_mouse_move(|_e, cx| {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.bg(gpui::rgb(0xCCCCFF))
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.justify_around()
|
||||
.child(
|
||||
div().w_full().flex().flex_row().justify_around().child(
|
||||
div()
|
||||
.flex()
|
||||
.bg(white())
|
||||
.size(Length::Definite(Pixels(300.0).into()))
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.shadow_lg()
|
||||
.border_1()
|
||||
.border_color(rgb(0x0000ff))
|
||||
.text_xl()
|
||||
.text_color(rgb(0xffffff))
|
||||
.child(
|
||||
div()
|
||||
.id("hello")
|
||||
.w(px(200.0))
|
||||
.h(px(100.0))
|
||||
.bg(green())
|
||||
.shadow(smallvec::smallvec![gpui::BoxShadow {
|
||||
color: Hsla {
|
||||
h: 0.,
|
||||
s: 0.,
|
||||
l: 0.,
|
||||
a: 1.0,
|
||||
},
|
||||
blur_radius: px(20.0),
|
||||
spread_radius: px(0.0),
|
||||
offset: point(px(0.0), px(0.0)),
|
||||
}])
|
||||
.map(|div| match decorations {
|
||||
Decorations::Server => div,
|
||||
Decorations::Client { .. } => div
|
||||
.on_mouse_down(MouseButton::Left, |_e, cx| {
|
||||
cx.start_window_move();
|
||||
})
|
||||
.on_click(|e, cx| {
|
||||
if e.down.button == MouseButton::Right {
|
||||
cx.show_window_menu(e.up.position);
|
||||
}
|
||||
})
|
||||
.text_color(black())
|
||||
.child("this is the custom titlebar"),
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn resize_edge(pos: Point<Pixels>, shadow_size: Pixels, size: Size<Pixels>) -> Option<ResizeEdge> {
|
||||
let edge = if pos.y < shadow_size && pos.x < shadow_size {
|
||||
ResizeEdge::TopLeft
|
||||
} else if pos.y < shadow_size && pos.x > size.width - shadow_size {
|
||||
ResizeEdge::TopRight
|
||||
} else if pos.y < shadow_size {
|
||||
ResizeEdge::Top
|
||||
} else if pos.y > size.height - shadow_size && pos.x < shadow_size {
|
||||
ResizeEdge::BottomLeft
|
||||
} else if pos.y > size.height - shadow_size && pos.x > size.width - shadow_size {
|
||||
ResizeEdge::BottomRight
|
||||
} else if pos.y > size.height - shadow_size {
|
||||
ResizeEdge::Bottom
|
||||
} else if pos.x < shadow_size {
|
||||
ResizeEdge::Left
|
||||
} else if pos.x > size.width - shadow_size {
|
||||
ResizeEdge::Right
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some(edge)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new().run(|cx: &mut AppContext| {
|
||||
let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
window_background: WindowBackgroundAppearance::Opaque,
|
||||
window_decorations: Some(WindowDecorations::Client),
|
||||
..Default::default()
|
||||
},
|
||||
|cx| {
|
||||
cx.new_view(|cx| {
|
||||
cx.observe_window_appearance(|_, cx| {
|
||||
cx.refresh();
|
||||
})
|
||||
.detach();
|
||||
WindowShadow {}
|
||||
})
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
@@ -309,6 +309,16 @@ pub fn transparent_black() -> Hsla {
|
||||
}
|
||||
}
|
||||
|
||||
/// Transparent black in [`Hsla`]
|
||||
pub fn transparent_white() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.,
|
||||
s: 0.,
|
||||
l: 1.,
|
||||
a: 0.,
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque grey in [`Hsla`], values will be clamped to the range [0, 1]
|
||||
pub fn opaque_grey(lightness: f32, opacity: f32) -> Hsla {
|
||||
Hsla {
|
||||
|
||||
@@ -883,6 +883,14 @@ where
|
||||
self.size.height = self.size.height.clone() + double_amount;
|
||||
}
|
||||
|
||||
/// inset the bounds by a specified amount
|
||||
/// Note that this may panic if T does not support negative values
|
||||
pub fn inset(&self, amount: T) -> Self {
|
||||
let mut result = self.clone();
|
||||
result.dilate(T::default() - amount);
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns the center point of the bounds.
|
||||
///
|
||||
/// Calculates the center by taking the origin's x and y coordinates and adding half the width and height
|
||||
@@ -1266,12 +1274,36 @@ where
|
||||
/// size: Size { width: 10.0, height: 20.0 },
|
||||
/// });
|
||||
/// ```
|
||||
pub fn map_origin(self, f: impl Fn(Point<T>) -> Point<T>) -> Bounds<T> {
|
||||
pub fn map_origin(self, f: impl Fn(T) -> T) -> Bounds<T> {
|
||||
Bounds {
|
||||
origin: f(self.origin),
|
||||
origin: self.origin.map(f),
|
||||
size: self.size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a function to the origin of the bounds, producing a new `Bounds` with the new origin
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 10.0, y: 10.0 },
|
||||
/// size: Size { width: 10.0, height: 20.0 },
|
||||
/// };
|
||||
/// let new_bounds = bounds.map_size(|value| value * 1.5);
|
||||
///
|
||||
/// assert_eq!(new_bounds, Bounds {
|
||||
/// origin: Point { x: 10.0, y: 10.0 },
|
||||
/// size: Size { width: 15.0, height: 30.0 },
|
||||
/// });
|
||||
/// ```
|
||||
pub fn map_size(self, f: impl Fn(T) -> T) -> Bounds<T> {
|
||||
Bounds {
|
||||
origin: self.origin,
|
||||
size: self.size.map(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the bounds represent an empty area.
|
||||
|
||||
@@ -210,6 +210,83 @@ impl Debug for DisplayId {
|
||||
|
||||
unsafe impl Send for DisplayId {}
|
||||
|
||||
/// Which part of the window to resize
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ResizeEdge {
|
||||
/// The top edge
|
||||
Top,
|
||||
/// The top right corner
|
||||
TopRight,
|
||||
/// The right edge
|
||||
Right,
|
||||
/// The bottom right corner
|
||||
BottomRight,
|
||||
/// The bottom edge
|
||||
Bottom,
|
||||
/// The bottom left corner
|
||||
BottomLeft,
|
||||
/// The left edge
|
||||
Left,
|
||||
/// The top left corner
|
||||
TopLeft,
|
||||
}
|
||||
|
||||
/// A type to describe the appearance of a window
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
||||
pub enum WindowDecorations {
|
||||
#[default]
|
||||
/// Server side decorations
|
||||
Server,
|
||||
/// Client side decorations
|
||||
Client,
|
||||
}
|
||||
|
||||
/// A type to describe how this window is currently configured
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
||||
pub enum Decorations {
|
||||
/// The window is configured to use server side decorations
|
||||
#[default]
|
||||
Server,
|
||||
/// The window is configured to use client side decorations
|
||||
Client {
|
||||
/// The edge tiling state
|
||||
tiling: Tiling,
|
||||
},
|
||||
}
|
||||
|
||||
/// What window controls this platform supports
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
||||
pub struct WindowControls {
|
||||
/// Whether this platform supports fullscreen
|
||||
pub fullscreen: bool,
|
||||
/// Whether this platform supports maximize
|
||||
pub maximize: bool,
|
||||
/// Whether this platform supports minimize
|
||||
pub minimize: bool,
|
||||
/// Whether this platform supports a window menu
|
||||
pub window_menu: bool,
|
||||
}
|
||||
|
||||
/// A type to describe which sides of the window are currently tiled in some way
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
||||
pub struct Tiling {
|
||||
/// Whether the top edge is tiled
|
||||
pub top: bool,
|
||||
/// Whether the left edge is tiled
|
||||
pub left: bool,
|
||||
/// Whether the right edge is tiled
|
||||
pub right: bool,
|
||||
/// Whether the bottom edge is tiled
|
||||
pub bottom: bool,
|
||||
}
|
||||
|
||||
impl Tiling {
|
||||
/// Whether any edge is tiled
|
||||
pub fn is_tiled(&self) -> bool {
|
||||
self.top || self.left || self.right || self.bottom
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn bounds(&self) -> Bounds<Pixels>;
|
||||
fn is_maximized(&self) -> bool;
|
||||
@@ -232,10 +309,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn activate(&self);
|
||||
fn is_active(&self) -> bool;
|
||||
fn set_title(&mut self, title: &str);
|
||||
fn set_app_id(&mut self, app_id: &str);
|
||||
fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance);
|
||||
fn set_edited(&mut self, edited: bool);
|
||||
fn show_character_palette(&self);
|
||||
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
|
||||
fn minimize(&self);
|
||||
fn zoom(&self);
|
||||
fn toggle_fullscreen(&self);
|
||||
@@ -252,12 +326,31 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn completed_frame(&self) {}
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||
|
||||
// macOS specific methods
|
||||
fn set_edited(&mut self, _edited: bool) {}
|
||||
fn show_character_palette(&self) {}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn get_raw_handle(&self) -> windows::HWND;
|
||||
|
||||
fn show_window_menu(&self, position: Point<Pixels>);
|
||||
fn start_system_move(&self);
|
||||
fn should_render_window_controls(&self) -> bool;
|
||||
// Linux specific methods
|
||||
fn request_decorations(&self, _decorations: WindowDecorations) {}
|
||||
fn show_window_menu(&self, _position: Point<Pixels>) {}
|
||||
fn start_window_move(&self) {}
|
||||
fn start_window_resize(&self, _edge: ResizeEdge) {}
|
||||
fn window_decorations(&self) -> Decorations {
|
||||
Decorations::Server
|
||||
}
|
||||
fn set_app_id(&mut self, _app_id: &str) {}
|
||||
fn window_controls(&self) -> WindowControls {
|
||||
WindowControls {
|
||||
fullscreen: true,
|
||||
maximize: true,
|
||||
minimize: true,
|
||||
window_menu: false,
|
||||
}
|
||||
}
|
||||
fn set_client_inset(&self, _inset: Pixels) {}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
||||
@@ -570,6 +663,10 @@ pub struct WindowOptions {
|
||||
|
||||
/// Window minimum size
|
||||
pub window_min_size: Option<Size<Pixels>>,
|
||||
|
||||
/// Whether to use client or server side decorations. Wayland only
|
||||
/// Note that this may be ignored.
|
||||
pub window_decorations: Option<WindowDecorations>,
|
||||
}
|
||||
|
||||
/// The variables that can be configured when creating a new window
|
||||
@@ -596,8 +693,6 @@ pub(crate) struct WindowParams {
|
||||
|
||||
pub display_id: Option<DisplayId>,
|
||||
|
||||
pub window_background: WindowBackgroundAppearance,
|
||||
|
||||
#[cfg_attr(target_os = "linux", allow(dead_code))]
|
||||
pub window_min_size: Option<Size<Pixels>>,
|
||||
}
|
||||
@@ -649,6 +744,7 @@ impl Default for WindowOptions {
|
||||
window_background: WindowBackgroundAppearance::default(),
|
||||
app_id: None,
|
||||
window_min_size: None,
|
||||
window_decorations: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -659,7 +755,7 @@ pub struct TitlebarOptions {
|
||||
/// The initial title of the window
|
||||
pub title: Option<SharedString>,
|
||||
|
||||
/// Whether the titlebar should appear transparent
|
||||
/// Whether the titlebar should appear transparent (macOS only)
|
||||
pub appears_transparent: bool,
|
||||
|
||||
/// The position of the macOS traffic light buttons
|
||||
@@ -805,6 +901,14 @@ pub enum CursorStyle {
|
||||
/// corresponds to the CSS cursor value `ns-resize`
|
||||
ResizeUpDown,
|
||||
|
||||
/// A resize cursor directing up-left and down-right
|
||||
/// corresponds to the CSS cursor value `nesw-resize`
|
||||
ResizeUpLeftDownRight,
|
||||
|
||||
/// A resize cursor directing up-right and down-left
|
||||
/// corresponds to the CSS cursor value `nwse-resize`
|
||||
ResizeUpRightDownLeft,
|
||||
|
||||
/// A cursor indicating that the item/column can be resized horizontally.
|
||||
/// corresponds to the CSS curosr value `col-resize`
|
||||
ResizeColumn,
|
||||
|
||||
@@ -205,7 +205,7 @@ impl CosmicTextSystemState {
|
||||
) -> Result<SmallVec<[FontId; 4]>> {
|
||||
// TODO: Determine the proper system UI font.
|
||||
let name = if name == ".SystemUIFont" {
|
||||
"Zed Sans"
|
||||
"Zed Plex Sans"
|
||||
} else {
|
||||
name
|
||||
};
|
||||
@@ -379,6 +379,7 @@ impl CosmicTextSystemState {
|
||||
offs..(offs + run.len),
|
||||
Attrs::new()
|
||||
.family(Family::Name(&font.families.first().unwrap().0))
|
||||
.stretch(font.stretch)
|
||||
.style(font.style)
|
||||
.weight(font.weight),
|
||||
);
|
||||
|
||||
@@ -5,9 +5,10 @@ use calloop::{
|
||||
timer::TimeoutAction,
|
||||
EventLoop,
|
||||
};
|
||||
use mio::Waker;
|
||||
use parking::{Parker, Unparker};
|
||||
use parking_lot::Mutex;
|
||||
use std::{thread, time::Duration};
|
||||
use std::{sync::Arc, thread, time::Duration};
|
||||
use util::ResultExt;
|
||||
|
||||
struct TimerAfter {
|
||||
@@ -18,6 +19,7 @@ struct TimerAfter {
|
||||
pub(crate) struct LinuxDispatcher {
|
||||
parker: Mutex<Parker>,
|
||||
main_sender: Sender<Runnable>,
|
||||
main_waker: Option<Arc<Waker>>,
|
||||
timer_sender: Sender<TimerAfter>,
|
||||
background_sender: flume::Sender<Runnable>,
|
||||
_background_threads: Vec<thread::JoinHandle<()>>,
|
||||
@@ -25,7 +27,7 @@ pub(crate) struct LinuxDispatcher {
|
||||
}
|
||||
|
||||
impl LinuxDispatcher {
|
||||
pub fn new(main_sender: Sender<Runnable>) -> Self {
|
||||
pub fn new(main_sender: Sender<Runnable>, main_waker: Option<Arc<Waker>>) -> Self {
|
||||
let (background_sender, background_receiver) = flume::unbounded::<Runnable>();
|
||||
let thread_count = std::thread::available_parallelism()
|
||||
.map(|i| i.get())
|
||||
@@ -77,6 +79,7 @@ impl LinuxDispatcher {
|
||||
Self {
|
||||
parker: Mutex::new(Parker::new()),
|
||||
main_sender,
|
||||
main_waker,
|
||||
timer_sender,
|
||||
background_sender,
|
||||
_background_threads: background_threads,
|
||||
@@ -96,6 +99,9 @@ impl PlatformDispatcher for LinuxDispatcher {
|
||||
|
||||
fn dispatch_on_main_thread(&self, runnable: Runnable) {
|
||||
self.main_sender.send(runnable).ok();
|
||||
if let Some(main_waker) = self.main_waker.as_ref() {
|
||||
main_waker.wake().ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
|
||||
|
||||
@@ -22,7 +22,7 @@ impl HeadlessClient {
|
||||
pub(crate) fn new() -> Self {
|
||||
let event_loop = EventLoop::try_new().unwrap();
|
||||
|
||||
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
|
||||
let (common, main_receiver) = LinuxCommon::new(Box::new(event_loop.get_signal()), None);
|
||||
|
||||
let handle = event_loop.handle();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use std::any::{type_name, Any};
|
||||
use std::cell::{self, RefCell};
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
@@ -25,6 +26,7 @@ use calloop::{EventLoop, LoopHandle, LoopSignal};
|
||||
use filedescriptor::FileDescriptor;
|
||||
use flume::{Receiver, Sender};
|
||||
use futures::channel::oneshot;
|
||||
use mio::Waker;
|
||||
use parking_lot::Mutex;
|
||||
use time::UtcOffset;
|
||||
use util::ResultExt;
|
||||
@@ -83,6 +85,16 @@ pub(crate) struct PlatformHandlers {
|
||||
pub(crate) validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
||||
}
|
||||
|
||||
pub trait QuitSignal {
|
||||
fn quit(&mut self);
|
||||
}
|
||||
|
||||
impl QuitSignal for LoopSignal {
|
||||
fn quit(&mut self) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct LinuxCommon {
|
||||
pub(crate) background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
@@ -90,17 +102,20 @@ pub(crate) struct LinuxCommon {
|
||||
pub(crate) appearance: WindowAppearance,
|
||||
pub(crate) auto_hide_scrollbars: bool,
|
||||
pub(crate) callbacks: PlatformHandlers,
|
||||
pub(crate) signal: LoopSignal,
|
||||
pub(crate) quit_signal: Box<dyn QuitSignal>,
|
||||
pub(crate) menus: Vec<OwnedMenu>,
|
||||
}
|
||||
|
||||
impl LinuxCommon {
|
||||
pub fn new(signal: LoopSignal) -> (Self, Channel<Runnable>) {
|
||||
pub fn new(
|
||||
quit_signal: Box<dyn QuitSignal>,
|
||||
main_waker: Option<Arc<Waker>>,
|
||||
) -> (Self, Channel<Runnable>) {
|
||||
let (main_sender, main_receiver) = calloop::channel::channel::<Runnable>();
|
||||
let text_system = Arc::new(CosmicTextSystem::new());
|
||||
let callbacks = PlatformHandlers::default();
|
||||
|
||||
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender.clone()));
|
||||
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender.clone(), main_waker));
|
||||
|
||||
let background_executor = BackgroundExecutor::new(dispatcher.clone());
|
||||
|
||||
@@ -111,7 +126,7 @@ impl LinuxCommon {
|
||||
appearance: WindowAppearance::Light,
|
||||
auto_hide_scrollbars: false,
|
||||
callbacks,
|
||||
signal,
|
||||
quit_signal,
|
||||
menus: Vec::new(),
|
||||
};
|
||||
|
||||
@@ -145,7 +160,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
}
|
||||
|
||||
fn quit(&self) {
|
||||
self.with_common(|common| common.signal.stop());
|
||||
self.with_common(|common| common.quit_signal.quit());
|
||||
}
|
||||
|
||||
fn compositor_name(&self) -> &'static str {
|
||||
@@ -508,6 +523,27 @@ pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bo
|
||||
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
|
||||
}
|
||||
|
||||
pub(super) fn get_xkb_compose_state(cx: &xkb::Context) -> Option<xkb::compose::State> {
|
||||
let mut locales = Vec::default();
|
||||
if let Some(locale) = std::env::var_os("LC_CTYPE") {
|
||||
locales.push(locale);
|
||||
}
|
||||
locales.push(OsString::from("C"));
|
||||
let mut state: Option<xkb::compose::State> = None;
|
||||
for locale in locales {
|
||||
if let Ok(table) =
|
||||
xkb::compose::Table::new_from_locale(&cx, &locale, xkb::compose::COMPILE_NO_FLAGS)
|
||||
{
|
||||
state = Some(xkb::compose::State::new(
|
||||
&table,
|
||||
xkb::compose::STATE_NO_FLAGS,
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
pub(super) unsafe fn read_fd(mut fd: FileDescriptor) -> Result<String> {
|
||||
let mut file = File::from_raw_fd(fd.as_raw_fd());
|
||||
|
||||
@@ -536,6 +572,8 @@ impl CursorStyle {
|
||||
CursorStyle::ResizeUp => Shape::NResize,
|
||||
CursorStyle::ResizeDown => Shape::SResize,
|
||||
CursorStyle::ResizeUpDown => Shape::NsResize,
|
||||
CursorStyle::ResizeUpLeftDownRight => Shape::NwseResize,
|
||||
CursorStyle::ResizeUpRightDownLeft => Shape::NeswResize,
|
||||
CursorStyle::ResizeColumn => Shape::ColResize,
|
||||
CursorStyle::ResizeRow => Shape::RowResize,
|
||||
CursorStyle::IBeamCursorForVerticalLayout => Shape::VerticalText,
|
||||
@@ -563,6 +601,8 @@ impl CursorStyle {
|
||||
CursorStyle::ResizeUp => "n-resize",
|
||||
CursorStyle::ResizeDown => "s-resize",
|
||||
CursorStyle::ResizeUpDown => "ns-resize",
|
||||
CursorStyle::ResizeUpLeftDownRight => "nwse-resize",
|
||||
CursorStyle::ResizeUpRightDownLeft => "nesw-resize",
|
||||
CursorStyle::ResizeColumn => "col-resize",
|
||||
CursorStyle::ResizeRow => "row-resize",
|
||||
CursorStyle::IBeamCursorForVerticalLayout => "vertical-text",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::ffi::OsString;
|
||||
use std::hash::Hash;
|
||||
use std::os::fd::{AsRawFd, BorrowedFd};
|
||||
use std::path::PathBuf;
|
||||
@@ -65,7 +64,6 @@ use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
|
||||
use super::super::{open_uri_internal, read_fd, DOUBLE_CLICK_INTERVAL};
|
||||
use super::display::WaylandDisplay;
|
||||
use super::window::{ImeInput, WaylandWindowStatePtr};
|
||||
use crate::platform::linux::is_within_click_distance;
|
||||
use crate::platform::linux::wayland::clipboard::{
|
||||
Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE,
|
||||
};
|
||||
@@ -74,6 +72,7 @@ use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker};
|
||||
use crate::platform::linux::wayland::window::WaylandWindow;
|
||||
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
|
||||
use crate::platform::linux::LinuxClient;
|
||||
use crate::platform::linux::{get_xkb_compose_state, is_within_click_distance};
|
||||
use crate::platform::PlatformWindow;
|
||||
use crate::{
|
||||
point, px, size, Bounds, DevicePixels, FileDropEvent, ForegroundExecutor, MouseExitEvent, Size,
|
||||
@@ -139,7 +138,7 @@ impl Globals {
|
||||
primary_selection_manager: globals.bind(&qh, 1..=1, ()).ok(),
|
||||
shm: globals.bind(&qh, 1..=1, ()).unwrap(),
|
||||
seat,
|
||||
wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
|
||||
wm_base: globals.bind(&qh, 2..=5, ()).unwrap(),
|
||||
viewporter: globals.bind(&qh, 1..=1, ()).ok(),
|
||||
fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
|
||||
decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
|
||||
@@ -311,7 +310,7 @@ impl WaylandClientStatePtr {
|
||||
}
|
||||
}
|
||||
if state.windows.is_empty() {
|
||||
state.common.signal.stop();
|
||||
state.common.quit_signal.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,7 +406,7 @@ impl WaylandClient {
|
||||
|
||||
let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap();
|
||||
|
||||
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
|
||||
let (common, main_receiver) = LinuxCommon::new(Box::new(event_loop.get_signal()), None);
|
||||
|
||||
let handle = event_loop.handle();
|
||||
handle
|
||||
@@ -444,7 +443,7 @@ impl WaylandClient {
|
||||
let mut cursor = Cursor::new(&conn, &globals, 24);
|
||||
|
||||
handle
|
||||
.insert_source(XDPEventSource::new(&common.background_executor), {
|
||||
.insert_source(XDPEventSource::new(&common.background_executor, None), {
|
||||
move |event, _, client| match event {
|
||||
XDPEvent::WindowAppearance(appearance) => {
|
||||
if let Some(client) = client.0.upgrade() {
|
||||
@@ -1054,21 +1053,8 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
.flatten()
|
||||
.expect("Failed to create keymap")
|
||||
};
|
||||
let table = {
|
||||
let locale = std::env::var_os("LC_CTYPE").unwrap_or(OsString::from("C"));
|
||||
xkb::compose::Table::new_from_locale(
|
||||
&xkb_context,
|
||||
&locale,
|
||||
xkb::compose::COMPILE_NO_FLAGS,
|
||||
)
|
||||
.log_err()
|
||||
.unwrap()
|
||||
};
|
||||
state.keymap_state = Some(xkb::State::new(&keymap));
|
||||
state.compose_state = Some(xkb::compose::State::new(
|
||||
&table,
|
||||
xkb::compose::STATE_NO_FLAGS,
|
||||
));
|
||||
state.compose_state = get_xkb_compose_state(&xkb_context);
|
||||
}
|
||||
wl_keyboard::Event::Enter {
|
||||
serial, surface, ..
|
||||
@@ -1148,6 +1134,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
compose.feed(keysym);
|
||||
match compose.status() {
|
||||
xkb::Status::Composing => {
|
||||
keystroke.ime_key = None;
|
||||
state.pre_edit_text =
|
||||
compose.utf8().or(Keystroke::underlying_dead_key(keysym));
|
||||
let pre_edit =
|
||||
@@ -1160,7 +1147,9 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
xkb::Status::Composed => {
|
||||
state.pre_edit_text.take();
|
||||
keystroke.ime_key = compose.utf8();
|
||||
keystroke.key = xkb::keysym_get_name(compose.keysym().unwrap());
|
||||
if let Some(keysym) = compose.keysym() {
|
||||
keystroke.key = xkb::keysym_get_name(keysym);
|
||||
}
|
||||
}
|
||||
xkb::Status::Cancelled => {
|
||||
let pre_edit = state.pre_edit_text.take();
|
||||
|
||||