Compare commits
4 Commits
window-dec
...
fix-9263
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04558cb563 | ||
|
|
da456c33a0 | ||
|
|
04e243f01e | ||
|
|
e7827d5069 |
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_path: target/release-notes.md
|
||||
body_file: 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' || env.RELEASE_CHANNEL == 'stable' }}
|
||||
if: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
|
||||
10
.mailmap
@@ -7,12 +7,10 @@
|
||||
# Reference: https://git-scm.com/docs/gitmailmap
|
||||
|
||||
# Keep these entries sorted alphabetically.
|
||||
# In Zed: `editor: sort lines case insensitive`
|
||||
# In Zed: `editor: sort lines case sensitive`
|
||||
|
||||
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>
|
||||
@@ -22,8 +20,6 @@ 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>
|
||||
@@ -58,16 +54,12 @@ 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,13 +19,6 @@
|
||||
"JavaScript": {
|
||||
"tab_size": 2,
|
||||
"formatter": "prettier"
|
||||
},
|
||||
"Rust": {
|
||||
"tasks": {
|
||||
"variables": {
|
||||
"RUST_DEFAULT_PACKAGE_RUN": "zed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"formatter": "auto",
|
||||
|
||||
158
Cargo.lock
generated
@@ -374,7 +374,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"assistant_slash_command",
|
||||
"async-watch",
|
||||
"breadcrumbs",
|
||||
"cargo_toml",
|
||||
"chrono",
|
||||
"client",
|
||||
@@ -383,15 +382,14 @@ dependencies = [
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"feature_flags",
|
||||
"file_icons",
|
||||
"fs",
|
||||
"futures 0.3.28",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"heed",
|
||||
"html_to_markdown 0.1.0",
|
||||
"html_to_markdown",
|
||||
"http 0.1.0",
|
||||
"indexed_docs",
|
||||
"indoc",
|
||||
"language",
|
||||
"log",
|
||||
@@ -407,6 +405,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rope",
|
||||
"rustdoc",
|
||||
"schemars",
|
||||
"search",
|
||||
"semantic_index",
|
||||
@@ -418,7 +417,6 @@ dependencies = [
|
||||
"strsim 0.11.1",
|
||||
"strum",
|
||||
"telemetry_events",
|
||||
"terminal",
|
||||
"terminal_view",
|
||||
"theme",
|
||||
"tiktoken-rs",
|
||||
@@ -2553,13 +2551,18 @@ 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",
|
||||
@@ -2572,6 +2575,7 @@ dependencies = [
|
||||
"picker",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"recent_projects",
|
||||
"release_channel",
|
||||
"rich_text",
|
||||
"rpc",
|
||||
@@ -2583,14 +2587,15 @@ dependencies = [
|
||||
"smallvec",
|
||||
"story",
|
||||
"theme",
|
||||
"theme_selector",
|
||||
"time",
|
||||
"time_format",
|
||||
"title_bar",
|
||||
"tree-sitter-markdown",
|
||||
"ui",
|
||||
"util",
|
||||
"vcs_menu",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3917,7 +3922,6 @@ dependencies = [
|
||||
"futures 0.3.28",
|
||||
"gpui",
|
||||
"http 0.1.0",
|
||||
"indexed_docs",
|
||||
"isahc",
|
||||
"language",
|
||||
"log",
|
||||
@@ -4889,7 +4893,6 @@ dependencies = [
|
||||
"log",
|
||||
"media",
|
||||
"metal",
|
||||
"mio 1.0.0",
|
||||
"num_cpus",
|
||||
"objc",
|
||||
"oo7",
|
||||
@@ -4916,7 +4919,6 @@ dependencies = [
|
||||
"taffy",
|
||||
"thiserror",
|
||||
"time",
|
||||
"unicode-segmentation",
|
||||
"usvg",
|
||||
"util",
|
||||
"uuid",
|
||||
@@ -5144,9 +5146,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
@@ -5240,18 +5242,6 @@ 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"
|
||||
@@ -5495,31 +5485,6 @@ 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"
|
||||
@@ -5660,7 +5625,7 @@ version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"hermit-abi 0.3.3",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
@@ -5691,7 +5656,7 @@ dependencies = [
|
||||
"fnv",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"mio 0.8.11",
|
||||
"mio",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"tempfile",
|
||||
@@ -6640,19 +6605,6 @@ 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"
|
||||
@@ -6891,7 +6843,7 @@ dependencies = [
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio 0.8.11",
|
||||
"mio",
|
||||
"walkdir",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
@@ -7071,7 +7023,7 @@ version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"hermit-abi 0.3.3",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -9059,6 +9011,31 @@ 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"
|
||||
@@ -11068,41 +11045,6 @@ 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"
|
||||
@@ -11112,7 +11054,7 @@ dependencies = [
|
||||
"backtrace",
|
||||
"bytes 1.5.0",
|
||||
"libc",
|
||||
"mio 0.8.11",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
@@ -13607,7 +13549,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.144.0"
|
||||
version = "0.142.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
@@ -13807,7 +13749,6 @@ 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",
|
||||
]
|
||||
|
||||
@@ -13855,7 +13796,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_prisma"
|
||||
version = "0.0.3"
|
||||
version = "0.0.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.4",
|
||||
]
|
||||
@@ -13896,13 +13837,6 @@ 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"
|
||||
@@ -13926,7 +13860,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_zig"
|
||||
version = "0.1.3"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.7",
|
||||
]
|
||||
|
||||
51
Cargo.toml
@@ -21,7 +21,6 @@ members = [
|
||||
"crates/command_palette_hooks",
|
||||
"crates/copilot",
|
||||
"crates/db",
|
||||
"crates/dev_server_projects",
|
||||
"crates/diagnostics",
|
||||
"crates/editor",
|
||||
"crates/extension",
|
||||
@@ -45,7 +44,6 @@ members = [
|
||||
"crates/html_to_markdown",
|
||||
"crates/http",
|
||||
"crates/image_viewer",
|
||||
"crates/indexed_docs",
|
||||
"crates/inline_completion_button",
|
||||
"crates/install_cli",
|
||||
"crates/journal",
|
||||
@@ -79,10 +77,14 @@ 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",
|
||||
@@ -93,20 +95,17 @@ 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",
|
||||
@@ -140,7 +139,6 @@ members = [
|
||||
"extensions/snippets",
|
||||
"extensions/svelte",
|
||||
"extensions/terraform",
|
||||
"extensions/test-extension",
|
||||
"extensions/toml",
|
||||
"extensions/uiua",
|
||||
"extensions/vue",
|
||||
@@ -159,8 +157,10 @@ 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,10 +195,9 @@ gpui_macros = { path = "crates/gpui_macros" }
|
||||
headless = { path = "crates/headless" }
|
||||
html_to_markdown = { path = "crates/html_to_markdown" }
|
||||
http = { path = "crates/http" }
|
||||
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" }
|
||||
image_viewer = { path = "crates/image_viewer" }
|
||||
inline_completion_button = { path = "crates/inline_completion_button" }
|
||||
journal = { path = "crates/journal" }
|
||||
language = { path = "crates/language" }
|
||||
language_selector = { path = "crates/language_selector" }
|
||||
@@ -224,16 +223,21 @@ 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" }
|
||||
@@ -241,23 +245,20 @@ 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" }
|
||||
@@ -265,13 +266,12 @@ 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"
|
||||
any_vec = "0.13"
|
||||
anyhow = "1.0.57"
|
||||
any_vec = "0.13"
|
||||
ashpd = "0.8.0"
|
||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||
async-dispatcher = { version = "0.1"}
|
||||
@@ -279,23 +279,20 @@ 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"
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
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,92 +0,0 @@
|
||||
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/zed-mono/zed-mono-extended.ttf
Normal file
BIN
assets/fonts/zed-mono/zed-mono-extendedbold.ttf
Normal file
BIN
assets/fonts/zed-mono/zed-mono-extendedbolditalic.ttf
Normal file
BIN
assets/fonts/zed-mono/zed-mono-extendeditalic.ttf
Normal file
BIN
assets/fonts/zed-sans/zed-sans-extended.ttf
Normal file
BIN
assets/fonts/zed-sans/zed-sans-extendedbold.ttf
Normal file
BIN
assets/fonts/zed-sans/zed-sans-extendedbolditalic.ttf
Normal file
BIN
assets/fonts/zed-sans/zed-sans-extendeditalic.ttf
Normal file
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 276 B |
@@ -94,7 +94,6 @@
|
||||
"lua": "lua",
|
||||
"m4a": "audio",
|
||||
"m4v": "video",
|
||||
"markdown": "document",
|
||||
"md": "document",
|
||||
"mdb": "storage",
|
||||
"mdf": "storage",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 329 B |
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 339 B |
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 296 B |
@@ -1,4 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 291 B |
@@ -1,3 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 161 B |
@@ -1,3 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 138 B |
@@ -1,4 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 228 B |
@@ -1,6 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 539 B |
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 301 B |
@@ -70,11 +70,6 @@
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
"f2": "project_panel::Rename",
|
||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"cmd-x": "project_panel::Cut",
|
||||
"cmd-c": "project_panel::Copy",
|
||||
"cmd-v": "project_panel::Paste",
|
||||
"ctrl-[": "project_panel::CollapseSelectedEntry",
|
||||
"ctrl-b": "project_panel::CollapseSelectedEntry",
|
||||
"alt-b": "project_panel::CollapseSelectedEntry",
|
||||
@@ -82,13 +77,5 @@
|
||||
"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,13 +55,11 @@
|
||||
"up": "editor::MoveUp",
|
||||
"ctrl-up": "editor::LineUp",
|
||||
"ctrl-down": "editor::LineDown",
|
||||
"pageup": "editor::MovePageUp",
|
||||
"alt-pageup": "editor::PageUp",
|
||||
"pageup": "editor::PageUp",
|
||||
"shift-pageup": "editor::SelectPageUp",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"down": "editor::MoveDown",
|
||||
"pagedown": "editor::MovePageDown",
|
||||
"alt-pagedown": "editor::PageDown",
|
||||
"pagedown": "editor::PageDown",
|
||||
"shift-pagedown": "editor::SelectPageDown",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"left": "editor::MoveLeft",
|
||||
@@ -155,7 +153,6 @@
|
||||
// }
|
||||
// ],
|
||||
"ctrl->": "assistant::QuoteSelection",
|
||||
"ctrl-<": "assistant::InsertIntoEditor",
|
||||
"ctrl-alt-e": "editor::SelectEnclosingSymbol"
|
||||
}
|
||||
},
|
||||
@@ -549,7 +546,6 @@
|
||||
"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",
|
||||
@@ -591,9 +587,8 @@
|
||||
"alt-ctrl-shift-c": "project_panel::CopyRelativePath",
|
||||
"f2": "project_panel::Rename",
|
||||
"enter": "project_panel::Rename",
|
||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"delete": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"backspace": "project_panel::Trash",
|
||||
"delete": "project_panel::Trash",
|
||||
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-ctrl-r": "project_panel::RevealInFinder",
|
||||
@@ -655,20 +650,13 @@
|
||||
"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"],
|
||||
"shift-pageup": "terminal::ScrollPageUp",
|
||||
"shift-pagedown": "terminal::ScrollPageDown",
|
||||
"shift-up": "terminal::ScrollLineUp",
|
||||
"shift-down": "terminal::ScrollLineDown",
|
||||
"shift-home": "terminal::ScrollToTop",
|
||||
"shift-end": "terminal::ScrollToBottom"
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -61,17 +61,13 @@
|
||||
"cmd-shift-z": "editor::Redo",
|
||||
"up": "editor::MoveUp",
|
||||
"ctrl-up": "editor::MoveToStartOfParagraph",
|
||||
"pageup": "editor::MovePageUp",
|
||||
"shift-pageup": "editor::SelectPageUp",
|
||||
"cmd-pageup": "editor::PageUp",
|
||||
"ctrl-pageup": "editor::LineUp",
|
||||
"pageup": "editor::PageUp",
|
||||
"shift-pageup": "editor::MovePageUp",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"down": "editor::MoveDown",
|
||||
"ctrl-down": "editor::MoveToEndOfParagraph",
|
||||
"pagedown": "editor::MovePageDown",
|
||||
"shift-pagedown": "editor::SelectPageDown",
|
||||
"cmd-pagedown": "editor::PageDown",
|
||||
"ctrl-pagedown": "editor::LineDown",
|
||||
"pagedown": "editor::PageDown",
|
||||
"shift-pagedown": "editor::MovePageDown",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"left": "editor::MoveLeft",
|
||||
"right": "editor::MoveRight",
|
||||
@@ -193,7 +189,6 @@
|
||||
}
|
||||
],
|
||||
"cmd->": "assistant::QuoteSelection",
|
||||
"cmd-<": "assistant::InsertIntoEditor",
|
||||
"cmd-alt-e": "editor::SelectEnclosingSymbol"
|
||||
}
|
||||
},
|
||||
@@ -239,7 +234,6 @@
|
||||
"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",
|
||||
@@ -611,7 +605,6 @@
|
||||
"left": "project_panel::CollapseSelectedEntry",
|
||||
"right": "project_panel::ExpandSelectedEntry",
|
||||
"cmd-n": "project_panel::NewFile",
|
||||
"cmd-d": "project_panel::Duplicate",
|
||||
"alt-cmd-n": "project_panel::NewDirectory",
|
||||
"cmd-x": "project_panel::Cut",
|
||||
"cmd-c": "project_panel::Copy",
|
||||
@@ -621,9 +614,8 @@
|
||||
"enter": "project_panel::Rename",
|
||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"delete": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
|
||||
"cmd-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-cmd-r": "project_panel::RevealInFinder",
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
@@ -690,7 +682,6 @@
|
||||
"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"],
|
||||
@@ -706,13 +697,7 @@
|
||||
"pagedown": ["terminal::SendKeystroke", "pagedown"],
|
||||
"escape": ["terminal::SendKeystroke", "escape"],
|
||||
"enter": ["terminal::SendKeystroke", "enter"],
|
||||
"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"
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -78,7 +78,6 @@
|
||||
"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"
|
||||
@@ -95,10 +94,6 @@
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
"enter": "project_panel::Open",
|
||||
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"delete": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"shift-f6": "project_panel::Rename"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"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",
|
||||
|
||||
@@ -87,15 +87,7 @@
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
|
||||
"cmd-d": "project_panel::Duplicate",
|
||||
"cmd-n": "project_panel::NewFolder",
|
||||
"return": "project_panel::Rename",
|
||||
"cmd-c": "project_panel::Copy",
|
||||
"cmd-v": "project_panel::Paste",
|
||||
"cmd-alt-c": "project_panel::CopyPath"
|
||||
}
|
||||
"bindings": {}
|
||||
},
|
||||
{
|
||||
"context": "Dock",
|
||||
|
||||
@@ -47,16 +47,19 @@
|
||||
"{": "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",
|
||||
{
|
||||
@@ -75,12 +78,8 @@
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"g shift-e": [
|
||||
"vim::PreviousWordEnd",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
|
||||
|
||||
"/": "vim::Search",
|
||||
"g /": "pane::DeploySearch",
|
||||
"?": [
|
||||
@@ -127,22 +126,8 @@
|
||||
}
|
||||
],
|
||||
"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",
|
||||
@@ -346,6 +331,7 @@
|
||||
"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",
|
||||
@@ -403,9 +389,6 @@
|
||||
"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
|
||||
@@ -693,8 +676,7 @@
|
||||
{
|
||||
"context": "EmptyPane || SharedScreen",
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
"g /": "pane::DeploySearch"
|
||||
":": "command_palette::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -722,14 +704,5 @@
|
||||
"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,15 +1,19 @@
|
||||
{
|
||||
// The name of the Zed theme to use for the UI.
|
||||
//
|
||||
// `mode` is one of:
|
||||
// The theme can also be set to follow system preferences:
|
||||
//
|
||||
// "theme": {
|
||||
// "mode": "system",
|
||||
// "light": "One Light",
|
||||
// "dark": "One Dark"
|
||||
// }
|
||||
//
|
||||
// Where `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": {
|
||||
"mode": "system",
|
||||
"light": "One Light",
|
||||
"dark": "One Dark"
|
||||
},
|
||||
"theme": "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:
|
||||
@@ -25,7 +29,7 @@
|
||||
"inline_completion_provider": "copilot"
|
||||
},
|
||||
// The name of a font to use for rendering text in the editor
|
||||
"buffer_font_family": "Zed Plex Mono",
|
||||
"buffer_font_family": "Zed Mono",
|
||||
// The OpenType features to enable for text in the editor.
|
||||
"buffer_font_features": {
|
||||
// Disable ligatures:
|
||||
@@ -38,17 +42,16 @@
|
||||
// Set the buffer's line height.
|
||||
// May take 3 values:
|
||||
// 1. Use a line height that's comfortable for reading (1.618)
|
||||
// "buffer_line_height": "comfortable"
|
||||
// "line_height": "comfortable"
|
||||
// 2. Use a standard line height, (1.3)
|
||||
// "buffer_line_height": "standard",
|
||||
// "line_height": "standard",
|
||||
// 3. Use a custom line height
|
||||
// "buffer_line_height": {
|
||||
// "line_height": {
|
||||
// "custom": 2
|
||||
// },
|
||||
"buffer_line_height": "comfortable",
|
||||
// The name of a font to use for rendering text in the UI
|
||||
// (On macOS) You can set this to ".SysmtemUIFont" to use the system font
|
||||
"ui_font_family": "Zed Plex Sans",
|
||||
"ui_font_family": ".SystemUIFont",
|
||||
// The OpenType features to enable for text in the UI
|
||||
"ui_font_features": {
|
||||
// Disable ligatures:
|
||||
@@ -162,11 +165,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:
|
||||
// "boundary"
|
||||
// "boundaries"
|
||||
// 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)
|
||||
@@ -307,8 +310,8 @@
|
||||
// when a corresponding project entry becomes active.
|
||||
// Gitignored entries are never auto revealed.
|
||||
"auto_reveal_entries": true,
|
||||
// Whether to fold directories automatically and show compact folders
|
||||
// (e.g. "a/b/c" ) when a directory has only one subdirectory inside.
|
||||
/// Whether to fold directories automatically
|
||||
/// when a directory has only one directory inside.
|
||||
"auto_fold_dirs": false,
|
||||
/// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
@@ -666,17 +669,13 @@
|
||||
// "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 Plex Mono",
|
||||
// "font_family": "Zed 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.
|
||||
@@ -689,9 +688,7 @@
|
||||
// "TOML": ["Embargo.lock"]
|
||||
// }
|
||||
//
|
||||
"file_types": {
|
||||
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json"]
|
||||
},
|
||||
"file_types": {},
|
||||
// The extensions that Zed should automatically install on startup.
|
||||
//
|
||||
// If you don't want any of these extensions, add this field to your settings
|
||||
@@ -761,11 +758,6 @@
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"JSONC": {
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"Markdown": {
|
||||
"format_on_save": "off",
|
||||
"prettier": {
|
||||
|
||||
@@ -8,10 +8,5 @@
|
||||
// from the command palette or from `Zed` application menu.
|
||||
{
|
||||
"ui_font_size": 16,
|
||||
"buffer_font_size": 16,
|
||||
"theme": {
|
||||
"mode": "system",
|
||||
"light": "One Light",
|
||||
"dark": "One Dark"
|
||||
}
|
||||
"buffer_font_size": 16
|
||||
}
|
||||
|
||||
@@ -52,13 +52,4 @@ 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,18 +13,17 @@ path = "src/assistant.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anthropic = { workspace = true, features = ["schemars"] }
|
||||
anyhow.workspace = true
|
||||
anthropic = { workspace = true, features = ["schemars"] }
|
||||
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
|
||||
feature_flags.workspace = true
|
||||
file_icons.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
@@ -32,7 +31,6 @@ 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
|
||||
@@ -46,6 +44,7 @@ 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
|
||||
@@ -57,7 +56,6 @@ 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,7 +9,6 @@ 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};
|
||||
@@ -20,16 +19,15 @@ 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, docs_command, fetch_command,
|
||||
file_command, now_command, project_command, prompt_command, search_command, tabs_command,
|
||||
term_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,
|
||||
};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
@@ -44,13 +42,11 @@ actions!(
|
||||
Split,
|
||||
CycleMessageRole,
|
||||
QuoteSelection,
|
||||
InsertIntoEditor,
|
||||
ToggleFocus,
|
||||
ResetKey,
|
||||
InlineAssist,
|
||||
InsertActivePrompt,
|
||||
DeployHistory,
|
||||
DeployPromptLibrary,
|
||||
ToggleHistory,
|
||||
ApplyEdit,
|
||||
ConfirmCommand,
|
||||
ToggleModelSelector
|
||||
@@ -293,8 +289,7 @@ 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);
|
||||
terminal_inline_assistant::init(fs.clone(), client.telemetry().clone(), cx);
|
||||
IndexedDocsRegistry::init_global(cx);
|
||||
RustdocStore::init_global(cx);
|
||||
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(Assistant::NAMESPACE);
|
||||
@@ -324,8 +319,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::DiagnosticsSlashCommand, true);
|
||||
slash_command_registry.register_command(docs_command::DocsSlashCommand, true);
|
||||
slash_command_registry.register_command(diagnostics_command::DiagnosticsCommand, true);
|
||||
slash_command_registry.register_command(rustdoc_command::RustdocSlashCommand, false);
|
||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,6 @@ pub enum AssistantProvider {
|
||||
model: OpenAiModel,
|
||||
api_url: String,
|
||||
low_speed_timeout_in_seconds: Option<u64>,
|
||||
available_models: Vec<OpenAiModel>,
|
||||
},
|
||||
Anthropic {
|
||||
model: AnthropicModel,
|
||||
@@ -189,7 +188,6 @@ impl Default for AssistantProvider {
|
||||
model: OpenAiModel::default(),
|
||||
api_url: open_ai::OPEN_AI_API_URL.into(),
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,7 +202,6 @@ pub enum AssistantProviderContent {
|
||||
default_model: Option<OpenAiModel>,
|
||||
api_url: Option<String>,
|
||||
low_speed_timeout_in_seconds: Option<u64>,
|
||||
available_models: Option<Vec<OpenAiModel>>,
|
||||
},
|
||||
#[serde(rename = "anthropic")]
|
||||
Anthropic {
|
||||
@@ -275,7 +272,6 @@ impl AssistantSettingsContent {
|
||||
default_model: settings.default_open_ai_model.clone(),
|
||||
api_url: Some(open_ai_api_url.clone()),
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Some(Default::default()),
|
||||
})
|
||||
} else {
|
||||
settings.default_open_ai_model.clone().map(|open_ai_model| {
|
||||
@@ -283,7 +279,6 @@ impl AssistantSettingsContent {
|
||||
default_model: Some(open_ai_model),
|
||||
api_url: None,
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Some(Default::default()),
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -331,14 +326,6 @@ impl AssistantSettingsContent {
|
||||
*model = Some(new_model);
|
||||
}
|
||||
}
|
||||
Some(AssistantProviderContent::Ollama {
|
||||
default_model: model,
|
||||
..
|
||||
}) => {
|
||||
if let LanguageModel::Ollama(new_model) = new_model {
|
||||
*model = Some(new_model);
|
||||
}
|
||||
}
|
||||
provider => match new_model {
|
||||
LanguageModel::Cloud(model) => {
|
||||
*provider = Some(AssistantProviderContent::ZedDotDev {
|
||||
@@ -350,7 +337,6 @@ impl AssistantSettingsContent {
|
||||
default_model: Some(model),
|
||||
api_url: None,
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Some(Default::default()),
|
||||
})
|
||||
}
|
||||
LanguageModel::Anthropic(model) => {
|
||||
@@ -495,18 +481,15 @@ impl Settings for AssistantSettings {
|
||||
model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
},
|
||||
AssistantProviderContent::OpenAi {
|
||||
default_model: model_override,
|
||||
api_url: api_url_override,
|
||||
low_speed_timeout_in_seconds: low_speed_timeout_in_seconds_override,
|
||||
available_models: available_models_override,
|
||||
},
|
||||
) => {
|
||||
merge(model, model_override);
|
||||
merge(api_url, api_url_override);
|
||||
merge(available_models, available_models_override);
|
||||
if let Some(low_speed_timeout_in_seconds_override) =
|
||||
low_speed_timeout_in_seconds_override
|
||||
{
|
||||
@@ -567,12 +550,10 @@ impl Settings for AssistantSettings {
|
||||
default_model: model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
} => AssistantProvider::OpenAi {
|
||||
model: model.unwrap_or_default(),
|
||||
api_url: api_url.unwrap_or_else(|| open_ai::OPEN_AI_API_URL.into()),
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models: available_models.unwrap_or_default(),
|
||||
},
|
||||
AssistantProviderContent::Anthropic {
|
||||
default_model: model,
|
||||
@@ -629,7 +610,6 @@ mod tests {
|
||||
model: OpenAiModel::FourOmni,
|
||||
api_url: open_ai::OPEN_AI_API_URL.into(),
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Default::default(),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -652,7 +632,6 @@ mod tests {
|
||||
model: OpenAiModel::FourOmni,
|
||||
api_url: "test-url".into(),
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Default::default(),
|
||||
}
|
||||
);
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
@@ -673,7 +652,6 @@ mod tests {
|
||||
model: OpenAiModel::Four,
|
||||
api_url: open_ai::OPEN_AI_API_URL.into(),
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Default::default(),
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -24,20 +24,6 @@ use settings::{Settings, SettingsStore};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Choose which model to use for openai provider.
|
||||
/// If the model is not available, try to use the first available model, or fallback to the original model.
|
||||
fn choose_openai_model(
|
||||
model: &::open_ai::Model,
|
||||
available_models: &[::open_ai::Model],
|
||||
) -> ::open_ai::Model {
|
||||
available_models
|
||||
.iter()
|
||||
.find(|&m| m == model)
|
||||
.or_else(|| available_models.first())
|
||||
.unwrap_or_else(|| model)
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
let mut settings_version = 0;
|
||||
let provider = match &AssistantSettings::get_global(cx).provider {
|
||||
@@ -48,9 +34,8 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
} => CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
|
||||
choose_openai_model(model, available_models),
|
||||
model.clone(),
|
||||
api_url.clone(),
|
||||
client.http_client(),
|
||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
||||
@@ -92,11 +77,10 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
},
|
||||
) => {
|
||||
provider.update(
|
||||
choose_openai_model(model, available_models),
|
||||
model.clone(),
|
||||
api_url.clone(),
|
||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
||||
settings_version,
|
||||
@@ -152,11 +136,10 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
},
|
||||
) => {
|
||||
*provider = CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
|
||||
choose_openai_model(model, available_models),
|
||||
model.clone(),
|
||||
api_url.clone(),
|
||||
client.http_client(),
|
||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
||||
@@ -218,10 +201,10 @@ impl CompletionProvider {
|
||||
cx.global::<Self>()
|
||||
}
|
||||
|
||||
pub fn available_models(&self, cx: &AppContext) -> Vec<LanguageModel> {
|
||||
pub fn available_models(&self) -> Vec<LanguageModel> {
|
||||
match self {
|
||||
CompletionProvider::OpenAi(provider) => provider
|
||||
.available_models(cx)
|
||||
.available_models()
|
||||
.map(LanguageModel::OpenAi)
|
||||
.collect(),
|
||||
CompletionProvider::Anthropic(provider) => provider
|
||||
|
||||
@@ -236,7 +236,7 @@ pub fn preprocess_anthropic_request(request: &mut LanguageModelRequest) {
|
||||
}
|
||||
|
||||
if !system_message.is_empty() {
|
||||
new_messages.insert(
|
||||
request.messages.insert(
|
||||
0,
|
||||
LanguageModelRequestMessage {
|
||||
role: Role::System,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::assistant_settings::CloudModel;
|
||||
use crate::assistant_settings::{AssistantProvider, AssistantSettings};
|
||||
use crate::{
|
||||
assistant_settings::OpenAiModel, CompletionProvider, LanguageModel, LanguageModelRequest, Role,
|
||||
};
|
||||
@@ -57,26 +56,8 @@ impl OpenAiCompletionProvider {
|
||||
self.settings_version = settings_version;
|
||||
}
|
||||
|
||||
pub fn available_models(&self, cx: &AppContext) -> impl Iterator<Item = OpenAiModel> {
|
||||
if let AssistantProvider::OpenAi {
|
||||
available_models, ..
|
||||
} = &AssistantSettings::get_global(cx).provider
|
||||
{
|
||||
if !available_models.is_empty() {
|
||||
// available_models is set, just return it
|
||||
return available_models.clone().into_iter();
|
||||
}
|
||||
}
|
||||
let available_models = if matches!(self.model, OpenAiModel::Custom { .. }) {
|
||||
// available_models is not set but the default model is set to custom, only show custom
|
||||
vec![self.model.clone()]
|
||||
} else {
|
||||
// default case, use all models except custom
|
||||
OpenAiModel::iter()
|
||||
.filter(|model| !matches!(model, OpenAiModel::Custom { .. }))
|
||||
.collect()
|
||||
};
|
||||
available_models.into_iter()
|
||||
pub fn available_models(&self) -> impl Iterator<Item = OpenAiModel> {
|
||||
OpenAiModel::iter()
|
||||
}
|
||||
|
||||
pub fn settings_version(&self) -> usize {
|
||||
@@ -232,8 +213,7 @@ pub fn count_open_ai_tokens(
|
||||
| LanguageModel::Cloud(CloudModel::Claude3_5Sonnet)
|
||||
| LanguageModel::Cloud(CloudModel::Claude3Opus)
|
||||
| LanguageModel::Cloud(CloudModel::Claude3Sonnet)
|
||||
| LanguageModel::Cloud(CloudModel::Claude3Haiku)
|
||||
| LanguageModel::OpenAi(OpenAiModel::Custom { .. }) => {
|
||||
| LanguageModel::Cloud(CloudModel::Claude3Haiku) => {
|
||||
// Tiktoken doesn't yet support these models, so we manually use the
|
||||
// same tokenizer as GPT-4.
|
||||
tiktoken_rs::num_tokens_from_messages("gpt-4", &messages)
|
||||
|
||||
@@ -18,9 +18,9 @@ use editor::{
|
||||
use fs::Fs;
|
||||
use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
|
||||
use gpui::{
|
||||
point, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, Global, HighlightStyle,
|
||||
Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView,
|
||||
WhiteSpace, WindowContext,
|
||||
point, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Global,
|
||||
HighlightStyle, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View,
|
||||
ViewContext, WeakView, WhiteSpace, WindowContext,
|
||||
};
|
||||
use language::{Buffer, Point, Selection, TransactionId};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
@@ -1298,8 +1298,7 @@ impl Render for PromptEditor {
|
||||
PopoverMenu::new("model-switcher")
|
||||
.menu(move |cx| {
|
||||
ContextMenu::build(cx, |mut menu, cx| {
|
||||
for model in CompletionProvider::global(cx).available_models(cx)
|
||||
{
|
||||
for model in CompletionProvider::global(cx).available_models() {
|
||||
menu = menu.custom_entry(
|
||||
{
|
||||
let model = model.clone();
|
||||
@@ -1730,7 +1729,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: settings.ui_font.weight,
|
||||
font_weight: FontWeight::NORMAL,
|
||||
font_style: FontStyle::Normal,
|
||||
line_height: relative(1.3),
|
||||
background_color: None,
|
||||
|
||||
@@ -23,7 +23,7 @@ impl RenderOnce for ModelSelector {
|
||||
.with_handle(self.handle)
|
||||
.menu(move |cx| {
|
||||
ContextMenu::build(cx, |mut menu, cx| {
|
||||
for model in CompletionProvider::global(cx).available_models(cx) {
|
||||
for model in CompletionProvider::global(cx).available_models() {
|
||||
menu = menu.custom_entry(
|
||||
{
|
||||
let model = model.clone();
|
||||
@@ -79,6 +79,6 @@ impl RenderOnce for ModelSelector {
|
||||
Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
|
||||
}),
|
||||
)
|
||||
.attach(gpui::AnchorCorner::BottomLeft)
|
||||
.anchor(gpui::AnchorCorner::BottomRight)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::{HashMap, HashSet};
|
||||
use collections::HashMap;
|
||||
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, Tooltip, ViewContext, VisualContext,
|
||||
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
@@ -42,12 +42,7 @@ use workspace::Workspace;
|
||||
|
||||
actions!(
|
||||
prompt_library,
|
||||
[
|
||||
NewPrompt,
|
||||
DeletePrompt,
|
||||
DuplicatePrompt,
|
||||
ToggleDefaultPrompt
|
||||
]
|
||||
[NewPrompt, DeletePrompt, ToggleDefaultPrompt]
|
||||
);
|
||||
|
||||
/// Init starts loading the PromptStore in the background and assigns
|
||||
@@ -416,12 +411,6 @@ 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);
|
||||
@@ -575,47 +564,6 @@ 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]
|
||||
@@ -773,7 +721,7 @@ impl PromptLibrary {
|
||||
.child(
|
||||
h_flex()
|
||||
.p(Spacing::Small.rems(cx))
|
||||
.h_9()
|
||||
.h(TitleBar::height(cx))
|
||||
.w_full()
|
||||
.flex_none()
|
||||
.justify_end()
|
||||
@@ -893,42 +841,6 @@ impl PromptLibrary {
|
||||
h_flex()
|
||||
.h_full()
|
||||
.gap(Spacing::XXLarge.rems(cx))
|
||||
.children(prompt_editor.token_count.map(
|
||||
|token_count| {
|
||||
let token_count: SharedString =
|
||||
token_count.to_string().into();
|
||||
let label_token_count: SharedString =
|
||||
token_count.to_string().into();
|
||||
|
||||
h_flex()
|
||||
.id("token_count")
|
||||
.tooltip(move |cx| {
|
||||
let token_count =
|
||||
token_count.clone();
|
||||
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"{} tokens",
|
||||
token_count.clone()
|
||||
),
|
||||
None,
|
||||
format!(
|
||||
"Model: {}",
|
||||
current_model
|
||||
.display_name()
|
||||
),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.child(
|
||||
Label::new(format!(
|
||||
"{} tokens",
|
||||
label_token_count.clone()
|
||||
))
|
||||
.color(Color::Muted),
|
||||
)
|
||||
},
|
||||
))
|
||||
.child(
|
||||
IconButton::new(
|
||||
"delete-prompt",
|
||||
@@ -949,28 +861,24 @@ 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",
|
||||
&DuplicatePrompt,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
DuplicatePrompt,
|
||||
));
|
||||
}),
|
||||
)
|
||||
// .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(
|
||||
"toggle-default-prompt",
|
||||
@@ -1012,7 +920,38 @@ impl PromptLibrary {
|
||||
.on_action(cx.listener(Self::move_up_from_body))
|
||||
.flex_grow()
|
||||
.h_full()
|
||||
.child(prompt_editor.body_editor.clone()),
|
||||
.child(prompt_editor.body_editor.clone())
|
||||
.children(prompt_editor.token_count.map(|token_count| {
|
||||
let token_count: SharedString = token_count.to_string().into();
|
||||
let label_token_count: SharedString =
|
||||
token_count.to_string().into();
|
||||
|
||||
h_flex()
|
||||
.id("token_count")
|
||||
.absolute()
|
||||
.bottom_1()
|
||||
.right_4()
|
||||
.flex_initial()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.tooltip(move |cx| {
|
||||
let token_count = token_count.clone();
|
||||
|
||||
Tooltip::with_meta(
|
||||
format!("{} tokens", token_count.clone()),
|
||||
None,
|
||||
format!("Model: {}", current_model.display_name()),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.child(
|
||||
Label::new(format!(
|
||||
"{} tokens",
|
||||
label_token_count.clone()
|
||||
))
|
||||
.color(Color::Muted),
|
||||
)
|
||||
})),
|
||||
),
|
||||
)
|
||||
}))
|
||||
@@ -1029,7 +968,6 @@ 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,130 +6,118 @@ 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,
|
||||
"Here's a file of text that I'm going to ask you to make an edit to."
|
||||
)?;
|
||||
"text"
|
||||
writeln!(prompt, "You are an expert engineer.")?;
|
||||
"Text"
|
||||
}
|
||||
Some(language_name) => {
|
||||
writeln!(prompt, "You are an expert {language_name} engineer.")?;
|
||||
writeln!(
|
||||
prompt,
|
||||
"Here's a file of {language_name} that I'm going to ask you to make an edit to."
|
||||
"Your answer MUST always and only be valid {}.",
|
||||
language_name
|
||||
)?;
|
||||
"code"
|
||||
"Code"
|
||||
}
|
||||
};
|
||||
|
||||
const MAX_CTX: usize = 50000;
|
||||
let mut is_truncated = false;
|
||||
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:"
|
||||
)?;
|
||||
if range.is_empty() {
|
||||
prompt.push_str("The point you'll need to insert at is marked with <insert_here></insert_here>.\n\n<document>");
|
||||
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();
|
||||
} else {
|
||||
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");
|
||||
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');
|
||||
for chunk in buffer.text_for_range(range.clone()) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
if !prompt.ends_with('\n') {
|
||||
prompt.push('\n');
|
||||
}
|
||||
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!(
|
||||
writeln!(prompt, "```")?;
|
||||
prompt.push('\n');
|
||||
|
||||
writeln!(
|
||||
prompt,
|
||||
"Start at the indentation level in the original file in the rewritten {content_type}. "
|
||||
"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("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```");
|
||||
}
|
||||
|
||||
writeln!(prompt, "Never make remarks about the output.").unwrap();
|
||||
writeln!(
|
||||
prompt,
|
||||
"Do not return anything else, except the generated {content_type}."
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
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 DiagnosticsSlashCommand;
|
||||
pub(crate) struct DiagnosticsCommand;
|
||||
|
||||
impl DiagnosticsSlashCommand {
|
||||
impl DiagnosticsCommand {
|
||||
fn search_paths(
|
||||
&self,
|
||||
query: String,
|
||||
@@ -81,7 +81,7 @@ impl DiagnosticsSlashCommand {
|
||||
}
|
||||
}
|
||||
|
||||
impl SlashCommand for DiagnosticsSlashCommand {
|
||||
impl SlashCommand for DiagnosticsCommand {
|
||||
fn name(&self) -> String {
|
||||
"diagnostics".into()
|
||||
}
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
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::Local;
|
||||
use chrono::{DateTime, Local};
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use language::LspAdapterDelegate;
|
||||
use ui::prelude::*;
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
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_rfc2822());
|
||||
let text = format!("Today is {now}.", now = now.to_rfc3339());
|
||||
let range = 0..text.len();
|
||||
|
||||
Task::ready(Ok(SlashCommandOutput {
|
||||
@@ -54,9 +54,29 @@ impl SlashCommand for NowSlashCommand {
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
icon: IconName::CountdownTimer,
|
||||
label: now.to_rfc2822().into(),
|
||||
label: now.to_rfc3339().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))
|
||||
}
|
||||
}
|
||||
|
||||
238
crates/assistant/src/slash_command/rustdoc_command.rs
Normal file
@@ -0,0 +1,238 @@
|
||||
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,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -196,24 +196,23 @@ 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"))?;
|
||||
|
||||
// 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(", "))
|
||||
})?
|
||||
};
|
||||
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),
|
||||
},
|
||||
}
|
||||
}?;
|
||||
|
||||
Ok(App(path))
|
||||
}
|
||||
|
||||
@@ -202,10 +202,6 @@ 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()),
|
||||
@@ -615,7 +611,6 @@ impl Telemetry {
|
||||
|
||||
let request_body = EventRequestBody {
|
||||
installation_id: state.installation_id.as_deref().map(Into::into),
|
||||
metrics_id: state.metrics_id.as_deref().map(Into::into),
|
||||
session_id: state.session_id.clone(),
|
||||
is_staff: state.is_staff,
|
||||
app_version: state.app_version.clone(),
|
||||
|
||||
@@ -87,27 +87,51 @@ impl Global {
|
||||
}
|
||||
|
||||
pub fn observed_any(&self, other: &Self) -> bool {
|
||||
self.0
|
||||
.iter()
|
||||
.zip(other.0.iter())
|
||||
.any(|(left, right)| *right > 0 && left >= right)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn observed_all(&self, other: &Self) -> bool {
|
||||
let mut lhs = self.0.iter();
|
||||
let mut rhs = other.0.iter();
|
||||
self.0.iter().all(|left| match rhs.next() {
|
||||
Some(right) => left >= right,
|
||||
None => true,
|
||||
}) && rhs.next().is_none()
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn changed_since(&self, other: &Self) -> bool {
|
||||
self.0.len() > other.0.len()
|
||||
|| self
|
||||
.0
|
||||
.iter()
|
||||
.zip(other.0.iter())
|
||||
.any(|(left, right)| left > right)
|
||||
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
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = Lamport> + '_ {
|
||||
|
||||
@@ -664,7 +664,6 @@ where
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct EditorEventRow {
|
||||
installation_id: String,
|
||||
metrics_id: String,
|
||||
operation: String,
|
||||
app_version: String,
|
||||
file_extension: String,
|
||||
@@ -714,7 +713,6 @@ impl EditorEventRow {
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
architecture: body.architecture.clone(),
|
||||
installation_id: body.installation_id.clone().unwrap_or_default(),
|
||||
metrics_id: body.metrics_id.clone().unwrap_or_default(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
|
||||
@@ -36,6 +36,8 @@ pub fn router() -> Router {
|
||||
struct GetExtensionsParams {
|
||||
filter: Option<String>,
|
||||
#[serde(default)]
|
||||
ids: Option<String>,
|
||||
#[serde(default)]
|
||||
max_schema_version: i32,
|
||||
}
|
||||
|
||||
@@ -43,15 +45,18 @@ async fn get_extensions(
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
Query(params): Query<GetExtensionsParams>,
|
||||
) -> Result<Json<GetExtensionsResponse>> {
|
||||
let extensions = app
|
||||
.db
|
||||
.get_extensions(params.filter.as_deref(), params.max_schema_version, 500)
|
||||
.await?;
|
||||
let extension_ids = params
|
||||
.ids
|
||||
.as_ref()
|
||||
.map(|s| s.split(',').map(|s| s.trim()).collect::<Vec<_>>());
|
||||
|
||||
if let Some(query) = params.filter.as_deref() {
|
||||
let count = extensions.len();
|
||||
tracing::info!(query, count, "extension_search")
|
||||
}
|
||||
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?
|
||||
};
|
||||
|
||||
Ok(Json(GetExtensionsResponse { data: extensions }))
|
||||
}
|
||||
|
||||
@@ -4462,7 +4462,6 @@ 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,13 +30,17 @@ 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
|
||||
@@ -47,6 +51,8 @@ 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
|
||||
@@ -58,13 +64,14 @@ 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,7 +2,10 @@ mod channel_modal;
|
||||
mod contact_finder;
|
||||
|
||||
use self::channel_modal::ChannelModal;
|
||||
use crate::{channel_view::ChannelView, chat_panel::ChatPanel, CollaborationPanelSettings};
|
||||
use crate::{
|
||||
channel_view::ChannelView, chat_panel::ChatPanel, face_pile::FacePile,
|
||||
CollaborationPanelSettings,
|
||||
};
|
||||
use call::ActiveCall;
|
||||
use channel::{Channel, ChannelEvent, ChannelStore};
|
||||
use client::{ChannelId, Client, Contact, ProjectId, User, UserStore};
|
||||
@@ -31,8 +34,7 @@ use std::{mem, sync::Arc};
|
||||
use theme::{ActiveTheme, ThemeSettings};
|
||||
use ui::{
|
||||
prelude::*, tooltip_container, Avatar, AvatarAvailabilityIndicator, Button, Color, ContextMenu,
|
||||
Facepile, Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem,
|
||||
Tooltip,
|
||||
Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem, Tooltip,
|
||||
};
|
||||
use util::{maybe, ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
@@ -2540,7 +2542,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,28 +1,21 @@
|
||||
mod call_controls;
|
||||
mod collab;
|
||||
mod platforms;
|
||||
mod window_controls;
|
||||
|
||||
use crate::platforms::{platform_linux, platform_mac, platform_windows};
|
||||
use crate::face_pile::FacePile;
|
||||
use auto_update::AutoUpdateStatus;
|
||||
use call::{ActiveCall, ParticipantLocation};
|
||||
use client::{Client, UserStore};
|
||||
use collab::render_color_ribbon;
|
||||
use call::{ActiveCall, ParticipantLocation, Room};
|
||||
use client::{proto::PeerId, Client, User, UserStore};
|
||||
use gpui::{
|
||||
actions, div, px, Action, AnyElement, AppContext, Decorations, Element, InteractiveElement,
|
||||
Interactivity, IntoElement, Model, MouseButton, ParentElement, Render, Stateful,
|
||||
actions, canvas, div, point, px, Action, AnyElement, AppContext, Element, Hsla,
|
||||
InteractiveElement, IntoElement, Model, ParentElement, Path, Render,
|
||||
StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use project::{Project, RepositoryEntry};
|
||||
use recent_projects::RecentProjects;
|
||||
use rpc::proto::DevServerStatus;
|
||||
use rpc::proto::{self, DevServerStatus};
|
||||
use settings::Settings;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
use theme::{ActiveTheme, ThemeSettings};
|
||||
use ui::{
|
||||
h_flex, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconButton,
|
||||
IconName, Indicator, PopoverMenu, TintColor, Tooltip,
|
||||
h_flex, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, ButtonStyle,
|
||||
ContextMenu, Icon, IconButton, IconName, Indicator, PopoverMenu, TintColor, TitleBar, Tooltip,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu};
|
||||
@@ -44,389 +37,333 @@ actions!(
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(|workspace: &mut Workspace, cx| {
|
||||
let item = cx.new_view(|cx| TitleBar::new("title-bar", workspace, cx));
|
||||
workspace.set_titlebar_item(item.into(), cx)
|
||||
let titlebar_item = cx.new_view(|cx| CollabTitlebarItem::new(workspace, cx));
|
||||
workspace.set_titlebar_item(titlebar_item.into(), cx)
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub struct TitleBar {
|
||||
platform_style: PlatformStyle,
|
||||
content: Stateful<Div>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
pub struct CollabTitlebarItem {
|
||||
project: Model<Project>,
|
||||
user_store: Model<UserStore>,
|
||||
client: Arc<Client>,
|
||||
workspace: WeakView<Workspace>,
|
||||
should_move: bool,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl Render for TitleBar {
|
||||
impl Render for CollabTitlebarItem {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let room = ActiveCall::global(cx).read(cx).room().cloned();
|
||||
let current_user = self.user_store.read(cx).current_user();
|
||||
let client = self.client.clone();
|
||||
let project_id = self.project.read(cx).remote_id();
|
||||
let workspace = self.workspace.upgrade();
|
||||
let close_action = Box::new(workspace::CloseWindow);
|
||||
|
||||
let platform_supported = cfg!(target_os = "macos");
|
||||
|
||||
let height = Self::height(cx);
|
||||
let supported_controls = cx.window_controls();
|
||||
let decorations = cx.window_decorations();
|
||||
|
||||
h_flex()
|
||||
.id("titlebar")
|
||||
.w_full()
|
||||
.pt(Self::top_padding(cx))
|
||||
.h(height + Self::top_padding(cx))
|
||||
.map(|this| {
|
||||
if cx.is_fullscreen() {
|
||||
this.pl_2()
|
||||
} else if self.platform_style == PlatformStyle::Mac {
|
||||
this.pl(px(platform_mac::TRAFFIC_LIGHT_PADDING))
|
||||
} else {
|
||||
this.pl_2()
|
||||
}
|
||||
TitleBar::new("collab-titlebar", Box::new(workspace::CloseWindow))
|
||||
// note: on windows titlebar behaviour is handled by the platform implementation
|
||||
.when(cfg!(not(windows)), |this| {
|
||||
this.on_click(|event, cx| {
|
||||
if event.up.click_count == 2 {
|
||||
cx.zoom_window();
|
||||
}
|
||||
})
|
||||
})
|
||||
.map(|el| {
|
||||
match decorations {
|
||||
Decorations::Server => el,
|
||||
Decorations::Client { tiling, .. } => el
|
||||
.when(!(tiling.top || tiling.right), |el| {
|
||||
el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
|
||||
})
|
||||
.when(!(tiling.top || tiling.left), |el| el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING))
|
||||
}
|
||||
})
|
||||
.bg(cx.theme().colors().title_bar_background)
|
||||
.content_stretch()
|
||||
// left side
|
||||
.child(
|
||||
div()
|
||||
.id("titlebar-content")
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_between()
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.children(self.render_application_menu(cx))
|
||||
.children(self.render_project_host(cx))
|
||||
.child(self.render_project_name(cx))
|
||||
.children(self.render_project_branch(cx))
|
||||
.on_mouse_move(|_, cx| cx.stop_propagation()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.id("collaborator-list")
|
||||
.w_full()
|
||||
// note: on windows titlebar behaviour is handled by the platform implementation
|
||||
.when(cfg!(not(windows)), |this| {
|
||||
this.on_click(|event, cx| {
|
||||
if event.up.click_count == 2 {
|
||||
cx.zoom_window();
|
||||
}
|
||||
})
|
||||
})
|
||||
// left side
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.children(self.render_application_menu(cx))
|
||||
.children(self.render_project_host(cx))
|
||||
.child(self.render_project_name(cx))
|
||||
.children(self.render_project_branch(cx))
|
||||
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.id("collaborator-list")
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.overflow_x_scroll()
|
||||
.when_some(
|
||||
current_user.clone().zip(client.peer_id()).zip(room.clone()),
|
||||
|this, ((current_user, peer_id), room)| {
|
||||
let player_colors = cx.theme().players();
|
||||
let room = room.read(cx);
|
||||
let mut remote_participants =
|
||||
room.remote_participants().values().collect::<Vec<_>>();
|
||||
remote_participants.sort_by_key(|p| p.participant_index.0);
|
||||
.gap_1()
|
||||
.overflow_x_scroll()
|
||||
.when_some(
|
||||
current_user.clone().zip(client.peer_id()).zip(room.clone()),
|
||||
|this, ((current_user, peer_id), room)| {
|
||||
let player_colors = cx.theme().players();
|
||||
let room = room.read(cx);
|
||||
let mut remote_participants =
|
||||
room.remote_participants().values().collect::<Vec<_>>();
|
||||
remote_participants.sort_by_key(|p| p.participant_index.0);
|
||||
|
||||
let current_user_face_pile = self.render_collaborator(
|
||||
¤t_user,
|
||||
peer_id,
|
||||
true,
|
||||
room.is_speaking(),
|
||||
room.is_muted(),
|
||||
None,
|
||||
&room,
|
||||
project_id,
|
||||
¤t_user,
|
||||
cx,
|
||||
);
|
||||
let current_user_face_pile = self.render_collaborator(
|
||||
¤t_user,
|
||||
peer_id,
|
||||
true,
|
||||
room.is_speaking(),
|
||||
room.is_muted(),
|
||||
None,
|
||||
&room,
|
||||
project_id,
|
||||
¤t_user,
|
||||
cx,
|
||||
);
|
||||
|
||||
this.children(current_user_face_pile.map(|face_pile| {
|
||||
v_flex()
|
||||
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
|
||||
.child(face_pile)
|
||||
.child(render_color_ribbon(player_colors.local().cursor))
|
||||
}))
|
||||
.children(
|
||||
remote_participants.iter().filter_map(|collaborator| {
|
||||
let player_color = player_colors
|
||||
.color_for_participant(collaborator.participant_index.0);
|
||||
let is_following = workspace
|
||||
.as_ref()?
|
||||
.read(cx)
|
||||
.is_being_followed(collaborator.peer_id);
|
||||
let is_present = project_id.map_or(false, |project_id| {
|
||||
collaborator.location
|
||||
== ParticipantLocation::SharedProject { project_id }
|
||||
});
|
||||
this.children(current_user_face_pile.map(|face_pile| {
|
||||
v_flex()
|
||||
.on_mouse_move(|_, cx| cx.stop_propagation())
|
||||
.child(face_pile)
|
||||
.child(render_color_ribbon(player_colors.local().cursor))
|
||||
}))
|
||||
.children(
|
||||
remote_participants.iter().filter_map(|collaborator| {
|
||||
let player_color = player_colors
|
||||
.color_for_participant(collaborator.participant_index.0);
|
||||
let is_following = workspace
|
||||
.as_ref()?
|
||||
.read(cx)
|
||||
.is_being_followed(collaborator.peer_id);
|
||||
let is_present = project_id.map_or(false, |project_id| {
|
||||
collaborator.location
|
||||
== ParticipantLocation::SharedProject { project_id }
|
||||
});
|
||||
|
||||
let facepile = self.render_collaborator(
|
||||
&collaborator.user,
|
||||
collaborator.peer_id,
|
||||
is_present,
|
||||
collaborator.speaking,
|
||||
collaborator.muted,
|
||||
is_following.then_some(player_color.selection),
|
||||
&room,
|
||||
project_id,
|
||||
¤t_user,
|
||||
cx,
|
||||
)?;
|
||||
let face_pile = self.render_collaborator(
|
||||
&collaborator.user,
|
||||
collaborator.peer_id,
|
||||
is_present,
|
||||
collaborator.speaking,
|
||||
collaborator.muted,
|
||||
is_following.then_some(player_color.selection),
|
||||
&room,
|
||||
project_id,
|
||||
¤t_user,
|
||||
cx,
|
||||
)?;
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.id(("collaborator", collaborator.user.id))
|
||||
.child(facepile)
|
||||
.child(render_color_ribbon(player_color.cursor))
|
||||
.cursor_pointer()
|
||||
.on_click({
|
||||
let peer_id = collaborator.peer_id;
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.follow(peer_id, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
Some(
|
||||
v_flex()
|
||||
.id(("collaborator", collaborator.user.id))
|
||||
.child(face_pile)
|
||||
.child(render_color_ribbon(player_color.cursor))
|
||||
.cursor_pointer()
|
||||
.on_click({
|
||||
let peer_id = collaborator.peer_id;
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.follow(peer_id, cx);
|
||||
})
|
||||
.tooltip({
|
||||
let login = collaborator.user.github_login.clone();
|
||||
move |cx| {
|
||||
Tooltip::text(format!("Follow {login}"), cx)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
// right side
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.pr_1()
|
||||
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
|
||||
.when_some(room, |this, room| {
|
||||
let room = room.read(cx);
|
||||
let project = self.project.read(cx);
|
||||
let is_local = project.is_local();
|
||||
let is_dev_server_project = project.dev_server_project_id().is_some();
|
||||
let is_shared = (is_local || is_dev_server_project) && project.is_shared();
|
||||
let is_muted = room.is_muted();
|
||||
let is_deafened = room.is_deafened().unwrap_or(false);
|
||||
let is_screen_sharing = room.is_screen_sharing();
|
||||
let can_use_microphone = room.can_use_microphone();
|
||||
let can_share_projects = room.can_share_projects();
|
||||
|
||||
this.when(
|
||||
(is_local || is_dev_server_project) && can_share_projects,
|
||||
|this| {
|
||||
this.child(
|
||||
Button::new(
|
||||
"toggle_sharing",
|
||||
if is_shared { "Unshare" } else { "Share" },
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if is_shared {
|
||||
"Stop sharing project with call participants"
|
||||
} else {
|
||||
"Share project with call participants"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.ok();
|
||||
})
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.selected(is_shared)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(
|
||||
move |this, _, cx| {
|
||||
if is_shared {
|
||||
this.unshare_project(&Default::default(), cx);
|
||||
} else {
|
||||
this.share_project(&Default::default(), cx);
|
||||
}
|
||||
},
|
||||
)),
|
||||
)
|
||||
},
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child(
|
||||
IconButton::new("leave-call", ui::IconName::Exit)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(|cx| Tooltip::text("Leave call", cx))
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(move |_, cx| {
|
||||
ActiveCall::global(cx)
|
||||
.update(cx, |call, cx| call.hang_up(cx))
|
||||
.detach_and_log_err(cx);
|
||||
}),
|
||||
)
|
||||
.pr_2(),
|
||||
)
|
||||
.when(can_use_microphone, |this| {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
"mute-microphone",
|
||||
if is_muted {
|
||||
ui::IconName::MicMute
|
||||
} else {
|
||||
ui::IconName::Mic
|
||||
},
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if !platform_supported {
|
||||
"Cannot share microphone"
|
||||
} else if is_muted {
|
||||
"Unmute microphone"
|
||||
} else {
|
||||
"Mute microphone"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(platform_supported && is_muted)
|
||||
.disabled(!platform_supported)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
.on_click(move |_, cx| {
|
||||
call_controls::toggle_mute(&Default::default(), cx);
|
||||
.tooltip({
|
||||
let login = collaborator.user.github_login.clone();
|
||||
move |cx| {
|
||||
Tooltip::text(format!("Follow {login}"), cx)
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
IconButton::new(
|
||||
"mute-sound",
|
||||
if is_deafened {
|
||||
ui::IconName::AudioOff
|
||||
} else {
|
||||
ui::IconName::AudioOn
|
||||
},
|
||||
)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(is_deafened)
|
||||
.disabled(!platform_supported)
|
||||
.tooltip(move |cx| {
|
||||
if !platform_supported {
|
||||
Tooltip::text("Cannot share microphone", cx)
|
||||
} else if can_use_microphone {
|
||||
Tooltip::with_meta(
|
||||
"Deafen Audio",
|
||||
None,
|
||||
"Mic will be muted",
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::text("Deafen Audio", cx)
|
||||
}
|
||||
})
|
||||
.on_click(move |_, cx| {
|
||||
call_controls::toggle_deafen(&Default::default(), cx)
|
||||
}),
|
||||
)
|
||||
.when(can_share_projects, |this| {
|
||||
this.child(
|
||||
IconButton::new("screen-share", ui::IconName::Screen)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(is_screen_sharing)
|
||||
.disabled(!platform_supported)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if !platform_supported {
|
||||
"Cannot share screen"
|
||||
} else if is_screen_sharing {
|
||||
"Stop Sharing Screen"
|
||||
} else {
|
||||
"Share Screen"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(move |_, cx| {
|
||||
call_controls::toggle_screen_sharing(&Default::default(), cx)
|
||||
}),
|
||||
}),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
// right side
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.pr_1()
|
||||
.on_mouse_move(|_, cx| cx.stop_propagation())
|
||||
.when_some(room, |this, room| {
|
||||
let room = room.read(cx);
|
||||
let project = self.project.read(cx);
|
||||
let is_local = project.is_local();
|
||||
let is_dev_server_project = project.dev_server_project_id().is_some();
|
||||
let is_shared = (is_local || is_dev_server_project) && project.is_shared();
|
||||
let is_muted = room.is_muted();
|
||||
let is_deafened = room.is_deafened().unwrap_or(false);
|
||||
let is_screen_sharing = room.is_screen_sharing();
|
||||
let can_use_microphone = room.can_use_microphone();
|
||||
let can_share_projects = room.can_share_projects();
|
||||
|
||||
this.when(
|
||||
(is_local || is_dev_server_project) && can_share_projects,
|
||||
|this| {
|
||||
this.child(
|
||||
Button::new(
|
||||
"toggle_sharing",
|
||||
if is_shared { "Unshare" } else { "Share" },
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if is_shared {
|
||||
"Stop sharing project with call participants"
|
||||
} else {
|
||||
"Share project with call participants"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.child(div().pr_2())
|
||||
})
|
||||
.map(|el| {
|
||||
let status = self.client.status();
|
||||
let status = &*status.borrow();
|
||||
if matches!(status, client::Status::Connected { .. }) {
|
||||
el.child(self.render_user_menu_button(cx))
|
||||
} else {
|
||||
el.children(self.render_connection_status(status, cx))
|
||||
.child(self.render_sign_in_button(cx))
|
||||
.child(self.render_user_menu_button(cx))
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
).when(
|
||||
self.platform_style == PlatformStyle::Windows && !cx.is_fullscreen(),
|
||||
|title_bar| title_bar.child(platform_windows::WindowsWindowControls::new(height)),
|
||||
).when(
|
||||
self.platform_style == PlatformStyle::Linux
|
||||
&& !cx.is_fullscreen()
|
||||
&& matches!(decorations, Decorations::Client { .. }),
|
||||
|title_bar| {
|
||||
title_bar
|
||||
.child(platform_linux::LinuxWindowControls::new(close_action))
|
||||
.when(supported_controls.window_menu, |titlebar| {
|
||||
titlebar.on_mouse_down(gpui::MouseButton::Right, move |ev, cx| {
|
||||
cx.show_window_menu(ev.position)
|
||||
})
|
||||
})
|
||||
|
||||
.on_mouse_move(cx.listener(move |this, _ev, cx| {
|
||||
if this.should_move {
|
||||
this.should_move = false;
|
||||
cx.start_window_move();
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.selected(is_shared)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(
|
||||
move |this, _, cx| {
|
||||
if is_shared {
|
||||
this.unshare_project(&Default::default(), cx);
|
||||
} else {
|
||||
this.share_project(&Default::default(), cx);
|
||||
}
|
||||
}))
|
||||
.on_mouse_down_out(cx.listener(move |this, _ev, _cx| {
|
||||
this.should_move = false;
|
||||
}))
|
||||
.on_mouse_down(gpui::MouseButton::Left, cx.listener(move |this, _ev, _cx| {
|
||||
this.should_move = true;
|
||||
}))
|
||||
|
||||
},
|
||||
)
|
||||
},
|
||||
)),
|
||||
)
|
||||
},
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child(
|
||||
IconButton::new("leave-call", ui::IconName::Exit)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(|cx| Tooltip::text("Leave call", cx))
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(move |_, cx| {
|
||||
ActiveCall::global(cx)
|
||||
.update(cx, |call, cx| call.hang_up(cx))
|
||||
.detach_and_log_err(cx);
|
||||
}),
|
||||
)
|
||||
.pr_2(),
|
||||
)
|
||||
.when(can_use_microphone, |this| {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
"mute-microphone",
|
||||
if is_muted {
|
||||
ui::IconName::MicMute
|
||||
} else {
|
||||
ui::IconName::Mic
|
||||
},
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if !platform_supported {
|
||||
"Cannot share microphone"
|
||||
} else if is_muted {
|
||||
"Unmute microphone"
|
||||
} else {
|
||||
"Mute microphone"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(platform_supported && is_muted)
|
||||
.disabled(!platform_supported)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
IconButton::new(
|
||||
"mute-sound",
|
||||
if is_deafened {
|
||||
ui::IconName::AudioOff
|
||||
} else {
|
||||
ui::IconName::AudioOn
|
||||
},
|
||||
)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(is_deafened)
|
||||
.disabled(!platform_supported)
|
||||
.tooltip(move |cx| {
|
||||
if !platform_supported {
|
||||
Tooltip::text("Cannot share microphone", cx)
|
||||
} else if can_use_microphone {
|
||||
Tooltip::with_meta(
|
||||
"Deafen Audio",
|
||||
None,
|
||||
"Mic will be muted",
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::text("Deafen Audio", cx)
|
||||
}
|
||||
})
|
||||
.on_click(move |_, cx| crate::toggle_deafen(&Default::default(), cx)),
|
||||
)
|
||||
.when(can_share_projects, |this| {
|
||||
this.child(
|
||||
IconButton::new("screen-share", ui::IconName::Screen)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(is_screen_sharing)
|
||||
.disabled(!platform_supported)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if !platform_supported {
|
||||
"Cannot share screen"
|
||||
} else if is_screen_sharing {
|
||||
"Stop Sharing Screen"
|
||||
} else {
|
||||
"Share Screen"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(move |_, cx| {
|
||||
crate::toggle_screen_sharing(&Default::default(), cx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(div().pr_2())
|
||||
})
|
||||
.map(|el| {
|
||||
let status = self.client.status();
|
||||
let status = &*status.borrow();
|
||||
if matches!(status, client::Status::Connected { .. }) {
|
||||
el.child(self.render_user_menu_button(cx))
|
||||
} else {
|
||||
el.children(self.render_connection_status(status, cx))
|
||||
.child(self.render_sign_in_button(cx))
|
||||
.child(self.render_user_menu_button(cx))
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TitleBar {
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
workspace: &Workspace,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
fn render_color_ribbon(color: Hsla) -> impl Element {
|
||||
canvas(
|
||||
move |_, _| {},
|
||||
move |bounds, _, cx| {
|
||||
let height = bounds.size.height;
|
||||
let horizontal_offset = height;
|
||||
let vertical_offset = px(height.0 / 2.0);
|
||||
let mut path = Path::new(bounds.lower_left());
|
||||
path.curve_to(
|
||||
bounds.origin + point(horizontal_offset, vertical_offset),
|
||||
bounds.origin + point(px(0.0), vertical_offset),
|
||||
);
|
||||
path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset));
|
||||
path.curve_to(
|
||||
bounds.lower_right(),
|
||||
bounds.upper_right() + point(px(0.0), vertical_offset),
|
||||
);
|
||||
path.line_to(bounds.lower_left());
|
||||
cx.paint_path(path, color);
|
||||
},
|
||||
)
|
||||
.h_1()
|
||||
.w_full()
|
||||
}
|
||||
|
||||
impl CollabTitlebarItem {
|
||||
pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
|
||||
let project = workspace.project().clone();
|
||||
let user_store = workspace.app_state().user_store.clone();
|
||||
let client = workspace.app_state().client.clone();
|
||||
@@ -443,11 +380,7 @@ impl TitleBar {
|
||||
subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
|
||||
|
||||
Self {
|
||||
platform_style: PlatformStyle::platform(),
|
||||
content: div().id(id.into()),
|
||||
children: SmallVec::new(),
|
||||
workspace: workspace.weak_handle(),
|
||||
should_move: false,
|
||||
project,
|
||||
user_store,
|
||||
client,
|
||||
@@ -455,45 +388,6 @@ impl TitleBar {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn height(cx: &mut WindowContext) -> Pixels {
|
||||
(1.75 * cx.rem_size()).max(px(34.))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn height(_cx: &mut WindowContext) -> Pixels {
|
||||
// todo(windows) instead of hard coded size report the actual size to the Windows platform API
|
||||
px(32.)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn top_padding(_cx: &WindowContext) -> Pixels {
|
||||
px(0.)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn top_padding(cx: &WindowContext) -> Pixels {
|
||||
use windows::Win32::UI::{
|
||||
HiDpi::GetSystemMetricsForDpi,
|
||||
WindowsAndMessaging::{SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI},
|
||||
};
|
||||
|
||||
// This top padding is not dependent on the title bar style and is instead a quirk of maximized windows on Windows:
|
||||
// https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543
|
||||
let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) };
|
||||
if cx.is_maximized() {
|
||||
px((padding * 2) as f32)
|
||||
} else {
|
||||
px(0.)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the platform style.
|
||||
pub fn platform_style(mut self, style: PlatformStyle) -> Self {
|
||||
self.platform_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn render_application_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
cfg!(not(target_os = "macos")).then(|| {
|
||||
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
||||
@@ -818,6 +712,97 @@ impl TitleBar {
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_collaborator(
|
||||
&self,
|
||||
user: &Arc<User>,
|
||||
peer_id: PeerId,
|
||||
is_present: bool,
|
||||
is_speaking: bool,
|
||||
is_muted: bool,
|
||||
leader_selection_color: Option<Hsla>,
|
||||
room: &Room,
|
||||
project_id: Option<u64>,
|
||||
current_user: &Arc<User>,
|
||||
cx: &ViewContext<Self>,
|
||||
) -> Option<Div> {
|
||||
if room.role_for_user(user.id) == Some(proto::ChannelRole::Guest) {
|
||||
return None;
|
||||
}
|
||||
|
||||
const FACEPILE_LIMIT: usize = 3;
|
||||
let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
|
||||
let extra_count = followers.len().saturating_sub(FACEPILE_LIMIT);
|
||||
|
||||
Some(
|
||||
div()
|
||||
.m_0p5()
|
||||
.p_0p5()
|
||||
// When the collaborator is not followed, still draw this wrapper div, but leave
|
||||
// it transparent, so that it does not shift the layout when following.
|
||||
.when_some(leader_selection_color, |div, color| {
|
||||
div.rounded_md().bg(color)
|
||||
})
|
||||
.child(
|
||||
FacePile::empty()
|
||||
.child(
|
||||
Avatar::new(user.avatar_uri.clone())
|
||||
.grayscale(!is_present)
|
||||
.border_color(if is_speaking {
|
||||
cx.theme().status().info
|
||||
} else {
|
||||
// We draw the border in a transparent color rather to avoid
|
||||
// the layout shift that would come with adding/removing the border.
|
||||
gpui::transparent_black()
|
||||
})
|
||||
.when(is_muted, |avatar| {
|
||||
avatar.indicator(
|
||||
AvatarAudioStatusIndicator::new(ui::AudioStatus::Muted)
|
||||
.tooltip({
|
||||
let github_login = user.github_login.clone();
|
||||
move |cx| {
|
||||
Tooltip::text(
|
||||
format!("{} is muted", github_login),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.children(followers.iter().take(FACEPILE_LIMIT).filter_map(
|
||||
|follower_peer_id| {
|
||||
let follower = room
|
||||
.remote_participants()
|
||||
.values()
|
||||
.find_map(|p| {
|
||||
(p.peer_id == *follower_peer_id).then_some(&p.user)
|
||||
})
|
||||
.or_else(|| {
|
||||
(self.client.peer_id() == Some(*follower_peer_id))
|
||||
.then_some(current_user)
|
||||
})?
|
||||
.clone();
|
||||
|
||||
Some(div().mt(-px(4.)).child(
|
||||
Avatar::new(follower.avatar_uri.clone()).size(rems(0.75)),
|
||||
))
|
||||
},
|
||||
))
|
||||
.children(if extra_count > 0 {
|
||||
Some(
|
||||
div()
|
||||
.ml_1()
|
||||
.child(Label::new(format!("+{extra_count}")))
|
||||
.into_any_element(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_window_active() {
|
||||
ActiveCall::global(cx)
|
||||
@@ -975,17 +960,3 @@ impl TitleBar {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractiveElement for TitleBar {
|
||||
fn interactivity(&mut self) -> &mut Interactivity {
|
||||
self.content.interactivity()
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulInteractiveElement for TitleBar {}
|
||||
|
||||
impl ParentElement for TitleBar {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
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::{
|
||||
point, AppContext, Pixels, PlatformDisplay, Size, WindowBackgroundAppearance, WindowBounds,
|
||||
WindowDecorations, WindowKind, WindowOptions,
|
||||
actions, point, AppContext, Pixels, PlatformDisplay, Size, Task, WindowBackgroundAppearance,
|
||||
WindowBounds, WindowContext, WindowKind, WindowOptions,
|
||||
};
|
||||
use panel_settings::MessageEditorSettings;
|
||||
pub use panel_settings::{
|
||||
@@ -19,7 +23,12 @@ pub use panel_settings::{
|
||||
use release_channel::ReleaseChannel;
|
||||
use settings::Settings;
|
||||
use ui::px;
|
||||
use workspace::AppState;
|
||||
use workspace::{notifications::DetachAndPromptErr, AppState};
|
||||
|
||||
actions!(
|
||||
collab,
|
||||
[ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
|
||||
);
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
CollaborationPanelSettings::register(cx);
|
||||
@@ -27,13 +36,63 @@ 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);
|
||||
title_bar::init(cx);
|
||||
vcs_menu::init(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));
|
||||
}
|
||||
}
|
||||
|
||||
fn notification_window_options(
|
||||
@@ -63,9 +122,8 @@ fn notification_window_options(
|
||||
kind: WindowKind::PopUp,
|
||||
is_movable: false,
|
||||
display_id: Some(screen.id()),
|
||||
window_background: WindowBackgroundAppearance::Transparent,
|
||||
window_background: WindowBackgroundAppearance::default(),
|
||||
app_id: Some(app_id.to_owned()),
|
||||
window_min_size: None,
|
||||
window_decorations: Some(WindowDecorations::Client),
|
||||
window_min_size: Size::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
use crate::prelude::*;
|
||||
use gpui::AnyElement;
|
||||
use smallvec::SmallVec;
|
||||
use ui::prelude::*;
|
||||
|
||||
/// A facepile is a collection of faces stacked horizontally–
|
||||
/// always with the leftmost face on top and descending in z-index
|
||||
///
|
||||
/// Facepiles are used to display a group of people or things,
|
||||
/// such as a list of participants in a collaboration session.
|
||||
#[derive(IntoElement)]
|
||||
pub struct Facepile {
|
||||
pub struct FacePile {
|
||||
base: Div,
|
||||
faces: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl Facepile {
|
||||
impl FacePile {
|
||||
pub fn empty() -> Self {
|
||||
Self::new(SmallVec::new())
|
||||
}
|
||||
@@ -23,7 +18,7 @@ impl Facepile {
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Facepile {
|
||||
impl RenderOnce for FacePile {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
// Lay the faces out in reverse so they overlap in the desired order (left to right, front to back)
|
||||
self.base
|
||||
@@ -41,13 +36,13 @@ impl RenderOnce for Facepile {
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for Facepile {
|
||||
impl ParentElement for FacePile {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.faces.extend(elements);
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for Facepile {
|
||||
impl Styled for FacePile {
|
||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||
self.base.style()
|
||||
}
|
||||
@@ -102,9 +102,6 @@ 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,7 +720,8 @@ 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, &editor_style.status);
|
||||
let diagnostic_color =
|
||||
super::diagnostic_style(severity, true, &editor_style.status);
|
||||
diagnostic_highlight.underline = Some(UnderlineStyle {
|
||||
color: Some(diagnostic_color),
|
||||
thickness: 1.0.into(),
|
||||
@@ -956,18 +957,16 @@ impl DisplaySnapshot {
|
||||
return 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)
|
||||
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
|
||||
}
|
||||
|
||||
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, Element};
|
||||
use gpui::{div, font, px, AssetSource, Element};
|
||||
use multi_buffer::MultiBuffer;
|
||||
use rand::prelude::*;
|
||||
use settings::SettingsStore;
|
||||
@@ -2014,7 +2014,12 @@ mod tests {
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
assets::Assets.load_test_fonts(cx);
|
||||
cx.text_system()
|
||||
.add_fonts(vec![assets::Assets
|
||||
.load("fonts/zed-mono/zed-mono-extended.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
impl TransformBlock {
|
||||
|
||||
@@ -2131,7 +2131,7 @@ impl Editor {
|
||||
self.refresh_inline_completion(false, cx);
|
||||
}
|
||||
|
||||
pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
|
||||
pub fn placeholder_text(&self, _cx: &mut WindowContext) -> Option<&str> {
|
||||
self.placeholder_text.as_deref()
|
||||
}
|
||||
|
||||
@@ -2914,9 +2914,6 @@ 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
|
||||
@@ -3123,24 +3120,14 @@ impl Editor {
|
||||
let anchor = snapshot.anchor_after(selection.end);
|
||||
if !self.linked_edit_ranges.is_empty() {
|
||||
let start_anchor = snapshot.anchor_before(selection.start);
|
||||
|
||||
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())));
|
||||
}
|
||||
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())));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6822,16 +6809,6 @@ 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;
|
||||
@@ -8479,14 +8456,13 @@ impl Editor {
|
||||
runnable: &mut Runnable,
|
||||
cx: &WindowContext<'_>,
|
||||
) -> Vec<(TaskSourceKind, TaskTemplate)> {
|
||||
let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
|
||||
let (worktree_id, file) = project
|
||||
let (inventory, worktree_id) = project.read_with(cx, |project, cx| {
|
||||
let worktree_id = project
|
||||
.buffer_for_id(runnable.buffer)
|
||||
.and_then(|buffer| buffer.read(cx).file())
|
||||
.map(|file| (WorktreeId::from_usize(file.worktree_id()), file.clone()))
|
||||
.unzip();
|
||||
.map(|file| WorktreeId::from_usize(file.worktree_id()));
|
||||
|
||||
(project.task_inventory().clone(), worktree_id, file)
|
||||
(project.task_inventory().clone(), worktree_id)
|
||||
});
|
||||
|
||||
let inventory = inventory.read(cx);
|
||||
@@ -8496,12 +8472,7 @@ impl Editor {
|
||||
.flat_map(|tag| {
|
||||
let tag = tag.0.clone();
|
||||
inventory
|
||||
.list_tasks(
|
||||
file.clone(),
|
||||
Some(runnable.language.clone()),
|
||||
worktree_id,
|
||||
cx,
|
||||
)
|
||||
.list_tasks(Some(runnable.language.clone()), worktree_id)
|
||||
.into_iter()
|
||||
.filter(move |(_, template)| {
|
||||
template.tags.iter().any(|source_tag| source_tag == &tag)
|
||||
@@ -8842,7 +8813,13 @@ 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| is_wrapped || !hunk.contains_display_row(display_point.row()))
|
||||
.filter(|hunk| {
|
||||
if is_wrapped {
|
||||
true
|
||||
} else {
|
||||
!hunk.contains_display_row(display_point.row())
|
||||
}
|
||||
})
|
||||
.dedup();
|
||||
|
||||
if let Some(hunk) = hunks.next() {
|
||||
@@ -12413,7 +12390,6 @@ impl ViewInputHandler for Editor {
|
||||
let font_id = cx.text_system().resolve_font(&style.text.font());
|
||||
let font_size = style.text.font_size.to_pixels(cx.rem_size());
|
||||
let line_height = style.text.line_height_in_pixels(cx.rem_size());
|
||||
|
||||
let em_width = cx
|
||||
.text_system()
|
||||
.typographic_bounds(font_id, font_size, 'm')
|
||||
@@ -12541,7 +12517,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, cx.theme().status());
|
||||
text_style.color = diagnostic_style(diagnostic.severity, true, 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;
|
||||
@@ -12637,19 +12613,25 @@ 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, colors: &StatusColors) -> Hsla {
|
||||
match severity {
|
||||
DiagnosticSeverity::ERROR => colors.error,
|
||||
DiagnosticSeverity::WARNING => colors.warning,
|
||||
DiagnosticSeverity::INFORMATION => colors.info,
|
||||
DiagnosticSeverity::HINT => colors.info,
|
||||
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,
|
||||
_ => colors.ignored,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ use crate::{
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
|
||||
WindowOptions,
|
||||
div, AssetSource, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
|
||||
WindowBounds, WindowOptions,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
@@ -7019,73 +7019,6 @@ 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, |_| {});
|
||||
@@ -12556,7 +12489,12 @@ pub(crate) fn update_test_project_settings(
|
||||
|
||||
pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
|
||||
_ = cx.update(|cx| {
|
||||
assets::Assets.load_test_fonts(cx);
|
||||
cx.text_system()
|
||||
.add_fonts(vec![assets::Assets
|
||||
.load("fonts/zed-mono/zed-mono-extended.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
|
||||
@@ -1119,12 +1119,11 @@ 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).max(rows_per_page);
|
||||
let total_rows = snapshot.max_point().row().as_f32() + scroll_beyond_last_line;
|
||||
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.);
|
||||
let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.0);
|
||||
|
||||
Some(ScrollbarLayout {
|
||||
hitbox: cx.insert_hitbox(track_bounds, false),
|
||||
@@ -4677,17 +4676,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 { .. }) {
|
||||
(max_row - height_in_lines + 1.).max(0.)
|
||||
(snapshot.max_point().row().as_f32() - 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.).max(0.),
|
||||
ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.0).max(0.0),
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => {
|
||||
(max_row - height_in_lines + 1. + settings.vertical_scroll_margin)
|
||||
.max(0.)
|
||||
(max_row - height_in_lines + 1.0 + settings.vertical_scroll_margin)
|
||||
.max(0.0)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1102,35 +1102,6 @@ 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,24 +203,13 @@ 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 => {
|
||||
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
|
||||
}
|
||||
}
|
||||
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,
|
||||
),
|
||||
};
|
||||
|
||||
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 Plex Mono".into(),
|
||||
family: "Zed 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::{View, ViewContext, VisualTestContext};
|
||||
use gpui::{AssetSource, View, ViewContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageQueries,
|
||||
@@ -39,7 +39,12 @@ impl EditorLspTestContext {
|
||||
let app_state = cx.update(AppState::test);
|
||||
|
||||
cx.update(|cx| {
|
||||
assets::Assets.load_test_fonts(cx);
|
||||
cx.text_system()
|
||||
.add_fonts(vec![assets::Assets
|
||||
.load("fonts/zed-mono/zed-mono-extended.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
language::init(cx);
|
||||
crate::init(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
|
||||
@@ -28,7 +28,6 @@ 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
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
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,8 +76,6 @@ 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)]
|
||||
@@ -139,9 +137,6 @@ 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
|
||||
@@ -205,6 +200,5 @@ fn manifest_from_old_manifest(
|
||||
.collect(),
|
||||
language_servers: Default::default(),
|
||||
slash_commands: BTreeMap::default(),
|
||||
indexed_docs_providers: BTreeMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
pub mod extension_builder;
|
||||
mod extension_indexed_docs_provider;
|
||||
mod extension_lsp_adapter;
|
||||
mod extension_manifest;
|
||||
mod extension_settings;
|
||||
@@ -9,7 +8,6 @@ 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};
|
||||
@@ -34,7 +32,6 @@ use gpui::{
|
||||
WeakModel,
|
||||
};
|
||||
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use indexed_docs::{IndexedDocsRegistry, ProviderId};
|
||||
use language::{
|
||||
LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
|
||||
};
|
||||
@@ -114,7 +111,6 @@ 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)>,
|
||||
@@ -192,7 +188,6 @@ pub fn init(
|
||||
language_registry,
|
||||
theme_registry,
|
||||
SlashCommandRegistry::global(cx),
|
||||
IndexedDocsRegistry::global(cx),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -226,7 +221,6 @@ 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");
|
||||
@@ -258,7 +252,6 @@ impl ExtensionStore {
|
||||
language_registry,
|
||||
theme_registry,
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
reload_tx,
|
||||
tasks: Vec::new(),
|
||||
};
|
||||
@@ -1199,18 +1192,7 @@ 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,7 +12,6 @@ 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;
|
||||
@@ -159,7 +158,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
.collect(),
|
||||
language_servers: BTreeMap::default(),
|
||||
slash_commands: BTreeMap::default(),
|
||||
indexed_docs_providers: BTreeMap::default(),
|
||||
}),
|
||||
dev: false,
|
||||
},
|
||||
@@ -184,7 +182,6 @@ 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,
|
||||
},
|
||||
@@ -257,7 +254,6 @@ 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| {
|
||||
@@ -271,7 +267,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
slash_command_registry.clone(),
|
||||
indexed_docs_registry.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -344,7 +339,6 @@ 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,
|
||||
},
|
||||
@@ -395,7 +389,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -446,7 +439,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
cx.executor().allow_parking();
|
||||
|
||||
@@ -456,8 +449,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
.parent()
|
||||
.unwrap();
|
||||
let cache_dir = root_dir.join("target");
|
||||
let test_extension_id = "test-extension";
|
||||
let test_extension_dir = root_dir.join("extensions").join(test_extension_id);
|
||||
let gleam_extension_dir = root_dir.join("extensions").join("gleam");
|
||||
|
||||
let fs = Arc::new(RealFs::default());
|
||||
let extensions_dir = temp_tree(json!({
|
||||
@@ -476,7 +468,6 @@ async fn test_extension_store_with_test_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();
|
||||
@@ -567,7 +558,6 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -597,7 +587,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
|
||||
extension_store
|
||||
.update(cx, |store, cx| {
|
||||
store.install_dev_extension(test_extension_dir.clone(), cx)
|
||||
store.install_dev_extension(gleam_extension_dir.clone(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -612,8 +602,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
.unwrap();
|
||||
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
let expected_server_path =
|
||||
extensions_dir.join(format!("work/{test_extension_id}/gleam-v1.2.3/gleam"));
|
||||
let expected_server_path = extensions_dir.join("work/gleam/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);
|
||||
@@ -727,8 +716,7 @@ async fn test_extension_store_with_test_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(format!("work/{test_extension_id}/gleam-v2.0.0/gleam"));
|
||||
let new_expected_server_path = extensions_dir.join("work/gleam/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,7 +2,6 @@ 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;
|
||||
|
||||
@@ -290,24 +289,6 @@ 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,13 +1,10 @@
|
||||
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
|
||||
use ::settings::Settings;
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use anyhow::{anyhow, bail, 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,
|
||||
};
|
||||
@@ -29,7 +26,6 @@ wasmtime::component::bindgen!({
|
||||
path: "../extension_api/wit/since_v0.0.7",
|
||||
with: {
|
||||
"worktree": ExtensionWorktree,
|
||||
"key-value-store": ExtensionKeyValueStore
|
||||
},
|
||||
});
|
||||
|
||||
@@ -41,31 +37,11 @@ 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(
|
||||
@@ -125,41 +101,6 @@ 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,7 +19,6 @@ 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,
|
||||
@@ -27,7 +26,7 @@ pub use wit::{
|
||||
zed::extension::platform::{current_platform, Architecture, Os},
|
||||
zed::extension::slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection},
|
||||
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
|
||||
KeyValueStore, LanguageServerInstallationStatus, Range, Worktree,
|
||||
LanguageServerInstallationStatus, Range, Worktree,
|
||||
};
|
||||
|
||||
// Undocumented WIT re-exports.
|
||||
@@ -127,15 +126,6 @@ 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.
|
||||
@@ -258,14 +248,6 @@ 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,7 +2,6 @@ package zed:extension;
|
||||
|
||||
world extension {
|
||||
import github;
|
||||
import http-client;
|
||||
import platform;
|
||||
import nodejs;
|
||||
|
||||
@@ -83,12 +82,6 @@ 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>;
|
||||
|
||||
@@ -134,7 +127,4 @@ 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>;
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
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,11 +29,6 @@ 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
|
||||
|
||||
@@ -80,7 +80,6 @@ backtrace = "0.3"
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
http = { workspace = true, features = ["test-support"] }
|
||||
unicode-segmentation.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
embed-resource = "2.4"
|
||||
@@ -133,7 +132,6 @@ 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 = [
|
||||
@@ -142,7 +140,6 @@ 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
|
||||
@@ -160,11 +157,3 @@ path = "examples/image/image.rs"
|
||||
[[example]]
|
||||
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,11 +23,6 @@ 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 {
|
||||
@@ -47,7 +42,7 @@ impl Render for AnimationExample {
|
||||
.child(
|
||||
svg()
|
||||
.size_8()
|
||||
.path(ARROW_CIRCLE_SVG)
|
||||
.path("examples/image/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(300.0), px(300.0)), cx);
|
||||
let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
|
||||
@@ -1,489 +0,0 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use gpui::*;
|
||||
use unicode_segmentation::*;
|
||||
|
||||
actions!(
|
||||
text_input,
|
||||
[
|
||||
Backspace,
|
||||
Delete,
|
||||
Left,
|
||||
Right,
|
||||
SelectLeft,
|
||||
SelectRight,
|
||||
SelectAll,
|
||||
Home,
|
||||
End,
|
||||
ShowCharacterPalette
|
||||
]
|
||||
);
|
||||
|
||||
struct TextInput {
|
||||
focus_handle: FocusHandle,
|
||||
content: SharedString,
|
||||
selected_range: Range<usize>,
|
||||
selection_reversed: bool,
|
||||
marked_range: Option<Range<usize>>,
|
||||
last_layout: Option<ShapedLine>,
|
||||
}
|
||||
|
||||
impl TextInput {
|
||||
fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
self.move_to(self.previous_boundary(self.cursor_offset()), cx);
|
||||
} else {
|
||||
self.move_to(self.selected_range.start, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
self.move_to(self.next_boundary(self.selected_range.end), cx);
|
||||
} else {
|
||||
self.move_to(self.selected_range.end, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
|
||||
self.select_to(self.previous_boundary(self.cursor_offset()), cx);
|
||||
}
|
||||
|
||||
fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), cx);
|
||||
}
|
||||
|
||||
fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
|
||||
self.move_to(0, cx);
|
||||
self.select_to(self.content.len(), cx)
|
||||
}
|
||||
|
||||
fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) {
|
||||
self.move_to(0, cx);
|
||||
}
|
||||
|
||||
fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) {
|
||||
self.move_to(self.content.len(), cx);
|
||||
}
|
||||
|
||||
fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
self.select_to(self.previous_boundary(self.cursor_offset()), cx)
|
||||
}
|
||||
self.replace_text_in_range(None, "", cx)
|
||||
}
|
||||
|
||||
fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), cx)
|
||||
}
|
||||
self.replace_text_in_range(None, "", cx)
|
||||
}
|
||||
|
||||
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
|
||||
cx.show_character_palette();
|
||||
}
|
||||
|
||||
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||
self.selected_range = offset..offset;
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn cursor_offset(&self) -> usize {
|
||||
if self.selection_reversed {
|
||||
self.selected_range.start
|
||||
} else {
|
||||
self.selected_range.end
|
||||
}
|
||||
}
|
||||
|
||||
fn select_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||
if self.selection_reversed {
|
||||
self.selected_range.start = offset
|
||||
} else {
|
||||
self.selected_range.end = offset
|
||||
};
|
||||
if self.selected_range.end < self.selected_range.start {
|
||||
self.selection_reversed = !self.selection_reversed;
|
||||
self.selected_range = self.selected_range.end..self.selected_range.start;
|
||||
}
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn offset_from_utf16(&self, offset: usize) -> usize {
|
||||
let mut utf8_offset = 0;
|
||||
let mut utf16_count = 0;
|
||||
|
||||
for ch in self.content.chars() {
|
||||
if utf16_count >= offset {
|
||||
break;
|
||||
}
|
||||
utf16_count += ch.len_utf16();
|
||||
utf8_offset += ch.len_utf8();
|
||||
}
|
||||
|
||||
utf8_offset
|
||||
}
|
||||
|
||||
fn offset_to_utf16(&self, offset: usize) -> usize {
|
||||
let mut utf16_offset = 0;
|
||||
let mut utf8_count = 0;
|
||||
|
||||
for ch in self.content.chars() {
|
||||
if utf8_count >= offset {
|
||||
break;
|
||||
}
|
||||
utf8_count += ch.len_utf8();
|
||||
utf16_offset += ch.len_utf16();
|
||||
}
|
||||
|
||||
utf16_offset
|
||||
}
|
||||
|
||||
fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
|
||||
self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
|
||||
}
|
||||
|
||||
fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
|
||||
self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
|
||||
}
|
||||
|
||||
fn previous_boundary(&self, offset: usize) -> usize {
|
||||
self.content
|
||||
.grapheme_indices(true)
|
||||
.rev()
|
||||
.find_map(|(idx, _)| (idx < offset).then_some(idx))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn next_boundary(&self, offset: usize) -> usize {
|
||||
self.content
|
||||
.grapheme_indices(true)
|
||||
.find_map(|(idx, _)| (idx > offset).then_some(idx))
|
||||
.unwrap_or(self.content.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewInputHandler for TextInput {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) -> Option<String> {
|
||||
let range = self.range_from_utf16(&range_utf16);
|
||||
Some(self.content[range].to_string())
|
||||
}
|
||||
|
||||
fn selected_text_range(&mut self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
|
||||
Some(self.range_to_utf16(&self.selected_range))
|
||||
}
|
||||
|
||||
fn marked_text_range(&self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
|
||||
self.marked_range
|
||||
.as_ref()
|
||||
.map(|range| self.range_to_utf16(range))
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self, _cx: &mut ViewContext<Self>) {
|
||||
self.marked_range = None;
|
||||
}
|
||||
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let range = range_utf16
|
||||
.as_ref()
|
||||
.map(|range_utf16| self.range_from_utf16(range_utf16))
|
||||
.or(self.marked_range.clone())
|
||||
.unwrap_or(self.selected_range.clone());
|
||||
|
||||
self.content =
|
||||
(self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
|
||||
.into();
|
||||
self.selected_range = range.start + new_text.len()..range.start + new_text.len();
|
||||
self.marked_range.take();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range_utf16: Option<Range<usize>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let range = range_utf16
|
||||
.as_ref()
|
||||
.map(|range_utf16| self.range_from_utf16(range_utf16))
|
||||
.or(self.marked_range.clone())
|
||||
.unwrap_or(self.selected_range.clone());
|
||||
|
||||
self.content =
|
||||
(self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
|
||||
.into();
|
||||
self.marked_range = Some(range.start..range.start + new_text.len());
|
||||
self.selected_range = new_selected_range_utf16
|
||||
.as_ref()
|
||||
.map(|range_utf16| self.range_from_utf16(range_utf16))
|
||||
.map(|new_range| new_range.start + range.start..new_range.end + range.end)
|
||||
.unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn bounds_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) -> Option<Bounds<Pixels>> {
|
||||
let Some(last_layout) = self.last_layout.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
let range = self.range_from_utf16(&range_utf16);
|
||||
Some(Bounds::from_corners(
|
||||
point(
|
||||
bounds.left() + last_layout.x_for_index(range.start),
|
||||
bounds.top(),
|
||||
),
|
||||
point(
|
||||
bounds.left() + last_layout.x_for_index(range.end),
|
||||
bounds.bottom(),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct TextElement {
|
||||
input: View<TextInput>,
|
||||
}
|
||||
|
||||
struct PrepaintState {
|
||||
line: Option<ShapedLine>,
|
||||
cursor: Option<PaintQuad>,
|
||||
selection: Option<PaintQuad>,
|
||||
}
|
||||
|
||||
impl IntoElement for TextElement {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for TextElement {
|
||||
type RequestLayoutState = ();
|
||||
|
||||
type PrepaintState = PrepaintState;
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_id: Option<&GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut style = Style::default();
|
||||
style.size.width = relative(1.).into();
|
||||
style.size.height = cx.line_height().into();
|
||||
(cx.request_layout(style, []), ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_id: Option<&GlobalElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self::PrepaintState {
|
||||
let input = self.input.read(cx);
|
||||
let content = input.content.clone();
|
||||
let selected_range = input.selected_range.clone();
|
||||
let cursor = input.cursor_offset();
|
||||
let style = cx.text_style();
|
||||
let run = TextRun {
|
||||
len: input.content.len(),
|
||||
font: style.font(),
|
||||
color: style.color,
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
};
|
||||
let runs = if let Some(marked_range) = input.marked_range.as_ref() {
|
||||
vec![
|
||||
TextRun {
|
||||
len: marked_range.start,
|
||||
..run.clone()
|
||||
},
|
||||
TextRun {
|
||||
len: marked_range.end - marked_range.start,
|
||||
underline: Some(UnderlineStyle {
|
||||
color: Some(run.color),
|
||||
thickness: px(1.0),
|
||||
wavy: false,
|
||||
}),
|
||||
..run.clone()
|
||||
},
|
||||
TextRun {
|
||||
len: input.content.len() - marked_range.end,
|
||||
..run.clone()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|run| run.len > 0)
|
||||
.collect()
|
||||
} else {
|
||||
vec![run]
|
||||
};
|
||||
|
||||
let font_size = style.font_size.to_pixels(cx.rem_size());
|
||||
let line = cx
|
||||
.text_system()
|
||||
.shape_line(content, font_size, &runs)
|
||||
.unwrap();
|
||||
|
||||
let cursor_pos = line.x_for_index(cursor);
|
||||
let (selection, cursor) = if selected_range.is_empty() {
|
||||
(
|
||||
None,
|
||||
Some(fill(
|
||||
Bounds::new(
|
||||
point(bounds.left() + cursor_pos, bounds.top()),
|
||||
size(px(2.), bounds.bottom() - bounds.top()),
|
||||
),
|
||||
gpui::blue(),
|
||||
)),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Some(fill(
|
||||
Bounds::from_corners(
|
||||
point(
|
||||
bounds.left() + line.x_for_index(selected_range.start),
|
||||
bounds.top(),
|
||||
),
|
||||
point(
|
||||
bounds.left() + line.x_for_index(selected_range.end),
|
||||
bounds.bottom(),
|
||||
),
|
||||
),
|
||||
rgba(0x3311FF30),
|
||||
)),
|
||||
None,
|
||||
)
|
||||
};
|
||||
PrepaintState {
|
||||
line: Some(line),
|
||||
cursor,
|
||||
selection,
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_id: Option<&GlobalElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let focus_handle = self.input.read(cx).focus_handle.clone();
|
||||
cx.handle_input(
|
||||
&focus_handle,
|
||||
ElementInputHandler::new(bounds, self.input.clone()),
|
||||
);
|
||||
if let Some(selection) = prepaint.selection.take() {
|
||||
cx.paint_quad(selection)
|
||||
}
|
||||
let line = prepaint.line.take().unwrap();
|
||||
line.paint(bounds.origin, cx.line_height(), cx).unwrap();
|
||||
|
||||
if let Some(cursor) = prepaint.cursor.take() {
|
||||
cx.paint_quad(cursor);
|
||||
}
|
||||
self.input.update(cx, |input, _cx| {
|
||||
input.last_layout = Some(line);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TextInput {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.flex()
|
||||
.key_context("TextInput")
|
||||
.track_focus(&self.focus_handle)
|
||||
.on_action(cx.listener(Self::backspace))
|
||||
.on_action(cx.listener(Self::delete))
|
||||
.on_action(cx.listener(Self::left))
|
||||
.on_action(cx.listener(Self::right))
|
||||
.on_action(cx.listener(Self::select_left))
|
||||
.on_action(cx.listener(Self::select_right))
|
||||
.on_action(cx.listener(Self::select_all))
|
||||
.on_action(cx.listener(Self::home))
|
||||
.on_action(cx.listener(Self::end))
|
||||
.on_action(cx.listener(Self::show_character_palette))
|
||||
.bg(rgb(0xeeeeee))
|
||||
.size_full()
|
||||
.line_height(px(30.))
|
||||
.text_size(px(24.))
|
||||
.child(
|
||||
div()
|
||||
.h(px(30. + 4. * 2.))
|
||||
.w_full()
|
||||
.p(px(4.))
|
||||
.bg(white())
|
||||
.child(TextElement {
|
||||
input: cx.view().clone(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new().run(|cx: &mut AppContext| {
|
||||
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("backspace", Backspace, None),
|
||||
KeyBinding::new("delete", Delete, None),
|
||||
KeyBinding::new("left", Left, None),
|
||||
KeyBinding::new("right", Right, None),
|
||||
KeyBinding::new("shift-left", SelectLeft, None),
|
||||
KeyBinding::new("shift-right", SelectRight, None),
|
||||
KeyBinding::new("cmd-a", SelectAll, None),
|
||||
KeyBinding::new("home", Home, None),
|
||||
KeyBinding::new("end", End, None),
|
||||
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None),
|
||||
]);
|
||||
let window = cx
|
||||
.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
..Default::default()
|
||||
},
|
||||
|cx| {
|
||||
cx.new_view(|cx| TextInput {
|
||||
focus_handle: cx.focus_handle(),
|
||||
content: "".into(),
|
||||
selected_range: 0..0,
|
||||
selection_reversed: false,
|
||||
marked_range: None,
|
||||
last_layout: None,
|
||||
})
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
window
|
||||
.update(cx, |view, cx| {
|
||||
view.focus_handle.focus(cx);
|
||||
cx.activate(true)
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
@@ -51,8 +51,7 @@ fn main() {
|
||||
kind: WindowKind::PopUp,
|
||||
is_movable: false,
|
||||
app_id: None,
|
||||
window_min_size: None,
|
||||
window_decorations: None,
|
||||
window_min_size: Size::default(),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
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,16 +309,6 @@ 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 {
|
||||
|
||||