Compare commits
7 Commits
settings-u
...
missing-hu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbd9cc62e1 | ||
|
|
128e7d3306 | ||
|
|
a42c95548e | ||
|
|
dc5cc1b6a0 | ||
|
|
0ea093124f | ||
|
|
45dacd4ff6 | ||
|
|
336953de15 |
6
.github/workflows/docs.yml
vendored
6
.github/workflows/docs.yml
vendored
@@ -24,13 +24,11 @@ jobs:
|
||||
- name: Prettier Check on /docs
|
||||
working-directory: ./docs
|
||||
run: |
|
||||
pnpm dlx prettier@${PRETTIER_VERSION} . --check || {
|
||||
pnpm dlx prettier . --check || {
|
||||
echo "To fix, run from the root of the zed repo:"
|
||||
echo " cd docs && pnpm dlx prettier@${PRETTIER_VERSION} . --write && cd .."
|
||||
echo " cd docs && pnpm dlx prettier . --write && cd .."
|
||||
false
|
||||
}
|
||||
env:
|
||||
PRETTIER_VERSION: 3.5.0
|
||||
|
||||
- name: Check for Typos with Typos-CLI
|
||||
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6
|
||||
|
||||
55
.gitignore
vendored
55
.gitignore
vendored
@@ -1,35 +1,36 @@
|
||||
**/*.db
|
||||
**/cargo-target
|
||||
**/target
|
||||
**/venv
|
||||
*.wasm
|
||||
*.xcodeproj
|
||||
.DS_Store
|
||||
.blob_store
|
||||
.build
|
||||
.envrc
|
||||
.flatpak-builder
|
||||
.idea
|
||||
.netrc
|
||||
.pytest_cache
|
||||
.swiftpm
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.venv
|
||||
.vscode
|
||||
.wrangler
|
||||
/.direnv
|
||||
/assets/*licenses.*
|
||||
/crates/collab/seed.json
|
||||
/crates/theme/schemas/theme.json
|
||||
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
|
||||
/dev.zed.Zed*.json
|
||||
.envrc
|
||||
.idea
|
||||
**/target
|
||||
**/cargo-target
|
||||
/zed.xcworkspace
|
||||
.DS_Store
|
||||
/plugins/bin
|
||||
/script/node_modules
|
||||
/zed.xcworkspace
|
||||
DerivedData/
|
||||
/crates/theme/schemas/theme.json
|
||||
/crates/collab/seed.json
|
||||
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
|
||||
/dev.zed.Zed*.json
|
||||
/assets/*licenses.*
|
||||
**/venv
|
||||
.build
|
||||
*.wasm
|
||||
Packages
|
||||
*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
.swiftpm
|
||||
**/*.db
|
||||
.pytest_cache
|
||||
.venv
|
||||
.blob_store
|
||||
.vscode
|
||||
.wrangler
|
||||
.flatpak-builder
|
||||
.envrc
|
||||
|
||||
# Don't commit any secrets to the repo.
|
||||
.env.secret.toml
|
||||
|
||||
92
Cargo.lock
generated
92
Cargo.lock
generated
@@ -2942,28 +2942,6 @@ dependencies = [
|
||||
"gpui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "component"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"collections",
|
||||
"gpui",
|
||||
"linkme",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"theme",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "component_preview"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"component",
|
||||
"gpui",
|
||||
"ui",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
@@ -5352,7 +5330,6 @@ dependencies = [
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"git",
|
||||
"gpui",
|
||||
"language",
|
||||
@@ -5372,7 +5349,6 @@ dependencies = [
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6387,8 +6363,6 @@ dependencies = [
|
||||
"file_icons",
|
||||
"gpui",
|
||||
"project",
|
||||
"schemars",
|
||||
"serde",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
@@ -7302,26 +7276,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linkme"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "566336154b9e58a4f055f6dd4cbab62c7dc0826ce3c0a04e63b2d2ecd784cdae"
|
||||
dependencies = [
|
||||
"linkme-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linkme-impl"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edbe595006d355eaf9ae11db92707d4338cd2384d16866131cc1afdbdd35d8d9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
@@ -7880,17 +7834,6 @@ dependencies = [
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "migrator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"collections",
|
||||
"convert_case 0.7.1",
|
||||
"pretty_assertions",
|
||||
"tree-sitter",
|
||||
"tree-sitter-json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mimalloc"
|
||||
version = "0.1.43"
|
||||
@@ -8735,9 +8678,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.3"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "oo7"
|
||||
@@ -9044,10 +8987,7 @@ dependencies = [
|
||||
name = "panel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"editor",
|
||||
"gpui",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
"workspace",
|
||||
]
|
||||
@@ -12038,7 +11978,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"indoc",
|
||||
"log",
|
||||
"migrator",
|
||||
"paths",
|
||||
"pretty_assertions",
|
||||
"release_channel",
|
||||
@@ -14365,10 +14304,8 @@ name = "ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"component",
|
||||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
"linkme",
|
||||
"menu",
|
||||
"serde",
|
||||
"settings",
|
||||
@@ -14396,7 +14333,6 @@ name = "ui_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"convert_case 0.7.1",
|
||||
"linkme",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
@@ -14666,6 +14602,22 @@ version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "vcs_menu"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"fuzzy",
|
||||
"git",
|
||||
"gpui",
|
||||
"picker",
|
||||
"project",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.0"
|
||||
@@ -16168,7 +16120,6 @@ dependencies = [
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"component",
|
||||
"db",
|
||||
"derive_more",
|
||||
"env_logger 0.11.6",
|
||||
@@ -16603,7 +16554,6 @@ dependencies = [
|
||||
"collections",
|
||||
"command_palette",
|
||||
"command_palette_hooks",
|
||||
"component_preview",
|
||||
"copilot",
|
||||
"db",
|
||||
"diagnostics",
|
||||
@@ -16693,6 +16643,7 @@ dependencies = [
|
||||
"urlencoding",
|
||||
"util",
|
||||
"uuid",
|
||||
"vcs_menu",
|
||||
"vim",
|
||||
"vim_mode_setting",
|
||||
"welcome",
|
||||
@@ -16801,13 +16752,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_llm_client"
|
||||
version = "0.4.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "614669bead4741b2fc352ae1967318be16949cf46f59013e548c6dbfdfc01252"
|
||||
checksum = "9ea4d8ead1e1158e5ebdd6735df25973781da70de5c8008e3a13595865ca4f31"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -26,8 +26,6 @@ members = [
|
||||
"crates/collections",
|
||||
"crates/command_palette",
|
||||
"crates/command_palette_hooks",
|
||||
"crates/component",
|
||||
"crates/component_preview",
|
||||
"crates/context_server",
|
||||
"crates/context_server_settings",
|
||||
"crates/copilot",
|
||||
@@ -83,7 +81,6 @@ members = [
|
||||
"crates/markdown_preview",
|
||||
"crates/media",
|
||||
"crates/menu",
|
||||
"crates/migrator",
|
||||
"crates/multi_buffer",
|
||||
"crates/node_runtime",
|
||||
"crates/notifications",
|
||||
@@ -149,6 +146,7 @@ members = [
|
||||
"crates/ui_macros",
|
||||
"crates/util",
|
||||
"crates/util_macros",
|
||||
"crates/vcs_menu",
|
||||
"crates/vim",
|
||||
"crates/vim_mode_setting",
|
||||
"crates/welcome",
|
||||
@@ -228,8 +226,6 @@ collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||
component = { path = "crates/component" }
|
||||
component_preview = { path = "crates/component_preview" }
|
||||
context_server = { path = "crates/context_server" }
|
||||
context_server_settings = { path = "crates/context_server_settings" }
|
||||
copilot = { path = "crates/copilot" }
|
||||
@@ -283,7 +279,6 @@ markdown = { path = "crates/markdown" }
|
||||
markdown_preview = { path = "crates/markdown_preview" }
|
||||
media = { path = "crates/media" }
|
||||
menu = { path = "crates/menu" }
|
||||
migrator = { path = "crates/migrator" }
|
||||
multi_buffer = { path = "crates/multi_buffer" }
|
||||
node_runtime = { path = "crates/node_runtime" }
|
||||
notifications = { path = "crates/notifications" }
|
||||
@@ -349,6 +344,7 @@ ui_input = { path = "crates/ui_input" }
|
||||
ui_macros = { path = "crates/ui_macros" }
|
||||
util = { path = "crates/util" }
|
||||
util_macros = { path = "crates/util_macros" }
|
||||
vcs_menu = { path = "crates/vcs_menu" }
|
||||
vim = { path = "crates/vim" }
|
||||
vim_mode_setting = { path = "crates/vim_mode_setting" }
|
||||
welcome = { path = "crates/welcome" }
|
||||
@@ -430,7 +426,6 @@ jupyter-websocket-client = { version = "0.9.0" }
|
||||
libc = "0.2"
|
||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||
linkify = "0.10.0"
|
||||
linkme = "0.3.31"
|
||||
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "811ceae29fabee455f110c56cd66b3f49a7e5003", features = [
|
||||
"dispatcher",
|
||||
"services-dispatcher",
|
||||
@@ -564,7 +559,7 @@ wasmtime = { version = "24", default-features = false, features = [
|
||||
wasmtime-wasi = "24"
|
||||
which = "6.0.0"
|
||||
wit-component = "0.201"
|
||||
zed_llm_client = "0.4"
|
||||
zed_llm_client = "0.2"
|
||||
zstd = "0.11"
|
||||
metal = "0.31"
|
||||
|
||||
@@ -679,6 +674,7 @@ telemetry_events = { codegen-units = 1 }
|
||||
theme_selector = { codegen-units = 1 }
|
||||
time_format = { codegen-units = 1 }
|
||||
ui_input = { codegen-units = 1 }
|
||||
vcs_menu = { codegen-units = 1 }
|
||||
zed_actions = { codegen-units = 1 }
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
"jpeg": "image",
|
||||
"jpg": "image",
|
||||
"js": "javascript",
|
||||
"json": "json",
|
||||
"json": "storage",
|
||||
"jsonc": "storage",
|
||||
"jsx": "react",
|
||||
"jxl": "image",
|
||||
@@ -150,19 +150,8 @@
|
||||
"postcss": "css",
|
||||
"ppt": "document",
|
||||
"pptx": "document",
|
||||
"prettier.config.cjs": "prettier",
|
||||
"prettier.config.js": "prettier",
|
||||
"prettier.config.mjs": "prettier",
|
||||
"prettierignore": "prettier",
|
||||
"prettierrc": "prettier",
|
||||
"prettierrc.cjs": "prettier",
|
||||
"prettierrc.js": "prettier",
|
||||
"prettierrc.json": "prettier",
|
||||
"prettierrc.json5": "prettier",
|
||||
"prettierrc.mjs": "prettier",
|
||||
"prettierrc.toml": "prettier",
|
||||
"prettierrc.yaml": "prettier",
|
||||
"prettierrc.yml": "prettier",
|
||||
"prisma": "prisma",
|
||||
"profile": "terminal",
|
||||
"ps1": "terminal",
|
||||
|
||||
@@ -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="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M8 9V11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<circle cx="8" cy="9" r="1" fill="black"/>
|
||||
<rect x="3.75" y="5.75" width="8.5" height="7.5" rx="1.25" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 452 B |
@@ -32,7 +32,7 @@
|
||||
"ctrl-q": "zed::Quit",
|
||||
"f11": "zed::ToggleFullScreen",
|
||||
"ctrl-alt-z": "zeta::RateCompletions",
|
||||
"ctrl-shift-i": "edit_prediction::ToggleMenu"
|
||||
"ctrl-shift-i": "inline_completion::ToggleMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -122,8 +122,7 @@
|
||||
"ctrl-i": "editor::ShowSignatureHelp",
|
||||
"alt-g b": "editor::ToggleGitBlame",
|
||||
"menu": "editor::OpenContextMenu",
|
||||
"shift-f10": "editor::OpenContextMenu",
|
||||
"ctrl-shift-e": "editor::ToggleEditPrediction"
|
||||
"shift-f10": "editor::OpenContextMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -146,17 +145,17 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full && edit_prediction",
|
||||
"context": "Editor && mode == full && inline_completion",
|
||||
"bindings": {
|
||||
"alt-]": "editor::NextEditPrediction",
|
||||
"alt-[": "editor::PreviousEditPrediction",
|
||||
"alt-right": "editor::AcceptPartialEditPrediction"
|
||||
"alt-]": "editor::NextInlineCompletion",
|
||||
"alt-[": "editor::PreviousInlineCompletion",
|
||||
"alt-right": "editor::AcceptPartialInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && !edit_prediction",
|
||||
"context": "Editor && !inline_completion",
|
||||
"bindings": {
|
||||
"alt-\\": "editor::ShowEditPrediction"
|
||||
"alt-\\": "editor::ShowInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -275,8 +274,8 @@
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"ctrl-f4": ["pane::CloseActiveItem", { "close_pinned": false }],
|
||||
"ctrl-w": ["pane::CloseActiveItem", { "close_pinned": false }],
|
||||
"ctrl-f4": "pane::CloseActiveItem",
|
||||
"ctrl-w": "pane::CloseActiveItem",
|
||||
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
|
||||
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
|
||||
@@ -349,15 +348,15 @@
|
||||
"ctrl-k ctrl-l": "editor::ToggleFold",
|
||||
"ctrl-k ctrl-[": "editor::FoldRecursive",
|
||||
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
|
||||
"ctrl-k ctrl-1": ["editor::FoldAtLevel", 1],
|
||||
"ctrl-k ctrl-2": ["editor::FoldAtLevel", 2],
|
||||
"ctrl-k ctrl-3": ["editor::FoldAtLevel", 3],
|
||||
"ctrl-k ctrl-4": ["editor::FoldAtLevel", 4],
|
||||
"ctrl-k ctrl-5": ["editor::FoldAtLevel", 5],
|
||||
"ctrl-k ctrl-6": ["editor::FoldAtLevel", 6],
|
||||
"ctrl-k ctrl-7": ["editor::FoldAtLevel", 7],
|
||||
"ctrl-k ctrl-8": ["editor::FoldAtLevel", 8],
|
||||
"ctrl-k ctrl-9": ["editor::FoldAtLevel", 9],
|
||||
"ctrl-k ctrl-1": ["editor::FoldAtLevel", { "level": 1 }],
|
||||
"ctrl-k ctrl-2": ["editor::FoldAtLevel", { "level": 2 }],
|
||||
"ctrl-k ctrl-3": ["editor::FoldAtLevel", { "level": 3 }],
|
||||
"ctrl-k ctrl-4": ["editor::FoldAtLevel", { "level": 4 }],
|
||||
"ctrl-k ctrl-5": ["editor::FoldAtLevel", { "level": 5 }],
|
||||
"ctrl-k ctrl-6": ["editor::FoldAtLevel", { "level": 6 }],
|
||||
"ctrl-k ctrl-7": ["editor::FoldAtLevel", { "level": 7 }],
|
||||
"ctrl-k ctrl-8": ["editor::FoldAtLevel", { "level": 8 }],
|
||||
"ctrl-k ctrl-9": ["editor::FoldAtLevel", { "level": 9 }],
|
||||
"ctrl-k ctrl-0": "editor::FoldAll",
|
||||
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
@@ -433,14 +432,14 @@
|
||||
"ctrl-alt-s": "workspace::SaveAll",
|
||||
"ctrl-k m": "language_selector::Toggle",
|
||||
"escape": "workspace::Unfollow",
|
||||
"ctrl-k ctrl-left": "workspace::ActivatePaneLeft",
|
||||
"ctrl-k ctrl-right": "workspace::ActivatePaneRight",
|
||||
"ctrl-k ctrl-up": "workspace::ActivatePaneUp",
|
||||
"ctrl-k ctrl-down": "workspace::ActivatePaneDown",
|
||||
"ctrl-k shift-left": "workspace::SwapPaneLeft",
|
||||
"ctrl-k shift-right": "workspace::SwapPaneRight",
|
||||
"ctrl-k shift-up": "workspace::SwapPaneUp",
|
||||
"ctrl-k shift-down": "workspace::SwapPaneDown",
|
||||
"ctrl-k ctrl-left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-k ctrl-right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-k ctrl-up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-k ctrl-down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
||||
"ctrl-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"ctrl-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"ctrl-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
|
||||
"ctrl-shift-x": "zed::Extensions",
|
||||
"ctrl-shift-r": "task::Rerun",
|
||||
"ctrl-alt-r": "task::Rerun",
|
||||
@@ -454,8 +453,8 @@
|
||||
{
|
||||
"context": "ApplicationMenu",
|
||||
"bindings": {
|
||||
"left": "app_menu::ActivateMenuLeft",
|
||||
"right": "app_menu::ActivateMenuRight"
|
||||
"left": ["app_menu::NavigateApplicationMenuInDirection", "Left"],
|
||||
"right": ["app_menu::NavigateApplicationMenuInDirection", "Right"]
|
||||
}
|
||||
},
|
||||
// Bindings from Sublime Text
|
||||
@@ -497,22 +496,24 @@
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_completions",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCompletion",
|
||||
"tab": "editor::ComposeCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && edit_prediction",
|
||||
"context": "Editor && inline_completion",
|
||||
"bindings": {
|
||||
// Changing the modifier currently breaks accepting while you also an LSP completions menu open
|
||||
"alt-enter": "editor::AcceptEditPrediction"
|
||||
"alt-enter": "editor::AcceptInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
|
||||
"context": "Editor && inline_completion && !inline_completion_requires_modifier",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"tab": "editor::AcceptEditPrediction"
|
||||
"tab": "editor::AcceptInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -536,7 +537,8 @@
|
||||
{
|
||||
"bindings": {
|
||||
"ctrl-alt-shift-f": "workspace::FollowNextCollaborator",
|
||||
"ctrl-alt-i": "zed::DebugElements"
|
||||
"ctrl-alt-i": "zed::DebugElements",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -554,8 +556,7 @@
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-f8": "editor::GoToHunk",
|
||||
"ctrl-shift-f8": "editor::GoToPrevHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
"ctrl-enter": "assistant::InlineAssist"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -601,12 +602,14 @@
|
||||
},
|
||||
{
|
||||
"context": "MessageEditor > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "assistant2::Chat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ContextStrip",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "assistant2::FocusUp",
|
||||
"right": "assistant2::FocusRight",
|
||||
@@ -699,32 +702,30 @@
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && !CommitEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "git_panel::Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && ChangesList",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "menu::SelectPrev",
|
||||
"down": "menu::SelectNext",
|
||||
"enter": "menu::Confirm",
|
||||
"space": "git::ToggleStaged",
|
||||
"ctrl-space": "git::StageAll",
|
||||
"ctrl-shift-space": "git::UnstageAll",
|
||||
"tab": "git_panel::FocusEditor",
|
||||
"shift-tab": "git_panel::FocusEditor",
|
||||
"escape": "git_panel::ToggleFocus"
|
||||
"ctrl-shift-space": "git::UnstageAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel > Editor",
|
||||
"context": "GitPanel && CommitEditor > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "git_panel::FocusChanges",
|
||||
"ctrl-enter": "git::Commit",
|
||||
"tab": "git_panel::FocusChanges",
|
||||
"shift-tab": "git_panel::FocusChanges",
|
||||
"alt-up": "git_panel::FocusChanges"
|
||||
"ctrl-enter": "git::CommitChanges",
|
||||
"ctrl-shift-enter": "git::CommitAllChanges"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -832,6 +833,7 @@
|
||||
},
|
||||
{
|
||||
"context": "ZedPredictModal",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "menu::Cancel"
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
"cmd-m": "zed::Minimize",
|
||||
"fn-f": "zed::ToggleFullScreen",
|
||||
"ctrl-cmd-f": "zed::ToggleFullScreen",
|
||||
"ctrl-cmd-z": "zeta::RateCompletions",
|
||||
"ctrl-cmd-i": "edit_prediction::ToggleMenu"
|
||||
"ctrl-shift-z": "zeta::RateCompletions",
|
||||
"ctrl-shift-i": "inline_completion::ToggleMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -132,8 +132,7 @@
|
||||
"cmd-alt-g b": "editor::ToggleGitBlame",
|
||||
"cmd-i": "editor::ShowSignatureHelp",
|
||||
"ctrl-f12": "editor::GoToDeclaration",
|
||||
"alt-ctrl-f12": "editor::GoToDeclarationSplit",
|
||||
"ctrl-cmd-e": "editor::ToggleEditPrediction"
|
||||
"alt-ctrl-f12": "editor::GoToDeclarationSplit"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -156,19 +155,19 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full && edit_prediction",
|
||||
"context": "Editor && mode == full && inline_completion",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-tab": "editor::NextEditPrediction",
|
||||
"alt-shift-tab": "editor::PreviousEditPrediction",
|
||||
"ctrl-cmd-right": "editor::AcceptPartialEditPrediction"
|
||||
"alt-tab": "editor::NextInlineCompletion",
|
||||
"alt-shift-tab": "editor::PreviousInlineCompletion",
|
||||
"ctrl-cmd-right": "editor::AcceptPartialInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && !edit_prediction",
|
||||
"context": "Editor && !inline_completion",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-tab": "editor::ShowEditPrediction"
|
||||
"alt-tab": "editor::ShowInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -350,7 +349,7 @@
|
||||
"cmd-}": "pane::ActivateNextItem",
|
||||
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"cmd-w": ["pane::CloseActiveItem", { "close_pinned": false }],
|
||||
"cmd-w": "pane::CloseActiveItem",
|
||||
"alt-cmd-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
|
||||
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
|
||||
"cmd-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
|
||||
@@ -414,15 +413,15 @@
|
||||
"cmd-k cmd-l": "editor::ToggleFold",
|
||||
"cmd-k cmd-[": "editor::FoldRecursive",
|
||||
"cmd-k cmd-]": "editor::UnfoldRecursive",
|
||||
"cmd-k cmd-1": ["editor::FoldAtLevel", 1],
|
||||
"cmd-k cmd-2": ["editor::FoldAtLevel", 2],
|
||||
"cmd-k cmd-3": ["editor::FoldAtLevel", 3],
|
||||
"cmd-k cmd-4": ["editor::FoldAtLevel", 4],
|
||||
"cmd-k cmd-5": ["editor::FoldAtLevel", 5],
|
||||
"cmd-k cmd-6": ["editor::FoldAtLevel", 6],
|
||||
"cmd-k cmd-7": ["editor::FoldAtLevel", 7],
|
||||
"cmd-k cmd-8": ["editor::FoldAtLevel", 8],
|
||||
"cmd-k cmd-9": ["editor::FoldAtLevel", 9],
|
||||
"cmd-k cmd-1": ["editor::FoldAtLevel", { "level": 1 }],
|
||||
"cmd-k cmd-2": ["editor::FoldAtLevel", { "level": 2 }],
|
||||
"cmd-k cmd-3": ["editor::FoldAtLevel", { "level": 3 }],
|
||||
"cmd-k cmd-4": ["editor::FoldAtLevel", { "level": 4 }],
|
||||
"cmd-k cmd-5": ["editor::FoldAtLevel", { "level": 5 }],
|
||||
"cmd-k cmd-6": ["editor::FoldAtLevel", { "level": 6 }],
|
||||
"cmd-k cmd-7": ["editor::FoldAtLevel", { "level": 7 }],
|
||||
"cmd-k cmd-8": ["editor::FoldAtLevel", { "level": 8 }],
|
||||
"cmd-k cmd-9": ["editor::FoldAtLevel", { "level": 9 }],
|
||||
"cmd-k cmd-0": "editor::FoldAll",
|
||||
"cmd-k cmd-j": "editor::UnfoldAll",
|
||||
// Using `ctrl-space` in Zed requires disabling the macOS global shortcut.
|
||||
@@ -510,14 +509,14 @@
|
||||
"cmd-alt-s": "workspace::SaveAll",
|
||||
"cmd-k m": "language_selector::Toggle",
|
||||
"escape": "workspace::Unfollow",
|
||||
"cmd-k cmd-left": "workspace::ActivatePaneLeft",
|
||||
"cmd-k cmd-right": "workspace::ActivatePaneRight",
|
||||
"cmd-k cmd-up": "workspace::ActivatePaneUp",
|
||||
"cmd-k cmd-down": "workspace::ActivatePaneDown",
|
||||
"cmd-k shift-left": "workspace::SwapPaneLeft",
|
||||
"cmd-k shift-right": "workspace::SwapPaneRight",
|
||||
"cmd-k shift-up": "workspace::SwapPaneUp",
|
||||
"cmd-k shift-down": "workspace::SwapPaneDown",
|
||||
"cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"cmd-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
||||
"cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
|
||||
"cmd-shift-x": "zed::Extensions"
|
||||
}
|
||||
},
|
||||
@@ -581,17 +580,17 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && edit_prediction",
|
||||
"context": "Editor && inline_completion",
|
||||
"bindings": {
|
||||
// Changing the modifier currently breaks accepting while you also an LSP completions menu open
|
||||
"alt-tab": "editor::AcceptEditPrediction"
|
||||
"alt-tab": "editor::AcceptInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
|
||||
"context": "Editor && inline_completion && !inline_completion_requires_modifier",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"tab": "editor::AcceptEditPrediction"
|
||||
"tab": "editor::AcceptInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -620,7 +619,8 @@
|
||||
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
|
||||
// TODO: Move this to a dock open action
|
||||
"cmd-shift-c": "collab_panel::ToggleFocus",
|
||||
"cmd-alt-i": "zed::DebugElements"
|
||||
"cmd-alt-i": "zed::DebugElements",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -633,8 +633,7 @@
|
||||
"cmd-shift-e": "pane::RevealInProjectPanel",
|
||||
"cmd-f8": "editor::GoToHunk",
|
||||
"cmd-shift-f8": "editor::GoToPrevHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
"ctrl-enter": "assistant::InlineAssist"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -716,6 +715,13 @@
|
||||
"space": "project_panel::Open"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && !CommitEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "git_panel::Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && ChangesList",
|
||||
"use_key_equivalents": true,
|
||||
@@ -728,21 +734,17 @@
|
||||
"space": "git::ToggleStaged",
|
||||
"cmd-shift-space": "git::StageAll",
|
||||
"ctrl-shift-space": "git::UnstageAll",
|
||||
"alt-down": "git_panel::FocusEditor",
|
||||
"tab": "git_panel::FocusEditor",
|
||||
"shift-tab": "git_panel::FocusEditor",
|
||||
"escape": "git_panel::ToggleFocus"
|
||||
"alt-down": "git_panel::FocusEditor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel > Editor",
|
||||
"context": "GitPanel && CommitEditor > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"cmd-enter": "git::Commit",
|
||||
"tab": "git_panel::FocusChanges",
|
||||
"shift-tab": "git_panel::FocusChanges",
|
||||
"alt-up": "git_panel::FocusChanges"
|
||||
"alt-up": "git_panel::FocusChanges",
|
||||
"escape": "git_panel::FocusChanges",
|
||||
"cmd-enter": "git::CommitChanges",
|
||||
"cmd-alt-enter": "git::CommitAllChanges"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
{
|
||||
"context": "VimControl && !menu",
|
||||
"bindings": {
|
||||
"i": ["vim::PushObject", { "around": false }],
|
||||
"a": ["vim::PushObject", { "around": true }],
|
||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||
"left": "vim::Left",
|
||||
"h": "vim::Left",
|
||||
"backspace": "vim::Backspace",
|
||||
@@ -54,10 +54,10 @@
|
||||
// "b": "vim::PreviousSubwordStart",
|
||||
// "e": "vim::NextSubwordEnd",
|
||||
// "g e": "vim::PreviousSubwordEnd",
|
||||
"shift-w": ["vim::NextWordStart", { "ignore_punctuation": true }],
|
||||
"shift-e": ["vim::NextWordEnd", { "ignore_punctuation": true }],
|
||||
"shift-b": ["vim::PreviousWordStart", { "ignore_punctuation": true }],
|
||||
"g shift-e": ["vim::PreviousWordEnd", { "ignore_punctuation": true }],
|
||||
"shift-w": ["vim::NextWordStart", { "ignorePunctuation": true }],
|
||||
"shift-e": ["vim::NextWordEnd", { "ignorePunctuation": true }],
|
||||
"shift-b": ["vim::PreviousWordStart", { "ignorePunctuation": true }],
|
||||
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
|
||||
"/": "vim::Search",
|
||||
"g /": "pane::DeploySearch",
|
||||
"?": ["vim::Search", { "backwards": true }],
|
||||
@@ -70,20 +70,20 @@
|
||||
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
|
||||
"] )": ["vim::UnmatchedForward", { "char": ")" }],
|
||||
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
|
||||
"f": ["vim::PushFindForward", { "before": false }],
|
||||
"t": ["vim::PushFindForward", { "before": true }],
|
||||
"shift-f": ["vim::PushFindBackward", { "after": false }],
|
||||
"shift-t": ["vim::PushFindBackward", { "after": true }],
|
||||
"m": "vim::PushMark",
|
||||
"'": ["vim::PushJump", { "line": true }],
|
||||
"`": ["vim::PushJump", { "line": false }],
|
||||
"f": ["vim::PushOperator", { "FindForward": { "before": false } }],
|
||||
"t": ["vim::PushOperator", { "FindForward": { "before": true } }],
|
||||
"shift-f": ["vim::PushOperator", { "FindBackward": { "after": false } }],
|
||||
"shift-t": ["vim::PushOperator", { "FindBackward": { "after": true } }],
|
||||
"m": ["vim::PushOperator", "Mark"],
|
||||
"'": ["vim::PushOperator", { "Jump": { "line": true } }],
|
||||
"`": ["vim::PushOperator", { "Jump": { "line": false } }],
|
||||
";": "vim::RepeatFind",
|
||||
",": "vim::RepeatFindReversed",
|
||||
"ctrl-o": "pane::GoBack",
|
||||
"ctrl-i": "pane::GoForward",
|
||||
"ctrl-]": "editor::GoToDefinition",
|
||||
"escape": "vim::SwitchToNormalMode",
|
||||
"ctrl-[": "vim::SwitchToNormalMode",
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||
"v": "vim::ToggleVisual",
|
||||
"shift-v": "vim::ToggleVisualLine",
|
||||
"ctrl-g": "vim::ShowLocation",
|
||||
@@ -102,7 +102,6 @@
|
||||
"ctrl-e": "vim::LineDown",
|
||||
"ctrl-y": "vim::LineUp",
|
||||
// "g" commands
|
||||
"g r": "vim::PushReplaceWithRegister",
|
||||
"g g": "vim::StartOfDocument",
|
||||
"g h": "editor::Hover",
|
||||
"g t": "pane::ActivateNextItem",
|
||||
@@ -125,17 +124,17 @@
|
||||
"g .": "editor::ToggleCodeActions", // zed specific
|
||||
"g shift-a": "editor::FindAllReferences", // zed specific
|
||||
"g space": "editor::OpenExcerpts", // zed specific
|
||||
"g *": ["vim::MoveToNext", { "partial_word": true }],
|
||||
"g #": ["vim::MoveToPrev", { "partial_word": true }],
|
||||
"g j": ["vim::Down", { "display_lines": true }],
|
||||
"g down": ["vim::Down", { "display_lines": true }],
|
||||
"g k": ["vim::Up", { "display_lines": true }],
|
||||
"g up": ["vim::Up", { "display_lines": true }],
|
||||
"g $": ["vim::EndOfLine", { "display_lines": true }],
|
||||
"g end": ["vim::EndOfLine", { "display_lines": true }],
|
||||
"g 0": ["vim::StartOfLine", { "display_lines": true }],
|
||||
"g home": ["vim::StartOfLine", { "display_lines": true }],
|
||||
"g ^": ["vim::FirstNonWhitespace", { "display_lines": true }],
|
||||
"g *": ["vim::MoveToNext", { "partialWord": true }],
|
||||
"g #": ["vim::MoveToPrev", { "partialWord": true }],
|
||||
"g j": ["vim::Down", { "displayLines": true }],
|
||||
"g down": ["vim::Down", { "displayLines": true }],
|
||||
"g k": ["vim::Up", { "displayLines": true }],
|
||||
"g up": ["vim::Up", { "displayLines": true }],
|
||||
"g $": ["vim::EndOfLine", { "displayLines": true }],
|
||||
"g end": ["vim::EndOfLine", { "displayLines": true }],
|
||||
"g 0": ["vim::StartOfLine", { "displayLines": true }],
|
||||
"g home": ["vim::StartOfLine", { "displayLines": true }],
|
||||
"g ^": ["vim::FirstNonWhitespace", { "displayLines": true }],
|
||||
"g v": "vim::RestoreVisualSelection",
|
||||
"g ]": "editor::GoToDiagnostic",
|
||||
"g [": "editor::GoToPrevDiagnostic",
|
||||
@@ -147,7 +146,7 @@
|
||||
"shift-l": "vim::WindowBottom",
|
||||
"q": "vim::ToggleRecord",
|
||||
"shift-q": "vim::ReplayLastRecording",
|
||||
"@": "vim::PushReplayRegister",
|
||||
"@": ["vim::PushOperator", "ReplayRegister"],
|
||||
// z commands
|
||||
"z enter": ["workspace::SendKeystrokes", "z t ^"],
|
||||
"z -": ["workspace::SendKeystrokes", "z b ^"],
|
||||
@@ -166,8 +165,8 @@
|
||||
"z f": "editor::FoldSelectedRanges",
|
||||
"z shift-m": "editor::FoldAll",
|
||||
"z shift-r": "editor::UnfoldAll",
|
||||
"shift-z shift-q": ["pane::CloseActiveItem", { "save_intent": "skip" }],
|
||||
"shift-z shift-z": ["pane::CloseActiveItem", { "save_intent": "save_all" }],
|
||||
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
|
||||
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
|
||||
// Count support
|
||||
"1": ["vim::Number", 1],
|
||||
"2": ["vim::Number", 2],
|
||||
@@ -194,13 +193,13 @@
|
||||
"escape": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
".": "vim::Repeat",
|
||||
"c": "vim::PushChange",
|
||||
"c": ["vim::PushOperator", "Change"],
|
||||
"shift-c": "vim::ChangeToEndOfLine",
|
||||
"d": "vim::PushDelete",
|
||||
"d": ["vim::PushOperator", "Delete"],
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"g shift-j": "vim::JoinLinesNoWhitespace",
|
||||
"y": "vim::PushYank",
|
||||
"y": ["vim::PushOperator", "Yank"],
|
||||
"shift-y": "vim::YankLine",
|
||||
"i": "vim::InsertBefore",
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
@@ -217,19 +216,19 @@
|
||||
"shift-p": ["vim::Paste", { "before": true }],
|
||||
"u": "vim::Undo",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"r": "vim::PushReplace",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
">": "vim::PushIndent",
|
||||
"<": "vim::PushOutdent",
|
||||
"=": "vim::PushAutoIndent",
|
||||
"!": "vim::PushShellCommand",
|
||||
"g u": "vim::PushLowercase",
|
||||
"g shift-u": "vim::PushUppercase",
|
||||
"g ~": "vim::PushOppositeCase",
|
||||
"\"": "vim::PushRegister",
|
||||
"g w": "vim::PushRewrap",
|
||||
"g q": "vim::PushRewrap",
|
||||
">": ["vim::PushOperator", "Indent"],
|
||||
"<": ["vim::PushOperator", "Outdent"],
|
||||
"=": ["vim::PushOperator", "AutoIndent"],
|
||||
"!": ["vim::PushOperator", "ShellCommand"],
|
||||
"g u": ["vim::PushOperator", "Lowercase"],
|
||||
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
||||
"g ~": ["vim::PushOperator", "OppositeCase"],
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
"g w": ["vim::PushOperator", "Rewrap"],
|
||||
"g q": ["vim::PushOperator", "Rewrap"],
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"insert": "vim::InsertBefore",
|
||||
@@ -240,7 +239,7 @@
|
||||
"[ d": "editor::GoToPrevDiagnostic",
|
||||
"] c": "editor::GoToHunk",
|
||||
"[ c": "editor::GoToPrevHunk",
|
||||
"g c": "vim::PushToggleComments"
|
||||
"g c": ["vim::PushOperator", "ToggleComments"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -265,14 +264,14 @@
|
||||
"y": "vim::VisualYank",
|
||||
"shift-y": "vim::VisualYankLine",
|
||||
"p": "vim::Paste",
|
||||
"shift-p": ["vim::Paste", { "preserve_clipboard": true }],
|
||||
"shift-p": ["vim::Paste", { "preserveClipboard": true }],
|
||||
"c": "vim::Substitute",
|
||||
"s": "vim::Substitute",
|
||||
"shift-r": "vim::SubstituteLine",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
"~": "vim::ChangeCase",
|
||||
"*": ["vim::MoveToNext", { "partial_word": true }],
|
||||
"#": ["vim::MoveToPrev", { "partial_word": true }],
|
||||
"*": ["vim::MoveToNext", { "partialWord": true }],
|
||||
"#": ["vim::MoveToPrev", { "partialWord": true }],
|
||||
"ctrl-a": "vim::Increment",
|
||||
"ctrl-x": "vim::Decrement",
|
||||
"g ctrl-a": ["vim::Increment", { "step": true }],
|
||||
@@ -283,19 +282,19 @@
|
||||
"g shift-a": "vim::VisualInsertEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"g shift-j": "vim::JoinLinesNoWhitespace",
|
||||
"r": "vim::PushReplace",
|
||||
"ctrl-c": "vim::SwitchToNormalMode",
|
||||
"ctrl-[": "vim::SwitchToNormalMode",
|
||||
"escape": "vim::SwitchToNormalMode",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"=": "vim::AutoIndent",
|
||||
"!": "vim::ShellCommand",
|
||||
"i": ["vim::PushObject", { "around": false }],
|
||||
"a": ["vim::PushObject", { "around": true }],
|
||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||
"g c": "vim::ToggleComments",
|
||||
"g q": "vim::Rewrap",
|
||||
"\"": "vim::PushRegister",
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode"
|
||||
@@ -310,19 +309,19 @@
|
||||
"ctrl-x": null,
|
||||
"ctrl-x ctrl-o": "editor::ShowCompletions",
|
||||
"ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
|
||||
"ctrl-x ctrl-c": "editor::ShowEditPrediction", // zed specific
|
||||
"ctrl-x ctrl-c": "editor::ShowInlineCompletion", // zed specific
|
||||
"ctrl-x ctrl-l": "editor::ToggleCodeActions", // zed specific
|
||||
"ctrl-x ctrl-z": "editor::Cancel",
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||
"ctrl-t": "vim::Indent",
|
||||
"ctrl-d": "vim::Outdent",
|
||||
"ctrl-k": ["vim::PushDigraph", {}],
|
||||
"ctrl-v": ["vim::PushLiteral", {}],
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
|
||||
"ctrl-q": ["vim::PushLiteral", {}],
|
||||
"ctrl-shift-q": ["vim::PushLiteral", {}],
|
||||
"ctrl-r": "vim::PushRegister",
|
||||
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-r": ["vim::PushOperator", "Register"],
|
||||
"insert": "vim::ToggleReplace",
|
||||
"ctrl-o": "vim::TemporaryNormal"
|
||||
}
|
||||
@@ -357,11 +356,11 @@
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
"ctrl-[": "vim::NormalBefore",
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-k": ["vim::PushDigraph", {}],
|
||||
"ctrl-v": ["vim::PushLiteral", {}],
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
|
||||
"ctrl-q": ["vim::PushLiteral", {}],
|
||||
"ctrl-shift-q": ["vim::PushLiteral", {}],
|
||||
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"backspace": "vim::UndoReplace",
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
@@ -376,15 +375,9 @@
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
"ctrl-[": "vim::ClearOperators",
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-k": ["vim::PushDigraph", {}],
|
||||
"ctrl-v": ["vim::PushLiteral", {}],
|
||||
"ctrl-q": ["vim::PushLiteral", {}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == waiting && (vim_operator == ys || vim_operator == cs)",
|
||||
"bindings": {
|
||||
"escape": "vim::SwitchToNormalMode"
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-q": ["vim::PushOperator", { "Literal": {} }]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -400,10 +393,10 @@
|
||||
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
|
||||
"bindings": {
|
||||
"w": "vim::Word",
|
||||
"shift-w": ["vim::Word", { "ignore_punctuation": true }],
|
||||
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
|
||||
// Subword TextObject
|
||||
// "w": "vim::Subword",
|
||||
// "shift-w": ["vim::Subword", { "ignore_punctuation": true }],
|
||||
// "shift-w": ["vim::Subword", { "ignorePunctuation": true }],
|
||||
"t": "vim::Tag",
|
||||
"s": "vim::Sentence",
|
||||
"p": "vim::Paragraph",
|
||||
@@ -426,7 +419,7 @@
|
||||
">": "vim::AngleBrackets",
|
||||
"a": "vim::Argument",
|
||||
"i": "vim::IndentObj",
|
||||
"shift-i": ["vim::IndentObj", { "include_below": true }],
|
||||
"shift-i": ["vim::IndentObj", { "includeBelow": true }],
|
||||
"f": "vim::Method",
|
||||
"c": "vim::Class",
|
||||
"e": "vim::EntireFile"
|
||||
@@ -437,14 +430,14 @@
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine",
|
||||
"d": "editor::Rename", // zed specific
|
||||
"s": ["vim::PushChangeSurrounds", {}]
|
||||
"s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == d",
|
||||
"bindings": {
|
||||
"d": "vim::CurrentLine",
|
||||
"s": "vim::PushDeleteSurrounds",
|
||||
"s": ["vim::PushOperator", "DeleteSurrounds"],
|
||||
"o": "editor::ToggleSelectedDiffHunks", // "d o"
|
||||
"p": "editor::RevertSelectedHunks" // "d p"
|
||||
}
|
||||
@@ -483,7 +476,7 @@
|
||||
"context": "vim_operator == y",
|
||||
"bindings": {
|
||||
"y": "vim::CurrentLine",
|
||||
"s": ["vim::PushAddSurrounds", {}]
|
||||
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -573,34 +566,34 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||
"bindings": {
|
||||
// window related commands (ctrl-w X)
|
||||
"ctrl-w": null,
|
||||
"ctrl-w left": "workspace::ActivatePaneLeft",
|
||||
"ctrl-w right": "workspace::ActivatePaneRight",
|
||||
"ctrl-w up": "workspace::ActivatePaneUp",
|
||||
"ctrl-w down": "workspace::ActivatePaneDown",
|
||||
"ctrl-w ctrl-h": "workspace::ActivatePaneLeft",
|
||||
"ctrl-w ctrl-l": "workspace::ActivatePaneRight",
|
||||
"ctrl-w ctrl-k": "workspace::ActivatePaneUp",
|
||||
"ctrl-w ctrl-j": "workspace::ActivatePaneDown",
|
||||
"ctrl-w h": "workspace::ActivatePaneLeft",
|
||||
"ctrl-w l": "workspace::ActivatePaneRight",
|
||||
"ctrl-w k": "workspace::ActivatePaneUp",
|
||||
"ctrl-w j": "workspace::ActivatePaneDown",
|
||||
"ctrl-w shift-left": "workspace::SwapPaneLeft",
|
||||
"ctrl-w shift-right": "workspace::SwapPaneRight",
|
||||
"ctrl-w shift-up": "workspace::SwapPaneUp",
|
||||
"ctrl-w shift-down": "workspace::SwapPaneDown",
|
||||
"ctrl-w shift-h": "workspace::SwapPaneLeft",
|
||||
"ctrl-w shift-l": "workspace::SwapPaneRight",
|
||||
"ctrl-w shift-k": "workspace::SwapPaneUp",
|
||||
"ctrl-w shift-j": "workspace::SwapPaneDown",
|
||||
"ctrl-w >": "vim::ResizePaneRight",
|
||||
"ctrl-w <": "vim::ResizePaneLeft",
|
||||
"ctrl-w -": "vim::ResizePaneDown",
|
||||
"ctrl-w +": "vim::ResizePaneUp",
|
||||
"ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
||||
"ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"ctrl-w shift-down": ["workspace::SwapPaneInDirection", "Down"],
|
||||
"ctrl-w shift-h": ["workspace::SwapPaneInDirection", "Left"],
|
||||
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
|
||||
"ctrl-w >": ["vim::ResizePane", "Widen"],
|
||||
"ctrl-w <": ["vim::ResizePane", "Narrow"],
|
||||
"ctrl-w -": ["vim::ResizePane", "Shorten"],
|
||||
"ctrl-w +": ["vim::ResizePane", "Lengthen"],
|
||||
"ctrl-w _": "vim::MaximizePane",
|
||||
"ctrl-w =": "vim::ResetPaneSizes",
|
||||
"ctrl-w g t": "pane::ActivateNextItem",
|
||||
@@ -631,7 +624,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
|
||||
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
"g /": "pane::DeploySearch"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
// Features that can be globally enabled or disabled
|
||||
"features": {
|
||||
// Which edit prediction provider to use.
|
||||
"edit_prediction_provider": "copilot"
|
||||
"inline_completion_provider": "copilot"
|
||||
},
|
||||
// The name of a font to use for rendering text in the editor
|
||||
"buffer_font_family": "Zed Plex Mono",
|
||||
@@ -93,13 +93,6 @@
|
||||
// workspace when the centered layout is used.
|
||||
"right_padding": 0.2
|
||||
},
|
||||
// All settings related to the image viewer.
|
||||
"image_viewer": {
|
||||
// The unit for image file sizes.
|
||||
// By default we're setting it to binary.
|
||||
// The second option is decimal.
|
||||
"unit": "binary"
|
||||
},
|
||||
// The key to use for adding multiple cursors
|
||||
// Currently "alt" or "cmd_or_ctrl" (also aliased as
|
||||
// "cmd" and "ctrl") are supported.
|
||||
@@ -170,7 +163,7 @@
|
||||
"show_signature_help_after_edits": false,
|
||||
/// Whether to show the edit predictions next to the completions provided by a language server.
|
||||
/// Only has an effect if edit prediction provider supports it.
|
||||
"show_edit_predictions_in_menu": true,
|
||||
"show_inline_completions_in_menu": true,
|
||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
||||
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
|
||||
@@ -204,11 +197,11 @@
|
||||
// no matter how they were inserted.
|
||||
"always_treat_brackets_as_autoclosed": false,
|
||||
// Controls whether edit predictions are shown immediately (true)
|
||||
// or manually by triggering `editor::ShowEditPrediction` (false).
|
||||
"show_edit_predictions": true,
|
||||
// or manually by triggering `editor::ShowInlineCompletion` (false).
|
||||
"show_inline_completions": true,
|
||||
// Controls whether edit predictions are shown in a given language scope.
|
||||
// Example: ["string", "comment"]
|
||||
"edit_predictions_disabled_in": [],
|
||||
"inline_completions_disabled_in": [],
|
||||
// Whether to show tabs and spaces in the editor.
|
||||
// This setting can take four values:
|
||||
//
|
||||
@@ -781,10 +774,8 @@
|
||||
// 2. Load direnv configuration through the shell hook, works for POSIX shells and fish.
|
||||
// "load_direnv": "shell_hook"
|
||||
"load_direnv": "direct",
|
||||
"edit_predictions": {
|
||||
"inline_completions": {
|
||||
// A list of globs representing files that edit predictions should be disabled for.
|
||||
// There's a sensible default list of globs already included.
|
||||
// Any addition to this list will be merged with the default list.
|
||||
"disabled_globs": [
|
||||
"**/.env*",
|
||||
"**/*.pem",
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
"terminal.ansi.bright_green": "#4d6140ff",
|
||||
"terminal.ansi.dim_green": "#d1e0bfff",
|
||||
"terminal.ansi.yellow": "#dec184ff",
|
||||
"terminal.ansi.bright_yellow": "#e5c07bff",
|
||||
"terminal.ansi.bright_yellow": "#786441ff",
|
||||
"terminal.ansi.dim_yellow": "#f1dfc1ff",
|
||||
"terminal.ansi.blue": "#74ade8ff",
|
||||
"terminal.ansi.bright_blue": "#385378ff",
|
||||
@@ -457,7 +457,7 @@
|
||||
"terminal.ansi.bright_green": "#b2cfa9ff",
|
||||
"terminal.ansi.dim_green": "#354d2eff",
|
||||
"terminal.ansi.yellow": "#dec184ff",
|
||||
"terminal.ansi.bright_yellow": "#826221ff",
|
||||
"terminal.ansi.bright_yellow": "#f1dfc1ff",
|
||||
"terminal.ansi.dim_yellow": "#786441ff",
|
||||
"terminal.ansi.blue": "#5c78e2ff",
|
||||
"terminal.ansi.bright_blue": "#b5baf2ff",
|
||||
|
||||
@@ -250,10 +250,10 @@ impl AssistantPanel {
|
||||
)
|
||||
.child(
|
||||
PopoverMenu::new("assistant-panel-popover-menu")
|
||||
.trigger_with_tooltip(
|
||||
.trigger(
|
||||
IconButton::new("menu", IconName::EllipsisVertical)
|
||||
.icon_size(IconSize::Small),
|
||||
Tooltip::text("Toggle Assistant Menu"),
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Toggle Assistant Menu")),
|
||||
)
|
||||
.menu(move |window, cx| {
|
||||
let zoom_label = if _pane.read(cx).is_zoomed() {
|
||||
|
||||
@@ -1595,22 +1595,22 @@ impl Render for PromptEditor {
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted),
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
))
|
||||
.map(|el| {
|
||||
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
|
||||
|
||||
@@ -646,22 +646,22 @@ impl Render for PromptEditor {
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted),
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
))
|
||||
.children(
|
||||
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
|
||||
|
||||
@@ -74,16 +74,16 @@ impl Render for AssistantModelSelector {
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.with_handle(self.menu_handle.clone())
|
||||
}
|
||||
|
||||
@@ -660,11 +660,11 @@ impl AssistantPanel {
|
||||
.gap(DynamicSpacing::Base02.rems(cx))
|
||||
.child(
|
||||
PopoverMenu::new("assistant-toolbar-new-popover-menu")
|
||||
.trigger_with_tooltip(
|
||||
.trigger(
|
||||
IconButton::new("new", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle),
|
||||
Tooltip::text("New…"),
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(Tooltip::text("New…")),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(self.new_item_context_menu_handle.clone())
|
||||
@@ -677,11 +677,11 @@ impl AssistantPanel {
|
||||
)
|
||||
.child(
|
||||
PopoverMenu::new("assistant-toolbar-history-popover-menu")
|
||||
.trigger_with_tooltip(
|
||||
.trigger(
|
||||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle),
|
||||
Tooltip::text("History…"),
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(Tooltip::text("History…")),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(self.open_history_context_menu_handle.clone())
|
||||
|
||||
@@ -411,22 +411,22 @@ impl Render for ContextStrip {
|
||||
|
||||
Some(context_picker.clone())
|
||||
})
|
||||
.trigger_with_tooltip(
|
||||
.trigger(
|
||||
IconButton::new("add-context", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ui::ButtonStyle::Filled),
|
||||
{
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Add Context",
|
||||
&ToggleContextPicker,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
},
|
||||
.style(ui::ButtonStyle::Filled)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Add Context",
|
||||
&ToggleContextPicker,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.attach(gpui::Corner::TopLeft)
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
|
||||
@@ -832,13 +832,12 @@ impl ContextEditor {
|
||||
let render_block: RenderBlock = Arc::new({
|
||||
let this = this.clone();
|
||||
let patch_range = range.clone();
|
||||
move |cx: &mut BlockContext| {
|
||||
move |cx: &mut BlockContext<'_, '_>| {
|
||||
let max_width = cx.max_width;
|
||||
let gutter_width = cx.gutter_dimensions.full_width();
|
||||
let block_id = cx.block_id;
|
||||
let selected = cx.selected;
|
||||
let window = &mut cx.window;
|
||||
this.update(cx.app, |this, cx| {
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.render_patch_block(
|
||||
patch_range.clone(),
|
||||
max_width,
|
||||
@@ -2359,8 +2358,8 @@ impl ContextEditor {
|
||||
.icon(IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start),
|
||||
Tooltip::text("Type / to insert via keyboard"),
|
||||
.icon_position(IconPosition::Start)
|
||||
.tooltip(Tooltip::text("Type / to insert via keyboard")),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3323,10 +3322,10 @@ impl Render for ContextEditorToolbarItem {
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action("Change Model", &ToggleModelSelector, window, cx)
|
||||
},
|
||||
)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Change Model", &ToggleModelSelector, window, cx)
|
||||
}),
|
||||
)
|
||||
.with_handle(self.language_model_selector_menu_handle.clone()),
|
||||
)
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use gpui::{AnyElement, AnyView, DismissEvent, SharedString, Task, WeakEntity};
|
||||
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakEntity};
|
||||
use picker::{Picker, PickerDelegate, PickerEditorPosition};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
|
||||
|
||||
use crate::context_editor::ContextEditor;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub(super) struct SlashCommandSelector<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
|
||||
working_set: Arc<SlashCommandWorkingSet>,
|
||||
active_context_editor: WeakEntity<ContextEditor>,
|
||||
trigger: T,
|
||||
tooltip: TT,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -53,22 +48,16 @@ pub(crate) struct SlashCommandDelegate {
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
impl<T, TT> SlashCommandSelector<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
impl<T: PopoverTrigger> SlashCommandSelector<T> {
|
||||
pub(crate) fn new(
|
||||
working_set: Arc<SlashCommandWorkingSet>,
|
||||
active_context_editor: WeakEntity<ContextEditor>,
|
||||
trigger: T,
|
||||
tooltip: TT,
|
||||
) -> Self {
|
||||
SlashCommandSelector {
|
||||
working_set,
|
||||
active_context_editor,
|
||||
trigger,
|
||||
tooltip,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,11 +241,7 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, TT> RenderOnce for SlashCommandSelector<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let all_models = self
|
||||
.working_set
|
||||
@@ -337,7 +322,7 @@ where
|
||||
.ok();
|
||||
PopoverMenu::new("model-switcher")
|
||||
.menu(move |_window, _cx| Some(picker_view.clone()))
|
||||
.trigger_with_tooltip(self.trigger, self.tooltip)
|
||||
.trigger(self.trigger)
|
||||
.attach(gpui::Corner::TopLeft)
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
|
||||
@@ -131,7 +131,7 @@ worktree = { workspace = true, features = ["test-support"] }
|
||||
livekit_client_macos = { workspace = true, features = ["test-support"] }
|
||||
|
||||
[target.'cfg(not(target_os = "macos"))'.dev-dependencies]
|
||||
livekit_client = { workspace = true, features = ["test-support"] }
|
||||
livekit_client = {workspace = true, features = ["test-support"] }
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["async-stripe"]
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "component"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/component.rs"
|
||||
|
||||
[dependencies]
|
||||
collections.workspace = true
|
||||
gpui.workspace = true
|
||||
linkme.workspace = true
|
||||
once_cell = "1.20.3"
|
||||
parking_lot.workspace = true
|
||||
theme.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
@@ -1,305 +0,0 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{div, prelude::*, AnyElement, App, IntoElement, RenderOnce, SharedString, Window};
|
||||
use linkme::distributed_slice;
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
use theme::ActiveTheme;
|
||||
|
||||
pub trait Component {
|
||||
fn scope() -> Option<&'static str>;
|
||||
fn name() -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
fn description() -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ComponentPreview: Component {
|
||||
fn preview(_window: &mut Window, _cx: &App) -> AnyElement;
|
||||
}
|
||||
|
||||
#[distributed_slice]
|
||||
pub static __ALL_COMPONENTS: [fn()] = [..];
|
||||
|
||||
#[distributed_slice]
|
||||
pub static __ALL_PREVIEWS: [fn()] = [..];
|
||||
|
||||
pub static COMPONENT_DATA: Lazy<RwLock<ComponentRegistry>> =
|
||||
Lazy::new(|| RwLock::new(ComponentRegistry::new()));
|
||||
|
||||
pub struct ComponentRegistry {
|
||||
components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>,
|
||||
previews: HashMap<&'static str, fn(&mut Window, &App) -> AnyElement>,
|
||||
}
|
||||
|
||||
impl ComponentRegistry {
|
||||
fn new() -> Self {
|
||||
ComponentRegistry {
|
||||
components: Vec::new(),
|
||||
previews: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
let component_fns: Vec<_> = __ALL_COMPONENTS.iter().cloned().collect();
|
||||
let preview_fns: Vec<_> = __ALL_PREVIEWS.iter().cloned().collect();
|
||||
|
||||
for f in component_fns {
|
||||
f();
|
||||
}
|
||||
for f in preview_fns {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_component<T: Component>() {
|
||||
let component_data = (T::scope(), T::name(), T::description());
|
||||
COMPONENT_DATA.write().components.push(component_data);
|
||||
}
|
||||
|
||||
pub fn register_preview<T: ComponentPreview>() {
|
||||
let preview_data = (T::name(), T::preview as fn(&mut Window, &App) -> AnyElement);
|
||||
COMPONENT_DATA
|
||||
.write()
|
||||
.previews
|
||||
.insert(preview_data.0, preview_data.1);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ComponentId(pub &'static str);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ComponentMetadata {
|
||||
name: SharedString,
|
||||
scope: Option<SharedString>,
|
||||
description: Option<SharedString>,
|
||||
preview: Option<fn(&mut Window, &App) -> AnyElement>,
|
||||
}
|
||||
|
||||
impl ComponentMetadata {
|
||||
pub fn name(&self) -> SharedString {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
pub fn scope(&self) -> Option<SharedString> {
|
||||
self.scope.clone()
|
||||
}
|
||||
|
||||
pub fn description(&self) -> Option<SharedString> {
|
||||
self.description.clone()
|
||||
}
|
||||
|
||||
pub fn preview(&self) -> Option<fn(&mut Window, &App) -> AnyElement> {
|
||||
self.preview
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AllComponents(pub HashMap<ComponentId, ComponentMetadata>);
|
||||
|
||||
impl AllComponents {
|
||||
pub fn new() -> Self {
|
||||
AllComponents(HashMap::default())
|
||||
}
|
||||
|
||||
/// Returns all components with previews
|
||||
pub fn all_previews(&self) -> Vec<&ComponentMetadata> {
|
||||
self.0.values().filter(|c| c.preview.is_some()).collect()
|
||||
}
|
||||
|
||||
/// Returns all components with previews sorted by name
|
||||
pub fn all_previews_sorted(&self) -> Vec<ComponentMetadata> {
|
||||
let mut previews: Vec<ComponentMetadata> =
|
||||
self.all_previews().into_iter().cloned().collect();
|
||||
previews.sort_by_key(|a| a.name());
|
||||
previews
|
||||
}
|
||||
|
||||
/// Returns all components
|
||||
pub fn all(&self) -> Vec<&ComponentMetadata> {
|
||||
self.0.values().collect()
|
||||
}
|
||||
|
||||
/// Returns all components sorted by name
|
||||
pub fn all_sorted(&self) -> Vec<ComponentMetadata> {
|
||||
let mut components: Vec<ComponentMetadata> = self.all().into_iter().cloned().collect();
|
||||
components.sort_by_key(|a| a.name());
|
||||
components
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AllComponents {
|
||||
type Target = HashMap<ComponentId, ComponentMetadata>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for AllComponents {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn components() -> AllComponents {
|
||||
let data = COMPONENT_DATA.read();
|
||||
let mut all_components = AllComponents::new();
|
||||
|
||||
for &(scope, name, description) in &data.components {
|
||||
let scope = scope.map(Into::into);
|
||||
let preview = data.previews.get(name).cloned();
|
||||
all_components.insert(
|
||||
ComponentId(name),
|
||||
ComponentMetadata {
|
||||
name: name.into(),
|
||||
scope,
|
||||
description: description.map(Into::into),
|
||||
preview,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
all_components
|
||||
}
|
||||
|
||||
/// Which side of the preview to show labels on
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExampleLabelSide {
|
||||
/// Left side
|
||||
Left,
|
||||
/// Right side
|
||||
Right,
|
||||
#[default]
|
||||
/// Top side
|
||||
Top,
|
||||
/// Bottom side
|
||||
Bottom,
|
||||
}
|
||||
|
||||
/// A single example of a component.
|
||||
#[derive(IntoElement)]
|
||||
pub struct ComponentExample {
|
||||
variant_name: SharedString,
|
||||
element: AnyElement,
|
||||
label_side: ExampleLabelSide,
|
||||
grow: bool,
|
||||
}
|
||||
|
||||
impl RenderOnce for ComponentExample {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let base = div().flex();
|
||||
|
||||
let base = match self.label_side {
|
||||
ExampleLabelSide::Right => base.flex_row(),
|
||||
ExampleLabelSide::Left => base.flex_row_reverse(),
|
||||
ExampleLabelSide::Bottom => base.flex_col(),
|
||||
ExampleLabelSide::Top => base.flex_col_reverse(),
|
||||
};
|
||||
|
||||
base.gap_1()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.when(self.grow, |this| this.flex_1())
|
||||
.child(self.element)
|
||||
.child(self.variant_name)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentExample {
|
||||
/// Create a new example with the given variant name and example value.
|
||||
pub fn new(variant_name: impl Into<SharedString>, element: AnyElement) -> Self {
|
||||
Self {
|
||||
variant_name: variant_name.into(),
|
||||
element,
|
||||
label_side: ExampleLabelSide::default(),
|
||||
grow: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the example to grow to fill the available horizontal space.
|
||||
pub fn grow(mut self) -> Self {
|
||||
self.grow = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A group of component examples.
|
||||
#[derive(IntoElement)]
|
||||
pub struct ComponentExampleGroup {
|
||||
pub title: Option<SharedString>,
|
||||
pub examples: Vec<ComponentExample>,
|
||||
pub grow: bool,
|
||||
}
|
||||
|
||||
impl RenderOnce for ComponentExampleGroup {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
div()
|
||||
.flex_col()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.when(self.grow, |this| this.w_full().flex_1())
|
||||
.when_some(self.title, |this, title| this.gap_4().child(title))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_start()
|
||||
.w_full()
|
||||
.gap_6()
|
||||
.children(self.examples)
|
||||
.into_any_element(),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentExampleGroup {
|
||||
/// Create a new group of examples with the given title.
|
||||
pub fn new(examples: Vec<ComponentExample>) -> Self {
|
||||
Self {
|
||||
title: None,
|
||||
examples,
|
||||
grow: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new group of examples with the given title.
|
||||
pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample>) -> Self {
|
||||
Self {
|
||||
title: Some(title.into()),
|
||||
examples,
|
||||
grow: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the group to grow to fill the available horizontal space.
|
||||
pub fn grow(mut self) -> Self {
|
||||
self.grow = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a single example
|
||||
pub fn single_example(
|
||||
variant_name: impl Into<SharedString>,
|
||||
example: AnyElement,
|
||||
) -> ComponentExample {
|
||||
ComponentExample::new(variant_name, example)
|
||||
}
|
||||
|
||||
/// Create a group of examples without a title
|
||||
pub fn example_group(examples: Vec<ComponentExample>) -> ComponentExampleGroup {
|
||||
ComponentExampleGroup::new(examples)
|
||||
}
|
||||
|
||||
/// Create a group of examples with a title
|
||||
pub fn example_group_with_title(
|
||||
title: impl Into<SharedString>,
|
||||
examples: Vec<ComponentExample>,
|
||||
) -> ComponentExampleGroup {
|
||||
ComponentExampleGroup::with_title(title, examples)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,178 +0,0 @@
|
||||
//! # Component Preview
|
||||
//!
|
||||
//! A view for exploring Zed components.
|
||||
|
||||
use component::{components, ComponentMetadata};
|
||||
use gpui::{prelude::*, App, EventEmitter, FocusHandle, Focusable, Window};
|
||||
use ui::prelude::*;
|
||||
|
||||
use workspace::{item::ItemEvent, Item, Workspace, WorkspaceId};
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.observe_new(|workspace: &mut Workspace, _, _cx| {
|
||||
workspace.register_action(
|
||||
|workspace, _: &workspace::OpenComponentPreview, window, cx| {
|
||||
let component_preview = cx.new(ComponentPreview::new);
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(component_preview),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
struct ComponentPreview {
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl ComponentPreview {
|
||||
pub fn new(cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_sidebar(&self, _window: &Window, _cx: &Context<Self>) -> impl IntoElement {
|
||||
let components = components().all_sorted();
|
||||
let sorted_components = components.clone();
|
||||
|
||||
v_flex().gap_px().p_1().children(
|
||||
sorted_components
|
||||
.into_iter()
|
||||
.map(|component| self.render_sidebar_entry(&component, _cx)),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_sidebar_entry(
|
||||
&self,
|
||||
component: &ComponentMetadata,
|
||||
_cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
h_flex()
|
||||
.w_40()
|
||||
.px_1p5()
|
||||
.py_1()
|
||||
.child(component.name().clone())
|
||||
}
|
||||
|
||||
fn render_preview(
|
||||
&self,
|
||||
component: &ComponentMetadata,
|
||||
window: &mut Window,
|
||||
cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let name = component.name();
|
||||
let scope = component.scope();
|
||||
|
||||
let description = component.description();
|
||||
|
||||
v_group()
|
||||
.w_full()
|
||||
.gap_4()
|
||||
.p_8()
|
||||
.rounded_md()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.text_xl()
|
||||
.child(div().child(name))
|
||||
.when_some(scope, |this, scope| {
|
||||
this.child(div().opacity(0.5).child(format!("({})", scope)))
|
||||
}),
|
||||
)
|
||||
.when_some(description, |this, description| {
|
||||
this.child(
|
||||
div()
|
||||
.text_ui_sm(cx)
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.max_w(px(600.0))
|
||||
.child(description),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when_some(component.preview(), |this, preview| {
|
||||
this.child(preview(window, cx))
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_previews(&self, window: &mut Window, cx: &Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.id("component-previews")
|
||||
.size_full()
|
||||
.overflow_y_scroll()
|
||||
.p_4()
|
||||
.gap_2()
|
||||
.children(
|
||||
components()
|
||||
.all_previews_sorted()
|
||||
.iter()
|
||||
.map(|component| self.render_preview(component, window, cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ComponentPreview {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.id("component-preview")
|
||||
.key_context("ComponentPreview")
|
||||
.items_start()
|
||||
.overflow_hidden()
|
||||
.size_full()
|
||||
.max_h_full()
|
||||
.track_focus(&self.focus_handle)
|
||||
.px_2()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(self.render_sidebar(window, cx))
|
||||
.child(self.render_previews(window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ItemEvent> for ComponentPreview {}
|
||||
|
||||
impl Focusable for ComponentPreview {
|
||||
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for ComponentPreview {
|
||||
type Event = ItemEvent;
|
||||
|
||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
||||
Some("Component Preview".into())
|
||||
}
|
||||
|
||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn show_toolbar(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn clone_on_split(
|
||||
&self,
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<gpui::Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Some(cx.new(Self::new))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
|
||||
f(*event)
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ use gpui::{
|
||||
use http_client::github::get_release_by_tag_name;
|
||||
use http_client::HttpClient;
|
||||
use language::{
|
||||
language_settings::{all_language_settings, language_settings, EditPredictionProvider},
|
||||
language_settings::{all_language_settings, language_settings, InlineCompletionProvider},
|
||||
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
|
||||
ToPointUtf16,
|
||||
};
|
||||
@@ -368,8 +368,8 @@ impl Copilot {
|
||||
let server_id = self.server_id;
|
||||
let http = self.http.clone();
|
||||
let node_runtime = self.node_runtime.clone();
|
||||
if all_language_settings(None, cx).edit_predictions.provider
|
||||
== EditPredictionProvider::Copilot
|
||||
if all_language_settings(None, cx).inline_completions.provider
|
||||
== InlineCompletionProvider::Copilot
|
||||
{
|
||||
if matches!(self.server, CopilotServer::Disabled) {
|
||||
let start_task = cx
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{Completion, Copilot};
|
||||
use anyhow::Result;
|
||||
use gpui::{App, Context, Entity, EntityId, Task};
|
||||
use inline_completion::{Direction, EditPredictionProvider, InlineCompletion};
|
||||
use inline_completion::{Direction, InlineCompletion, InlineCompletionProvider};
|
||||
use language::{language_settings::AllLanguageSettings, Buffer, OffsetRangeExt, ToOffset};
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
@@ -48,7 +48,7 @@ impl CopilotCompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl EditPredictionProvider for CopilotCompletionProvider {
|
||||
impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
fn name() -> &'static str {
|
||||
"copilot"
|
||||
}
|
||||
@@ -61,6 +61,10 @@ impl EditPredictionProvider for CopilotCompletionProvider {
|
||||
false
|
||||
}
|
||||
|
||||
fn show_completions_in_normal_mode() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_refreshing(&self) -> bool {
|
||||
self.pending_refresh.is_some()
|
||||
}
|
||||
@@ -242,7 +246,6 @@ impl EditPredictionProvider for CopilotCompletionProvider {
|
||||
} else {
|
||||
let position = cursor_position.bias_right(buffer);
|
||||
Some(InlineCompletion {
|
||||
id: None,
|
||||
edits: vec![(position..position, completion_text.into())],
|
||||
edit_preview: None,
|
||||
})
|
||||
@@ -302,7 +305,7 @@ mod tests {
|
||||
.await;
|
||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
|
||||
});
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
@@ -437,8 +440,8 @@ mod tests {
|
||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
|
||||
|
||||
// AcceptEditPrediction when there is an active suggestion inserts it.
|
||||
editor.accept_edit_prediction(&Default::default(), window, cx);
|
||||
// AcceptInlineCompletion when there is an active suggestion inserts it.
|
||||
editor.accept_inline_completion(&Default::default(), window, cx);
|
||||
assert!(!editor.has_active_inline_completion());
|
||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
|
||||
@@ -483,7 +486,7 @@ mod tests {
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.next_edit_prediction(&Default::default(), window, cx)
|
||||
editor.next_inline_completion(&Default::default(), window, cx)
|
||||
});
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
@@ -497,8 +500,8 @@ mod tests {
|
||||
assert_eq!(editor.text(cx), "fn foo() {\n \n}");
|
||||
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
|
||||
|
||||
// Using AcceptEditPrediction again accepts the suggestion.
|
||||
editor.accept_edit_prediction(&Default::default(), window, cx);
|
||||
// Using AcceptInlineCompletion again accepts the suggestion.
|
||||
editor.accept_inline_completion(&Default::default(), window, cx);
|
||||
assert!(!editor.has_active_inline_completion());
|
||||
assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
|
||||
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
|
||||
@@ -527,7 +530,7 @@ mod tests {
|
||||
.await;
|
||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
|
||||
});
|
||||
|
||||
// Setup the editor with a completion request.
|
||||
@@ -651,7 +654,7 @@ mod tests {
|
||||
.await;
|
||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
|
||||
});
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
@@ -670,7 +673,7 @@ mod tests {
|
||||
vec![],
|
||||
);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.next_edit_prediction(&Default::default(), window, cx)
|
||||
editor.next_inline_completion(&Default::default(), window, cx)
|
||||
});
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
@@ -741,7 +744,7 @@ mod tests {
|
||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -759,7 +762,7 @@ mod tests {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
|
||||
});
|
||||
editor.next_edit_prediction(&Default::default(), window, cx);
|
||||
editor.next_inline_completion(&Default::default(), window, cx);
|
||||
});
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
_ = editor.update(cx, |editor, _, cx| {
|
||||
@@ -835,7 +838,7 @@ mod tests {
|
||||
.await;
|
||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
|
||||
});
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
@@ -863,7 +866,7 @@ mod tests {
|
||||
vec![],
|
||||
);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.next_edit_prediction(&Default::default(), window, cx)
|
||||
editor.next_inline_completion(&Default::default(), window, cx)
|
||||
});
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
@@ -931,7 +934,7 @@ mod tests {
|
||||
async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||
init_test(cx, |settings| {
|
||||
settings
|
||||
.edit_predictions
|
||||
.inline_completions
|
||||
.get_or_insert(Default::default())
|
||||
.disabled_globs = Some(vec![".env*".to_string()]);
|
||||
});
|
||||
@@ -993,7 +996,7 @@ mod tests {
|
||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use unindent::Unindent as _;
|
||||
use util::{path, post_inc, RandomCharIter};
|
||||
use util::{post_inc, RandomCharIter};
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
@@ -33,7 +33,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/test"),
|
||||
"/test",
|
||||
json!({
|
||||
"consts.rs": "
|
||||
const a: i32 = 'a';
|
||||
@@ -59,7 +59,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
.await;
|
||||
|
||||
let language_server_id = LanguageServerId(0);
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
@@ -70,7 +70,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
lsp_store
|
||||
.update_diagnostic_entries(
|
||||
language_server_id,
|
||||
PathBuf::from(path!("/test/main.rs")),
|
||||
PathBuf::from("/test/main.rs"),
|
||||
None,
|
||||
vec![
|
||||
DiagnosticEntry {
|
||||
@@ -234,7 +234,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
lsp_store
|
||||
.update_diagnostic_entries(
|
||||
language_server_id,
|
||||
PathBuf::from(path!("/test/consts.rs")),
|
||||
PathBuf::from("/test/consts.rs"),
|
||||
None,
|
||||
vec![DiagnosticEntry {
|
||||
range: Unclipped(PointUtf16::new(0, 15))..Unclipped(PointUtf16::new(0, 15)),
|
||||
@@ -341,7 +341,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
lsp_store
|
||||
.update_diagnostic_entries(
|
||||
language_server_id,
|
||||
PathBuf::from(path!("/test/consts.rs")),
|
||||
PathBuf::from("/test/consts.rs"),
|
||||
None,
|
||||
vec![
|
||||
DiagnosticEntry {
|
||||
@@ -464,7 +464,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/test"),
|
||||
"/test",
|
||||
json!({
|
||||
"main.js": "
|
||||
a();
|
||||
@@ -479,7 +479,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
|
||||
let server_id_1 = LanguageServerId(100);
|
||||
let server_id_2 = LanguageServerId(101);
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
@@ -504,7 +504,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
lsp_store
|
||||
.update_diagnostic_entries(
|
||||
server_id_1,
|
||||
PathBuf::from(path!("/test/main.js")),
|
||||
PathBuf::from("/test/main.js"),
|
||||
None,
|
||||
vec![DiagnosticEntry {
|
||||
range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 1)),
|
||||
@@ -557,7 +557,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
lsp_store
|
||||
.update_diagnostic_entries(
|
||||
server_id_2,
|
||||
PathBuf::from(path!("/test/main.js")),
|
||||
PathBuf::from("/test/main.js"),
|
||||
None,
|
||||
vec![DiagnosticEntry {
|
||||
range: Unclipped(PointUtf16::new(1, 0))..Unclipped(PointUtf16::new(1, 1)),
|
||||
@@ -619,7 +619,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
lsp_store
|
||||
.update_diagnostic_entries(
|
||||
server_id_1,
|
||||
PathBuf::from(path!("/test/main.js")),
|
||||
PathBuf::from("/test/main.js"),
|
||||
None,
|
||||
vec![DiagnosticEntry {
|
||||
range: Unclipped(PointUtf16::new(2, 0))..Unclipped(PointUtf16::new(2, 1)),
|
||||
@@ -638,7 +638,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
lsp_store
|
||||
.update_diagnostic_entries(
|
||||
server_id_2,
|
||||
PathBuf::from(path!("/test/main.rs")),
|
||||
PathBuf::from("/test/main.rs"),
|
||||
None,
|
||||
vec![],
|
||||
cx,
|
||||
@@ -689,7 +689,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
lsp_store
|
||||
.update_diagnostic_entries(
|
||||
server_id_2,
|
||||
PathBuf::from(path!("/test/main.js")),
|
||||
PathBuf::from("/test/main.js"),
|
||||
None,
|
||||
vec![DiagnosticEntry {
|
||||
range: Unclipped(PointUtf16::new(3, 0))..Unclipped(PointUtf16::new(3, 1)),
|
||||
@@ -755,9 +755,9 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
.unwrap_or(10);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(path!("/test"), json!({})).await;
|
||||
fs.insert_tree("/test", json!({})).await;
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
@@ -817,7 +817,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
// insert a set of diagnostics for a new path
|
||||
_ => {
|
||||
let path: PathBuf =
|
||||
format!(path!("/test/{}.rs"), post_inc(&mut next_filename)).into();
|
||||
format!("/test/{}.rs", post_inc(&mut next_filename)).into();
|
||||
let len = rng.gen_range(128..256);
|
||||
let content =
|
||||
RandomCharIter::new(&mut rng).take(len).collect::<String>();
|
||||
@@ -891,7 +891,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
for diagnostic in diagnostics {
|
||||
let found_excerpt = reference_excerpts.iter().any(|info| {
|
||||
let row_range = info.range.context.start.row..info.range.context.end.row;
|
||||
info.path == path.strip_prefix(path!("/test")).unwrap()
|
||||
info.path == path.strip_prefix("/test").unwrap()
|
||||
&& info.language_server == language_server_id
|
||||
&& row_range.contains(&diagnostic.range.start.0.row)
|
||||
});
|
||||
|
||||
@@ -11,9 +11,6 @@ workspace = true
|
||||
[lib]
|
||||
path = "src/diff.rs"
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
futures.workspace = true
|
||||
git2.workspace = true
|
||||
@@ -26,7 +23,10 @@ text.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
serde_json.workspace = true
|
||||
text = { workspace = true, features = ["test-support"] }
|
||||
unindent.workspace = true
|
||||
serde_json.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
text = {workspace = true, features = ["test-support"]}
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
@@ -3,64 +3,56 @@ use super::*;
|
||||
use gpui::{action_as, action_with_deprecated_aliases};
|
||||
use schemars::JsonSchema;
|
||||
use util::serde::default_true;
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SelectNext {
|
||||
#[serde(default)]
|
||||
pub replace_newest: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SelectPrevious {
|
||||
#[serde(default)]
|
||||
pub replace_newest: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MoveToBeginningOfLine {
|
||||
#[serde(default = "default_true")]
|
||||
pub stop_at_soft_wraps: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SelectToBeginningOfLine {
|
||||
#[serde(default)]
|
||||
pub(super) stop_at_soft_wraps: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MovePageUp {
|
||||
#[serde(default)]
|
||||
pub(super) center_cursor: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MovePageDown {
|
||||
#[serde(default)]
|
||||
pub(super) center_cursor: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MoveToEndOfLine {
|
||||
#[serde(default = "default_true")]
|
||||
pub stop_at_soft_wraps: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SelectToEndOfLine {
|
||||
#[serde(default)]
|
||||
pub(super) stop_at_soft_wraps: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ToggleCodeActions {
|
||||
// Display row from which the action was deployed.
|
||||
#[serde(default)]
|
||||
@@ -69,28 +61,24 @@ pub struct ToggleCodeActions {
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfirmCompletion {
|
||||
#[serde(default)]
|
||||
pub item_ix: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ComposeCompletion {
|
||||
#[serde(default)]
|
||||
pub item_ix: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfirmCodeAction {
|
||||
#[serde(default)]
|
||||
pub item_ix: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ToggleComments {
|
||||
#[serde(default)]
|
||||
pub advance_downwards: bool,
|
||||
@@ -99,70 +87,60 @@ pub struct ToggleComments {
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct FoldAt {
|
||||
#[serde(skip)]
|
||||
pub buffer_row: MultiBufferRow,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct UnfoldAt {
|
||||
#[serde(skip)]
|
||||
pub buffer_row: MultiBufferRow,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MoveUpByLines {
|
||||
#[serde(default)]
|
||||
pub(super) lines: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MoveDownByLines {
|
||||
#[serde(default)]
|
||||
pub(super) lines: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SelectUpByLines {
|
||||
#[serde(default)]
|
||||
pub(super) lines: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SelectDownByLines {
|
||||
#[serde(default)]
|
||||
pub(super) lines: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ExpandExcerpts {
|
||||
#[serde(default)]
|
||||
pub(super) lines: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ExpandExcerptsUp {
|
||||
#[serde(default)]
|
||||
pub(super) lines: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ExpandExcerptsDown {
|
||||
#[serde(default)]
|
||||
pub(super) lines: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ShowCompletions {
|
||||
#[serde(default)]
|
||||
pub(super) trigger: Option<String>,
|
||||
@@ -172,24 +150,23 @@ pub struct ShowCompletions {
|
||||
pub struct HandleInput(pub String);
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct DeleteToNextWordEnd {
|
||||
#[serde(default)]
|
||||
pub ignore_newlines: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct DeleteToPreviousWordStart {
|
||||
#[serde(default)]
|
||||
pub ignore_newlines: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
pub struct FoldAtLevel(pub u32);
|
||||
pub struct FoldAtLevel {
|
||||
pub level: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SpawnNearestTask {
|
||||
#[serde(default)]
|
||||
pub reveal: task::RevealStrategy,
|
||||
@@ -239,9 +216,9 @@ impl_actions!(
|
||||
gpui::actions!(
|
||||
editor,
|
||||
[
|
||||
AcceptEditPrediction,
|
||||
AcceptInlineCompletion,
|
||||
AcceptPartialCopilotSuggestion,
|
||||
AcceptPartialEditPrediction,
|
||||
AcceptPartialInlineCompletion,
|
||||
AddSelectionAbove,
|
||||
AddSelectionBelow,
|
||||
ApplyAllDiffHunks,
|
||||
@@ -333,7 +310,7 @@ gpui::actions!(
|
||||
Newline,
|
||||
NewlineAbove,
|
||||
NewlineBelow,
|
||||
NextEditPrediction,
|
||||
NextInlineCompletion,
|
||||
NextScreen,
|
||||
OpenContextMenu,
|
||||
OpenExcerpts,
|
||||
@@ -348,7 +325,7 @@ gpui::actions!(
|
||||
PageDown,
|
||||
PageUp,
|
||||
Paste,
|
||||
PreviousEditPrediction,
|
||||
PreviousInlineCompletion,
|
||||
Redo,
|
||||
RedoSelection,
|
||||
Rename,
|
||||
@@ -384,7 +361,7 @@ gpui::actions!(
|
||||
SelectToStartOfParagraph,
|
||||
SelectUp,
|
||||
ShowCharacterPalette,
|
||||
ShowEditPrediction,
|
||||
ShowInlineCompletion,
|
||||
ShowSignatureHelp,
|
||||
ShuffleLines,
|
||||
SortLinesCaseInsensitive,
|
||||
@@ -398,7 +375,7 @@ gpui::actions!(
|
||||
ToggleGitBlameInline,
|
||||
ToggleIndentGuides,
|
||||
ToggleInlayHints,
|
||||
ToggleEditPrediction,
|
||||
ToggleInlineCompletions,
|
||||
ToggleLineNumbers,
|
||||
SwapSelectionEnds,
|
||||
SetMark,
|
||||
|
||||
@@ -517,6 +517,7 @@ impl CompletionsMenu {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let color_swatch = completion
|
||||
.color()
|
||||
.map(|color| div().size_4().bg(color).rounded_sm());
|
||||
|
||||
@@ -90,7 +90,7 @@ use hover_popover::{hide_hover, HoverState};
|
||||
use indent_guides::ActiveIndentGuidesState;
|
||||
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
|
||||
pub use inline_completion::Direction;
|
||||
use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
|
||||
use inline_completion::{InlineCompletionProvider, InlineCompletionProviderHandle};
|
||||
pub use items::MAX_TAB_TITLE_LEN;
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
@@ -490,7 +490,6 @@ enum InlineCompletion {
|
||||
struct InlineCompletionState {
|
||||
inlay_ids: Vec<InlayId>,
|
||||
completion: InlineCompletion,
|
||||
completion_id: Option<SharedString>,
|
||||
invalidation_range: Range<Anchor>,
|
||||
}
|
||||
|
||||
@@ -675,12 +674,14 @@ pub struct Editor {
|
||||
pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
|
||||
gutter_hovered: bool,
|
||||
hovered_link_state: Option<HoveredLinkState>,
|
||||
edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
|
||||
inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
|
||||
code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
|
||||
active_inline_completion: Option<InlineCompletionState>,
|
||||
/// Used to prevent flickering as the user types while the menu is open
|
||||
stale_inline_completion_in_menu: Option<InlineCompletionState>,
|
||||
inline_completions_hidden_for_vim_mode: bool,
|
||||
// enable_inline_completions is a switch that Vim can use to disable
|
||||
// edit predictions based on its mode.
|
||||
show_inline_completions: bool,
|
||||
show_inline_completions_override: Option<bool>,
|
||||
menu_inline_completions_policy: MenuInlineCompletionsPolicy,
|
||||
previewing_inline_completion: bool,
|
||||
@@ -1372,7 +1373,7 @@ impl Editor {
|
||||
hover_state: Default::default(),
|
||||
pending_mouse_down: None,
|
||||
hovered_link_state: Default::default(),
|
||||
edit_prediction_provider: None,
|
||||
inline_completion_provider: None,
|
||||
active_inline_completion: None,
|
||||
stale_inline_completion_in_menu: None,
|
||||
previewing_inline_completion: false,
|
||||
@@ -1389,8 +1390,8 @@ impl Editor {
|
||||
hovered_cursors: Default::default(),
|
||||
next_editor_action_id: EditorActionId::default(),
|
||||
editor_actions: Rc::default(),
|
||||
inline_completions_hidden_for_vim_mode: false,
|
||||
show_inline_completions_override: None,
|
||||
show_inline_completions: true,
|
||||
menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
|
||||
custom_context_menu: None,
|
||||
show_git_blame_gutter: false,
|
||||
@@ -1525,10 +1526,10 @@ impl Editor {
|
||||
|
||||
if self.has_active_inline_completion() {
|
||||
key_context.add("copilot_suggestion");
|
||||
key_context.add("edit_prediction");
|
||||
key_context.add("inline_completion");
|
||||
|
||||
if showing_completions || self.edit_prediction_requires_modifier(cx) {
|
||||
key_context.add("edit_prediction_requires_modifier");
|
||||
if showing_completions || self.inline_completion_requires_modifier(cx) {
|
||||
key_context.add("inline_completion_requires_modifier");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1738,15 +1739,15 @@ impl Editor {
|
||||
self.semantics_provider = provider;
|
||||
}
|
||||
|
||||
pub fn set_edit_prediction_provider<T>(
|
||||
pub fn set_inline_completion_provider<T>(
|
||||
&mut self,
|
||||
provider: Option<Entity<T>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) where
|
||||
T: EditPredictionProvider,
|
||||
T: InlineCompletionProvider,
|
||||
{
|
||||
self.edit_prediction_provider =
|
||||
self.inline_completion_provider =
|
||||
provider.map(|provider| RegisteredInlineCompletionProvider {
|
||||
_subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
|
||||
if this.focus_handle.is_focused(window) {
|
||||
@@ -1794,7 +1795,7 @@ impl Editor {
|
||||
self.collapse_matches = collapse_matches;
|
||||
}
|
||||
|
||||
fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
|
||||
pub fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
|
||||
let buffers = self.buffer.read(cx).all_buffers();
|
||||
let Some(lsp_store) = self.lsp_store(cx) else {
|
||||
return;
|
||||
@@ -1828,19 +1829,11 @@ impl Editor {
|
||||
self.input_enabled = input_enabled;
|
||||
}
|
||||
|
||||
pub fn set_inline_completions_hidden_for_vim_mode(
|
||||
&mut self,
|
||||
hidden: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if hidden != self.inline_completions_hidden_for_vim_mode {
|
||||
self.inline_completions_hidden_for_vim_mode = hidden;
|
||||
if hidden {
|
||||
self.update_visible_inline_completion(window, cx);
|
||||
} else {
|
||||
self.refresh_inline_completion(true, false, window, cx);
|
||||
}
|
||||
pub fn set_show_inline_completions_enabled(&mut self, enabled: bool, cx: &mut Context<Self>) {
|
||||
self.show_inline_completions = enabled;
|
||||
if !self.show_inline_completions {
|
||||
self.take_active_inline_completion(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1878,7 +1871,7 @@ impl Editor {
|
||||
|
||||
pub fn toggle_inline_completions(
|
||||
&mut self,
|
||||
_: &ToggleEditPrediction,
|
||||
_: &ToggleInlineCompletions,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -1901,23 +1894,14 @@ impl Editor {
|
||||
|
||||
pub fn set_show_inline_completions(
|
||||
&mut self,
|
||||
show_edit_predictions: Option<bool>,
|
||||
show_inline_completions: Option<bool>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.show_inline_completions_override = show_edit_predictions;
|
||||
self.show_inline_completions_override = show_inline_completions;
|
||||
self.refresh_inline_completion(false, true, window, cx);
|
||||
}
|
||||
|
||||
pub fn inline_completion_start_anchor(&self) -> Option<Anchor> {
|
||||
let active_completion = self.active_inline_completion.as_ref()?;
|
||||
let result = match &active_completion.completion {
|
||||
InlineCompletion::Edit { edits, .. } => edits.first()?.0.start,
|
||||
InlineCompletion::Move { target, .. } => *target,
|
||||
};
|
||||
Some(result)
|
||||
}
|
||||
|
||||
fn inline_completions_disabled_in_scope(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||
@@ -1933,7 +1917,7 @@ impl Editor {
|
||||
|
||||
scope.override_name().map_or(false, |scope_name| {
|
||||
settings
|
||||
.edit_predictions_disabled_in
|
||||
.inline_completions_disabled_in
|
||||
.iter()
|
||||
.any(|s| s == scope_name)
|
||||
})
|
||||
@@ -2021,21 +2005,6 @@ impl Editor {
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(buffer_id) = new_cursor_position.buffer_id {
|
||||
if !self.registered_buffers.contains_key(&buffer_id) {
|
||||
if let Some(lsp_store) = self.lsp_store(cx) {
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
|
||||
return;
|
||||
};
|
||||
self.registered_buffers.insert(
|
||||
buffer_id,
|
||||
lsp_store.register_buffer_with_language_servers(&buffer, cx),
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(completion_menu) = completion_menu {
|
||||
let cursor_position = new_cursor_position.to_offset(buffer);
|
||||
@@ -2597,7 +2566,7 @@ impl Editor {
|
||||
|
||||
pub fn dismiss_menus_and_popups(
|
||||
&mut self,
|
||||
is_user_requested: bool,
|
||||
should_report_inline_completion_event: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
@@ -2621,7 +2590,7 @@ impl Editor {
|
||||
return true;
|
||||
}
|
||||
|
||||
if is_user_requested && self.discard_inline_completion(true, cx) {
|
||||
if self.discard_inline_completion(should_report_inline_completion_event, cx) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3016,7 +2985,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
let trigger_in_words =
|
||||
this.show_edit_predictions_in_menu(cx) || !had_active_inline_completion;
|
||||
this.show_inline_completions_in_menu(cx) || !had_active_inline_completion;
|
||||
this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
|
||||
linked_editing_ranges::refresh_linked_ranges(this, window, cx);
|
||||
this.refresh_inline_completion(true, false, window, cx);
|
||||
@@ -3909,7 +3878,7 @@ impl Editor {
|
||||
*editor.context_menu.borrow_mut() =
|
||||
Some(CodeContextMenu::Completions(menu));
|
||||
|
||||
if editor.show_edit_predictions_in_menu(cx) {
|
||||
if editor.show_inline_completions_in_menu(cx) {
|
||||
editor.update_visible_inline_completion(window, cx);
|
||||
} else {
|
||||
editor.discard_inline_completion(false, cx);
|
||||
@@ -3923,7 +3892,7 @@ impl Editor {
|
||||
// If it was already hidden and we don't show inline
|
||||
// completions in the menu, we should also show the
|
||||
// inline-completion when available.
|
||||
if was_hidden && editor.show_edit_predictions_in_menu(cx) {
|
||||
if was_hidden && editor.show_inline_completions_in_menu(cx) {
|
||||
editor.update_visible_inline_completion(window, cx);
|
||||
}
|
||||
}
|
||||
@@ -3973,7 +3942,7 @@ impl Editor {
|
||||
|
||||
let entries = completions_menu.entries.borrow();
|
||||
let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
|
||||
if self.show_edit_predictions_in_menu(cx) {
|
||||
if self.show_inline_completions_in_menu(cx) {
|
||||
self.discard_inline_completion(true, cx);
|
||||
}
|
||||
let candidate_id = mat.candidate_id;
|
||||
@@ -4654,7 +4623,7 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<()> {
|
||||
let provider = self.edit_prediction_provider()?;
|
||||
let provider = self.inline_completion_provider()?;
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
let (buffer, cursor_buffer_position) =
|
||||
self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
|
||||
@@ -4665,7 +4634,12 @@ impl Editor {
|
||||
}
|
||||
|
||||
if !user_requested
|
||||
&& (!self.should_show_inline_completions_in_buffer(&buffer, cursor_buffer_position, cx)
|
||||
&& (!self.show_inline_completions
|
||||
|| !self.should_show_inline_completions_in_buffer(
|
||||
&buffer,
|
||||
cursor_buffer_position,
|
||||
cx,
|
||||
)
|
||||
|| !self.is_focused(window)
|
||||
|| buffer.read(cx).is_empty())
|
||||
{
|
||||
@@ -4695,7 +4669,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn edit_prediction_requires_modifier(&self, cx: &App) -> bool {
|
||||
fn inline_completion_requires_modifier(&self, cx: &App) -> bool {
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
|
||||
self.buffer
|
||||
@@ -4732,7 +4706,7 @@ impl Editor {
|
||||
buffer.file(),
|
||||
cx,
|
||||
)
|
||||
.show_edit_predictions
|
||||
.show_inline_completions
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4754,7 +4728,7 @@ impl Editor {
|
||||
cx: &App,
|
||||
) -> bool {
|
||||
maybe!({
|
||||
let provider = self.edit_prediction_provider()?;
|
||||
let provider = self.inline_completion_provider()?;
|
||||
if !provider.is_enabled(&buffer, buffer_position, cx) {
|
||||
return Some(false);
|
||||
}
|
||||
@@ -4774,11 +4748,11 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<()> {
|
||||
let provider = self.edit_prediction_provider()?;
|
||||
let provider = self.inline_completion_provider()?;
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
let (buffer, cursor_buffer_position) =
|
||||
self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
|
||||
if self.inline_completions_hidden_for_vim_mode
|
||||
if !self.show_inline_completions
|
||||
|| !self.should_show_inline_completions_in_buffer(&buffer, cursor_buffer_position, cx)
|
||||
{
|
||||
return None;
|
||||
@@ -4792,7 +4766,7 @@ impl Editor {
|
||||
|
||||
pub fn show_inline_completion(
|
||||
&mut self,
|
||||
_: &ShowEditPrediction,
|
||||
_: &ShowInlineCompletion,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -4827,9 +4801,9 @@ impl Editor {
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn next_edit_prediction(
|
||||
pub fn next_inline_completion(
|
||||
&mut self,
|
||||
_: &NextEditPrediction,
|
||||
_: &NextInlineCompletion,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -4845,9 +4819,9 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous_edit_prediction(
|
||||
pub fn previous_inline_completion(
|
||||
&mut self,
|
||||
_: &PreviousEditPrediction,
|
||||
_: &PreviousInlineCompletion,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -4863,9 +4837,9 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accept_edit_prediction(
|
||||
pub fn accept_inline_completion(
|
||||
&mut self,
|
||||
_: &AcceptEditPrediction,
|
||||
_: &AcceptInlineCompletion,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -4886,7 +4860,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
if self.show_edit_predictions_in_menu(cx) {
|
||||
if self.show_inline_completions_in_menu(cx) {
|
||||
self.hide_context_menu(window, cx);
|
||||
}
|
||||
|
||||
@@ -4894,22 +4868,17 @@ impl Editor {
|
||||
return;
|
||||
};
|
||||
|
||||
self.report_inline_completion_event(
|
||||
active_inline_completion.completion_id.clone(),
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
self.report_inline_completion_event(true, cx);
|
||||
|
||||
match &active_inline_completion.completion {
|
||||
InlineCompletion::Move { target, .. } => {
|
||||
let target = *target;
|
||||
// Note that this is also done in vim's handler of the Tab action.
|
||||
self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([target..target]);
|
||||
});
|
||||
}
|
||||
InlineCompletion::Edit { edits, .. } => {
|
||||
if let Some(provider) = self.edit_prediction_provider() {
|
||||
if let Some(provider) = self.inline_completion_provider() {
|
||||
provider.accept(cx);
|
||||
}
|
||||
|
||||
@@ -4936,7 +4905,7 @@ impl Editor {
|
||||
|
||||
pub fn accept_partial_inline_completion(
|
||||
&mut self,
|
||||
_: &AcceptPartialEditPrediction,
|
||||
_: &AcceptPartialInlineCompletion,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -4947,11 +4916,7 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.report_inline_completion_event(
|
||||
active_inline_completion.completion_id.clone(),
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
self.report_inline_completion_event(true, cx);
|
||||
|
||||
match &active_inline_completion.completion {
|
||||
InlineCompletion::Move { target, .. } => {
|
||||
@@ -4997,7 +4962,7 @@ impl Editor {
|
||||
self.refresh_inline_completion(true, true, window, cx);
|
||||
cx.notify();
|
||||
} else {
|
||||
self.accept_edit_prediction(&Default::default(), window, cx);
|
||||
self.accept_inline_completion(&Default::default(), window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5009,23 +4974,18 @@ impl Editor {
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
if should_report_inline_completion_event {
|
||||
let completion_id = self
|
||||
.active_inline_completion
|
||||
.as_ref()
|
||||
.and_then(|active_completion| active_completion.completion_id.clone());
|
||||
|
||||
self.report_inline_completion_event(completion_id, false, cx);
|
||||
self.report_inline_completion_event(false, cx);
|
||||
}
|
||||
|
||||
if let Some(provider) = self.edit_prediction_provider() {
|
||||
if let Some(provider) = self.inline_completion_provider() {
|
||||
provider.discard(cx);
|
||||
}
|
||||
|
||||
self.take_active_inline_completion(cx)
|
||||
}
|
||||
|
||||
fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
|
||||
let Some(provider) = self.edit_prediction_provider() else {
|
||||
fn report_inline_completion_event(&self, accepted: bool, cx: &App) {
|
||||
let Some(provider) = self.inline_completion_provider() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -5049,7 +5009,6 @@ impl Editor {
|
||||
telemetry::event!(
|
||||
event_type,
|
||||
provider = provider.name(),
|
||||
prediction_id = id,
|
||||
suggestion_accepted = accepted,
|
||||
file_extension = extension,
|
||||
);
|
||||
@@ -5079,7 +5038,7 @@ impl Editor {
|
||||
cx: &App,
|
||||
) -> bool {
|
||||
if self.previewing_inline_completion
|
||||
|| !self.show_edit_predictions_in_menu(cx)
|
||||
|| !self.show_inline_completions_in_menu(cx)
|
||||
|| !self.should_show_inline_completions(cx)
|
||||
{
|
||||
return false;
|
||||
@@ -5089,7 +5048,7 @@ impl Editor {
|
||||
return true;
|
||||
}
|
||||
|
||||
has_completion && self.edit_prediction_requires_modifier(cx)
|
||||
has_completion && self.inline_completion_requires_modifier(cx)
|
||||
}
|
||||
|
||||
fn update_inline_completion_preview(
|
||||
@@ -5098,7 +5057,7 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if !self.show_edit_predictions_in_menu(cx) {
|
||||
if !self.show_inline_completions_in_menu(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5118,12 +5077,13 @@ impl Editor {
|
||||
let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
|
||||
let excerpt_id = cursor.excerpt_id;
|
||||
|
||||
let show_in_menu = self.show_edit_predictions_in_menu(cx);
|
||||
let show_in_menu = self.show_inline_completions_in_menu(cx);
|
||||
let completions_menu_has_precedence = !show_in_menu
|
||||
&& (self.context_menu.borrow().is_some()
|
||||
|| (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
|
||||
if completions_menu_has_precedence
|
||||
|| !offset_selection.is_empty()
|
||||
|| !self.show_inline_completions
|
||||
|| self
|
||||
.active_inline_completion
|
||||
.as_ref()
|
||||
@@ -5138,7 +5098,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
self.take_active_inline_completion(cx);
|
||||
let provider = self.edit_prediction_provider()?;
|
||||
let provider = self.inline_completion_provider()?;
|
||||
|
||||
let (buffer, cursor_buffer_position) =
|
||||
self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
|
||||
@@ -5178,11 +5138,8 @@ impl Editor {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let is_move =
|
||||
move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
|
||||
let completion = if is_move {
|
||||
invalidation_row_range =
|
||||
move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
|
||||
let completion = if let Some(move_invalidation_row_range) = move_invalidation_row_range {
|
||||
invalidation_row_range = move_invalidation_row_range;
|
||||
let target = first_edit_start;
|
||||
let target_point = text::ToPoint::to_point(&target.text_anchor, &snapshot);
|
||||
// TODO: Base this off of TreeSitter or word boundaries?
|
||||
@@ -5201,10 +5158,7 @@ impl Editor {
|
||||
snapshot,
|
||||
}
|
||||
} else {
|
||||
let show_completions_in_buffer = !self
|
||||
.inline_completion_visible_in_cursor_popover(true, cx)
|
||||
&& !self.inline_completions_hidden_for_vim_mode;
|
||||
if show_completions_in_buffer {
|
||||
if !self.inline_completion_visible_in_cursor_popover(true, cx) {
|
||||
if edits
|
||||
.iter()
|
||||
.all(|(range, _)| range.to_offset(&multibuffer).is_empty())
|
||||
@@ -5265,7 +5219,6 @@ impl Editor {
|
||||
self.active_inline_completion = Some(InlineCompletionState {
|
||||
inlay_ids,
|
||||
completion,
|
||||
completion_id: inline_completion.id,
|
||||
invalidation_range,
|
||||
});
|
||||
|
||||
@@ -5274,20 +5227,20 @@ impl Editor {
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
|
||||
Some(self.edit_prediction_provider.as_ref()?.provider.clone())
|
||||
pub fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
|
||||
Some(self.inline_completion_provider.as_ref()?.provider.clone())
|
||||
}
|
||||
|
||||
fn show_edit_predictions_in_menu(&self, cx: &App) -> bool {
|
||||
fn show_inline_completions_in_menu(&self, cx: &App) -> bool {
|
||||
let by_provider = matches!(
|
||||
self.menu_inline_completions_policy,
|
||||
MenuInlineCompletionsPolicy::ByProvider
|
||||
);
|
||||
|
||||
by_provider
|
||||
&& EditorSettings::get_global(cx).show_edit_predictions_in_menu
|
||||
&& EditorSettings::get_global(cx).show_inline_completions_in_menu
|
||||
&& self
|
||||
.edit_prediction_provider()
|
||||
.inline_completion_provider()
|
||||
.map_or(false, |provider| provider.show_completions_in_menu())
|
||||
}
|
||||
|
||||
@@ -5540,7 +5493,7 @@ impl Editor {
|
||||
window: &Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Option<AnyElement> {
|
||||
let provider = self.edit_prediction_provider.as_ref()?;
|
||||
let provider = self.inline_completion_provider.as_ref()?;
|
||||
|
||||
if provider.provider.needs_terms_acceptance(cx) {
|
||||
return Some(
|
||||
@@ -5565,7 +5518,7 @@ impl Editor {
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.child(Icon::new(IconName::ZedPredict))
|
||||
.child(Label::new("Accept Terms of Service"))
|
||||
@@ -5660,7 +5613,6 @@ impl Editor {
|
||||
} else {
|
||||
Color::Default
|
||||
}),
|
||||
None,
|
||||
true,
|
||||
),
|
||||
))
|
||||
@@ -10299,26 +10251,14 @@ impl Editor {
|
||||
if entry.diagnostic.is_primary
|
||||
&& entry.diagnostic.severity <= DiagnosticSeverity::WARNING
|
||||
&& entry.range.start != entry.range.end
|
||||
// if we match with the active diagnostic, skip it
|
||||
&& Some(entry.diagnostic.group_id)
|
||||
!= self.active_diagnostics.as_ref().map(|d| d.group_id)
|
||||
{
|
||||
let entry_group = entry.diagnostic.group_id;
|
||||
let in_next_group = self.active_diagnostics.as_ref().map_or(
|
||||
true,
|
||||
|active| match direction {
|
||||
Direction::Prev => {
|
||||
entry_group != active.group_id
|
||||
&& (active.group_id == 0 || entry_group < active.group_id)
|
||||
}
|
||||
Direction::Next => {
|
||||
entry_group != active.group_id
|
||||
&& (entry_group == 0 || entry_group > active.group_id)
|
||||
}
|
||||
},
|
||||
);
|
||||
if in_next_group {
|
||||
return Some((entry.range, entry.diagnostic.group_id));
|
||||
}
|
||||
Some((entry.range, entry.diagnostic.group_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
});
|
||||
|
||||
if let Some((primary_range, group_id)) = group {
|
||||
@@ -11824,7 +11764,7 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
let fold_at_level = fold_at.0;
|
||||
let fold_at_level = fold_at.level;
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut to_fold = Vec::new();
|
||||
let mut stack = vec![(0, snapshot.max_row().0, 1)];
|
||||
@@ -12296,19 +12236,11 @@ impl Editor {
|
||||
cx: &mut Context<'_, Editor>,
|
||||
) {
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
|
||||
buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
|
||||
})
|
||||
}
|
||||
|
||||
fn toggle_diff_hunks_in_ranges_narrow(
|
||||
&mut self,
|
||||
ranges: Vec<Range<Anchor>>,
|
||||
cx: &mut Context<'_, Editor>,
|
||||
) {
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
|
||||
buffer.expand_or_collapse_diff_hunks_narrow(ranges, expand, cx);
|
||||
if buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx) {
|
||||
buffer.collapse_diff_hunks(ranges, cx)
|
||||
} else {
|
||||
buffer.expand_diff_hunks(ranges, cx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14218,14 +14150,14 @@ impl Editor {
|
||||
.get("vim_mode")
|
||||
== Some(&serde_json::Value::Bool(true));
|
||||
|
||||
let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
|
||||
let edit_predictions_provider = all_language_settings(file, cx).inline_completions.provider;
|
||||
let copilot_enabled = edit_predictions_provider
|
||||
== language::language_settings::EditPredictionProvider::Copilot;
|
||||
== language::language_settings::InlineCompletionProvider::Copilot;
|
||||
let copilot_enabled_for_language = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.settings_at(0, cx)
|
||||
.show_edit_predictions;
|
||||
.show_inline_completions;
|
||||
|
||||
let project = project.read(cx);
|
||||
telemetry::event!(
|
||||
|
||||
@@ -35,7 +35,7 @@ pub struct EditorSettings {
|
||||
pub auto_signature_help: bool,
|
||||
pub show_signature_help_after_edits: bool,
|
||||
pub jupyter: Jupyter,
|
||||
pub show_edit_predictions_in_menu: bool,
|
||||
pub show_inline_completions_in_menu: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
@@ -372,7 +372,7 @@ pub struct EditorSettingsContent {
|
||||
/// Only has an effect if edit prediction provider supports it.
|
||||
///
|
||||
/// Default: true
|
||||
pub show_edit_predictions_in_menu: Option<bool>,
|
||||
pub show_inline_completions_in_menu: Option<bool>,
|
||||
|
||||
/// Jupyter REPL settings.
|
||||
pub jupyter: Option<JupyterContent>,
|
||||
|
||||
@@ -1159,7 +1159,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
editor.fold_at_level(&FoldAtLevel(2), window, cx);
|
||||
editor.fold_at_level(&FoldAtLevel { level: 2 }, window, cx);
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
"
|
||||
@@ -1183,7 +1183,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
editor.fold_at_level(&FoldAtLevel(1), window, cx);
|
||||
editor.fold_at_level(&FoldAtLevel { level: 1 }, window, cx);
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
"
|
||||
@@ -1198,7 +1198,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
|
||||
);
|
||||
|
||||
editor.unfold_all(&UnfoldAll, window, cx);
|
||||
editor.fold_at_level(&FoldAtLevel(0), window, cx);
|
||||
editor.fold_at_level(&FoldAtLevel { level: 0 }, window, cx);
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
"
|
||||
@@ -10653,176 +10653,6 @@ async fn go_to_prev_overlapping_diagnostic(
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn cycle_through_same_place_diagnostics(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
let lsp_store =
|
||||
cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
ˇfn func(abc def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update(|_, cx| {
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![
|
||||
lsp::Diagnostic {
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 11),
|
||||
lsp::Position::new(0, 12),
|
||||
),
|
||||
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::Diagnostic {
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 12),
|
||||
lsp::Position::new(0, 15),
|
||||
),
|
||||
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::Diagnostic {
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 12),
|
||||
lsp::Position::new(0, 15),
|
||||
),
|
||||
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::Diagnostic {
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 25),
|
||||
lsp::Position::new(0, 28),
|
||||
),
|
||||
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
},
|
||||
&[],
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
//// Backward
|
||||
|
||||
// Fourth diagnostic
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abc def: i32) -> ˇu32 {
|
||||
}
|
||||
"});
|
||||
|
||||
// Third diagnostic
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abc ˇdef: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
// Second diagnostic, same place
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abc ˇdef: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
// First diagnostic
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abcˇ def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
// Wrapped over, fourth diagnostic
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abc def: i32) -> ˇu32 {
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.move_to_beginning(&MoveToBeginning, window, cx);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
ˇfn func(abc def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
//// Forward
|
||||
|
||||
// First diagnostic
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abcˇ def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
// Second diagnostic
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abc ˇdef: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
// Third diagnostic, same place
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abc ˇdef: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
// Fourth diagnostic
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abc def: i32) -> ˇu32 {
|
||||
}
|
||||
"});
|
||||
|
||||
// Wrapped around, first diagnostic
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abcˇ def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -15045,7 +14875,7 @@ async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/a"),
|
||||
"/a",
|
||||
json!({
|
||||
"first.rs": sample_text_1,
|
||||
"second.rs": sample_text_2,
|
||||
@@ -15053,7 +14883,7 @@ async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
|
||||
let worktree = project.update(cx, |project, cx| {
|
||||
@@ -15229,7 +15059,7 @@ async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/a"),
|
||||
"/a",
|
||||
json!({
|
||||
"first.rs": sample_text_1,
|
||||
"second.rs": sample_text_2,
|
||||
@@ -15237,7 +15067,7 @@ async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
|
||||
let worktree = project.update(cx, |project, cx| {
|
||||
@@ -15376,13 +15206,13 @@ async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppCon
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/a"),
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": sample_text,
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
|
||||
let worktree = project.update(cx, |project, cx| {
|
||||
|
||||
@@ -475,8 +475,8 @@ impl EditorElement {
|
||||
}
|
||||
});
|
||||
register_action(editor, window, Editor::show_signature_help);
|
||||
register_action(editor, window, Editor::next_edit_prediction);
|
||||
register_action(editor, window, Editor::previous_edit_prediction);
|
||||
register_action(editor, window, Editor::next_inline_completion);
|
||||
register_action(editor, window, Editor::previous_inline_completion);
|
||||
register_action(editor, window, Editor::show_inline_completion);
|
||||
register_action(editor, window, Editor::context_menu_first);
|
||||
register_action(editor, window, Editor::context_menu_prev);
|
||||
@@ -486,7 +486,7 @@ impl EditorElement {
|
||||
register_action(editor, window, Editor::unique_lines_case_insensitive);
|
||||
register_action(editor, window, Editor::unique_lines_case_sensitive);
|
||||
register_action(editor, window, Editor::accept_partial_inline_completion);
|
||||
register_action(editor, window, Editor::accept_edit_prediction);
|
||||
register_action(editor, window, Editor::accept_inline_completion);
|
||||
register_action(editor, window, Editor::revert_file);
|
||||
register_action(editor, window, Editor::revert_selected_hunks);
|
||||
register_action(editor, window, Editor::apply_all_diff_hunks);
|
||||
@@ -559,7 +559,7 @@ impl EditorElement {
|
||||
let mut modifiers = event.modifiers;
|
||||
|
||||
if let Some(hovered_hunk) = hovered_hunk {
|
||||
editor.toggle_diff_hunks_in_ranges_narrow(vec![hovered_hunk], cx);
|
||||
editor.toggle_diff_hunks_in_ranges(vec![hovered_hunk], cx);
|
||||
cx.notify();
|
||||
return;
|
||||
} else if gutter_hitbox.is_hovered(window) {
|
||||
@@ -3197,7 +3197,7 @@ impl EditorElement {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// let bindings = window.bindings_for_action_in(
|
||||
// &crate::AcceptEditPrediction,
|
||||
// &crate::AcceptInlineCompletion,
|
||||
// &self.editor.focus_handle(cx),
|
||||
// );
|
||||
|
||||
@@ -3636,7 +3636,7 @@ impl EditorElement {
|
||||
self.editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)?;
|
||||
);
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
let offset = point((text_bounds.size.width - size.width) / 2., PADDING_Y);
|
||||
element.prepaint_at(text_bounds.origin + offset, window, cx);
|
||||
@@ -3649,7 +3649,7 @@ impl EditorElement {
|
||||
self.editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)?;
|
||||
);
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
let offset = point(
|
||||
(text_bounds.size.width - size.width) / 2.,
|
||||
@@ -3665,7 +3665,7 @@ impl EditorElement {
|
||||
self.editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)?;
|
||||
);
|
||||
|
||||
let target_line_end = DisplayPoint::new(
|
||||
target_display_point.row(),
|
||||
@@ -3740,7 +3740,7 @@ impl EditorElement {
|
||||
self.editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)?;
|
||||
);
|
||||
|
||||
element.prepaint_as_root(
|
||||
text_bounds.origin + origin + point(PADDING_X, px(0.)),
|
||||
@@ -5742,43 +5742,35 @@ fn inline_completion_accept_indicator(
|
||||
focus_handle: FocusHandle,
|
||||
window: &Window,
|
||||
cx: &App,
|
||||
) -> Option<AnyElement> {
|
||||
let use_hardcoded_linux_bindings;
|
||||
) -> AnyElement {
|
||||
let use_hardcoded_linux_preview_binding;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use_hardcoded_linux_bindings = false;
|
||||
use_hardcoded_linux_preview_binding = false;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
use_hardcoded_linux_bindings = true;
|
||||
use_hardcoded_linux_preview_binding = previewing;
|
||||
}
|
||||
|
||||
let accept_keystroke = if use_hardcoded_linux_bindings {
|
||||
if previewing {
|
||||
Keystroke {
|
||||
modifiers: Default::default(),
|
||||
key: "enter".to_string(),
|
||||
key_char: None,
|
||||
}
|
||||
} else {
|
||||
Keystroke {
|
||||
modifiers: Default::default(),
|
||||
key: "tab".to_string(),
|
||||
key_char: None,
|
||||
}
|
||||
let accept_keystroke = if use_hardcoded_linux_preview_binding {
|
||||
Keystroke {
|
||||
modifiers: Default::default(),
|
||||
key: "enter".to_string(),
|
||||
key_char: None,
|
||||
}
|
||||
} else {
|
||||
let bindings = window.bindings_for_action_in(&crate::AcceptEditPrediction, &focus_handle);
|
||||
let bindings = window.bindings_for_action_in(&crate::AcceptInlineCompletion, &focus_handle);
|
||||
if let Some(keystroke) = bindings
|
||||
.last()
|
||||
.and_then(|binding| binding.keystrokes().first())
|
||||
{
|
||||
// TODO: clone unnecessary once `use_hardcoded_linux_bindings` is removed.
|
||||
// TODO: clone unnecessary once `use_hardcoded_linux_preview_binding` is removed.
|
||||
keystroke.clone()
|
||||
} else {
|
||||
return None;
|
||||
return div().into_any();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5793,39 +5785,33 @@ fn inline_completion_accept_indicator(
|
||||
&accept_keystroke.modifiers,
|
||||
PlatformStyle::platform(),
|
||||
Some(Color::Default),
|
||||
None,
|
||||
false,
|
||||
))
|
||||
})
|
||||
.child(accept_keystroke.key.clone());
|
||||
|
||||
let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
|
||||
let accent_color = cx.theme().colors().text_accent;
|
||||
let editor_bg_color = cx.theme().colors().editor_background;
|
||||
let bg_color = editor_bg_color.blend(accent_color.opacity(0.2));
|
||||
|
||||
Some(
|
||||
h_flex()
|
||||
.py_0p5()
|
||||
.pl_1()
|
||||
.pr(padding_right)
|
||||
.gap_1()
|
||||
.bg(bg_color)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().text_accent.opacity(0.8))
|
||||
.rounded_md()
|
||||
.shadow_sm()
|
||||
.child(accept_key)
|
||||
.child(Label::new(label).size(LabelSize::Small))
|
||||
.when_some(icon, |element, icon| {
|
||||
element.child(
|
||||
div()
|
||||
.mt(px(1.5))
|
||||
.child(Icon::new(icon).size(IconSize::Small)),
|
||||
)
|
||||
})
|
||||
.into_any(),
|
||||
)
|
||||
h_flex()
|
||||
.py_0p5()
|
||||
.pl_1()
|
||||
.pr(padding_right)
|
||||
.gap_1()
|
||||
.bg(cx.theme().colors().text_accent.opacity(0.15))
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().text_accent.opacity(0.8))
|
||||
.rounded_md()
|
||||
.shadow_sm()
|
||||
.child(accept_key)
|
||||
.child(Label::new(label).size(LabelSize::Small))
|
||||
.when_some(icon, |element, icon| {
|
||||
element.child(
|
||||
div()
|
||||
.mt(px(1.5))
|
||||
.child(Icon::new(icon).size(IconSize::Small)),
|
||||
)
|
||||
})
|
||||
.into_any()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
||||
@@ -598,7 +598,7 @@ async fn parse_blocks(
|
||||
},
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
selection_background_color: { cx.theme().players().local().selection },
|
||||
|
||||
break_style: Default::default(),
|
||||
heading: StyleRefinement::default()
|
||||
.font_weight(FontWeight::BOLD)
|
||||
.text_base()
|
||||
@@ -885,10 +885,8 @@ mod tests {
|
||||
let slice = data;
|
||||
|
||||
for (range, event) in slice.iter() {
|
||||
match event {
|
||||
MarkdownEvent::Text(parsed) => rendered_text.push_str(parsed),
|
||||
MarkdownEvent::Code => rendered_text.push_str(&text[range.clone()]),
|
||||
_ => {}
|
||||
if [MarkdownEvent::Text, MarkdownEvent::Code].contains(event) {
|
||||
rendered_text.push_str(&text[range.clone()])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,7 +763,7 @@ impl Editor {
|
||||
this.child({
|
||||
let focus = editor.focus_handle(cx);
|
||||
PopoverMenu::new("hunk-controls-dropdown")
|
||||
.trigger_with_tooltip(
|
||||
.trigger(
|
||||
IconButton::new(
|
||||
"toggle_editor_selections_icon",
|
||||
IconName::EllipsisVertical,
|
||||
@@ -774,8 +774,19 @@ impl Editor {
|
||||
.toggle_state(
|
||||
hunk_controls_menu_handle
|
||||
.is_deployed(),
|
||||
)
|
||||
.when(
|
||||
!hunk_controls_menu_handle
|
||||
.is_deployed(),
|
||||
|this| {
|
||||
this.tooltip(|_, cx| {
|
||||
Tooltip::simple(
|
||||
"Hunk Controls",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
},
|
||||
),
|
||||
Tooltip::simple("Hunk Controls", cx),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(hunk_controls_menu_handle)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{prelude::*, Entity};
|
||||
use indoc::indoc;
|
||||
use inline_completion::EditPredictionProvider;
|
||||
use inline_completion::InlineCompletionProvider;
|
||||
use language::{Language, LanguageConfig};
|
||||
use multi_buffer::{Anchor, MultiBufferSnapshot, ToPoint};
|
||||
use project::Project;
|
||||
@@ -315,7 +315,7 @@ fn assert_editor_active_move_completion(
|
||||
|
||||
fn accept_completion(cx: &mut EditorTestContext) {
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
|
||||
editor.accept_inline_completion(&crate::AcceptInlineCompletion, window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -333,7 +333,6 @@ fn propose_edits<T: ToOffset>(
|
||||
cx.update(|_, cx| {
|
||||
provider.update(cx, |provider, _| {
|
||||
provider.set_inline_completion(Some(inline_completion::InlineCompletion {
|
||||
id: None,
|
||||
edits: edits.collect(),
|
||||
edit_preview: None,
|
||||
}))
|
||||
@@ -346,7 +345,7 @@ fn assign_editor_completion_provider(
|
||||
cx: &mut EditorTestContext,
|
||||
) {
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.set_edit_prediction_provider(Some(provider), window, cx);
|
||||
editor.set_inline_completion_provider(Some(provider), window, cx);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -364,7 +363,7 @@ impl FakeInlineCompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl EditPredictionProvider for FakeInlineCompletionProvider {
|
||||
impl InlineCompletionProvider for FakeInlineCompletionProvider {
|
||||
fn name() -> &'static str {
|
||||
"fake-completion-provider"
|
||||
}
|
||||
@@ -377,6 +376,10 @@ impl EditPredictionProvider for FakeInlineCompletionProvider {
|
||||
false
|
||||
}
|
||||
|
||||
fn show_completions_in_normal_mode() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
_buffer: &gpui::Entity<language::Buffer>,
|
||||
|
||||
@@ -118,8 +118,6 @@ pub trait ExtensionThemeProxy: Send + Sync + 'static {
|
||||
icons_root_dir: PathBuf,
|
||||
fs: Arc<dyn Fs>,
|
||||
) -> Task<Result<()>>;
|
||||
|
||||
fn reload_current_icon_theme(&self, cx: &mut App);
|
||||
}
|
||||
|
||||
impl ExtensionThemeProxy for ExtensionHostProxy {
|
||||
@@ -187,14 +185,6 @@ impl ExtensionThemeProxy for ExtensionHostProxy {
|
||||
|
||||
proxy.load_icon_theme(icon_theme_path, icons_root_dir, fs)
|
||||
}
|
||||
|
||||
fn reload_current_icon_theme(&self, cx: &mut App) {
|
||||
let Some(proxy) = self.theme_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.reload_current_icon_theme(cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtensionGrammarProxy: Send + Sync + 'static {
|
||||
|
||||
@@ -1292,7 +1292,6 @@ impl ExtensionStore {
|
||||
|
||||
this.wasm_extensions.extend(wasm_extensions);
|
||||
this.proxy.reload_current_theme(cx);
|
||||
this.proxy.reload_current_icon_theme(cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
|
||||
@@ -64,17 +64,12 @@ impl Display for SystemSpecs {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let os_information = format!("OS: {} {}", self.os_name, self.os_version);
|
||||
let app_version_information = format!(
|
||||
"Zed: v{} ({}) {}",
|
||||
"Zed: v{} ({})",
|
||||
self.app_version,
|
||||
match &self.commit_sha {
|
||||
Some(commit_sha) => format!("{} {}", self.release_channel, commit_sha),
|
||||
None => self.release_channel.to_string(),
|
||||
},
|
||||
if cfg!(debug_assertions) {
|
||||
"(Taylor's Version)"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
}
|
||||
);
|
||||
let system_specs = [
|
||||
app_version_information,
|
||||
|
||||
@@ -817,10 +817,7 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
|
||||
.as_u64() as usize,
|
||||
)
|
||||
});
|
||||
cx.dispatch_action(workspace::CloseActiveItem {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
});
|
||||
cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
|
||||
|
||||
let initial_history_items =
|
||||
open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
|
||||
@@ -2003,10 +2000,7 @@ async fn open_close_queried_buffer(
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.dispatch_action(workspace::CloseActiveItem {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
});
|
||||
cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
|
||||
|
||||
history_items
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use gpui::{App, AssetSource, Global, SharedString};
|
||||
use serde_derive::Deserialize;
|
||||
use settings::Settings;
|
||||
use theme::{IconTheme, ThemeRegistry, ThemeSettings};
|
||||
use util::paths::PathExt;
|
||||
use util::{maybe, paths::PathExt};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct FileIcons {
|
||||
@@ -43,45 +43,20 @@ impl FileIcons {
|
||||
pub fn get_icon(path: &Path, cx: &App) -> Option<SharedString> {
|
||||
let this = cx.try_global::<Self>()?;
|
||||
|
||||
let get_icon_from_suffix = |suffix: &str| -> Option<SharedString> {
|
||||
this.stems
|
||||
.get(suffix)
|
||||
.or_else(|| this.suffixes.get(suffix))
|
||||
.and_then(|typ| this.get_icon_for_type(typ, cx))
|
||||
};
|
||||
// TODO: Associate a type with the languages and have the file's language
|
||||
// override these associations
|
||||
maybe!({
|
||||
let suffix = path.icon_stem_or_suffix()?;
|
||||
|
||||
// check if file name is in suffixes
|
||||
// e.g. catch file named `eslint.config.js` instead of `.eslint.config.js`
|
||||
if let Some(typ) = path.file_name().and_then(|typ| typ.to_str()) {
|
||||
let maybe_path = get_icon_from_suffix(typ);
|
||||
if maybe_path.is_some() {
|
||||
return maybe_path;
|
||||
if let Some(type_str) = this.stems.get(suffix) {
|
||||
return this.get_icon_for_type(type_str, cx);
|
||||
}
|
||||
}
|
||||
|
||||
// primary case: check if the files extension or the hidden file name
|
||||
// matches some icon path
|
||||
if let Some(suffix) = path.extension_or_hidden_file_name() {
|
||||
let maybe_path = get_icon_from_suffix(suffix);
|
||||
if maybe_path.is_some() {
|
||||
return maybe_path;
|
||||
}
|
||||
}
|
||||
|
||||
// this _should_ only happen when the file is hidden (has leading '.')
|
||||
// and is not a "special" file we have an icon (e.g. not `.eslint.config.js`)
|
||||
// that should be caught above. In the remaining cases, we want to check
|
||||
// for a normal supported extension e.g. `.data.json` -> `json`
|
||||
let extension = path.extension().and_then(|ext| ext.to_str());
|
||||
if let Some(extension) = extension {
|
||||
let maybe_path = get_icon_from_suffix(extension);
|
||||
if maybe_path.is_some() {
|
||||
return maybe_path;
|
||||
}
|
||||
}
|
||||
return this.get_icon_for_type("default", cx);
|
||||
this.suffixes
|
||||
.get(suffix)
|
||||
.and_then(|type_str| this.get_icon_for_type(type_str, cx))
|
||||
})
|
||||
.or_else(|| this.get_icon_for_type("default", cx))
|
||||
}
|
||||
|
||||
fn default_icon_theme(cx: &App) -> Option<Arc<IconTheme>> {
|
||||
|
||||
@@ -11,9 +11,6 @@ workspace = true
|
||||
[lib]
|
||||
path = "src/git.rs"
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
@@ -35,7 +32,10 @@ url.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
serde_json.workspace = true
|
||||
text = { workspace = true, features = ["test-support"] }
|
||||
unindent.workspace = true
|
||||
serde_json.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
text = {workspace = true, features = ["test-support"]}
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
@@ -38,7 +38,8 @@ actions!(
|
||||
StageAll,
|
||||
UnstageAll,
|
||||
RevertAll,
|
||||
Commit,
|
||||
CommitChanges,
|
||||
CommitAllChanges,
|
||||
ClearCommitMessage
|
||||
]
|
||||
);
|
||||
|
||||
@@ -58,17 +58,8 @@ pub trait GitRepository: Send + Sync {
|
||||
|
||||
fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame>;
|
||||
|
||||
/// Returns the absolute path to the repository. For worktrees, this will be the path to the
|
||||
/// worktree's gitdir within the main repository (typically `.git/worktrees/<name>`).
|
||||
fn path(&self) -> PathBuf;
|
||||
|
||||
/// Returns the absolute path to the ".git" dir for the main repository, typically a `.git`
|
||||
/// folder. For worktrees, this will be the path to the repository the worktree was created
|
||||
/// from. Otherwise, this is the same value as `path()`.
|
||||
///
|
||||
/// Git documentation calls this the "commondir", and for git CLI is overridden by
|
||||
/// `GIT_COMMON_DIR`.
|
||||
fn main_repository_path(&self) -> PathBuf;
|
||||
/// Returns the path to the repository, typically the `.git` folder.
|
||||
fn dot_git_dir(&self) -> PathBuf;
|
||||
|
||||
/// Updates the index to match the worktree at the given paths.
|
||||
///
|
||||
@@ -118,16 +109,11 @@ impl GitRepository for RealGitRepository {
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> PathBuf {
|
||||
fn dot_git_dir(&self) -> PathBuf {
|
||||
let repo = self.repository.lock();
|
||||
repo.path().into()
|
||||
}
|
||||
|
||||
fn main_repository_path(&self) -> PathBuf {
|
||||
let repo = self.repository.lock();
|
||||
repo.commondir().into()
|
||||
}
|
||||
|
||||
fn load_index_text(&self, path: &RepoPath) -> Option<String> {
|
||||
fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
|
||||
const STAGE_NORMAL: i32 = 0;
|
||||
@@ -293,16 +279,13 @@ impl GitRepository for RealGitRepository {
|
||||
.to_path_buf();
|
||||
|
||||
if !paths.is_empty() {
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
let status = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["update-index", "--add", "--remove", "--"])
|
||||
.args(paths.iter().map(|p| p.as_ref()))
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to stage paths:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
.status()?;
|
||||
if !status.success() {
|
||||
return Err(anyhow!("Failed to stage paths: {status}"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -317,16 +300,13 @@ impl GitRepository for RealGitRepository {
|
||||
.to_path_buf();
|
||||
|
||||
if !paths.is_empty() {
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
let cmd = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["reset", "--quiet", "--"])
|
||||
.args(paths.iter().map(|p| p.as_ref()))
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to unstage:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
.status()?;
|
||||
if !cmd.success() {
|
||||
return Err(anyhow!("Failed to unstage paths: {cmd}"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -346,16 +326,12 @@ impl GitRepository for RealGitRepository {
|
||||
args.push(author);
|
||||
}
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
let cmd = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(args)
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to commit:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
.status()?;
|
||||
if !cmd.success() {
|
||||
return Err(anyhow!("Failed to commit: {cmd}"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -368,7 +344,7 @@ pub struct FakeGitRepository {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FakeGitRepositoryState {
|
||||
pub path: PathBuf,
|
||||
pub dot_git_dir: PathBuf,
|
||||
pub event_emitter: smol::channel::Sender<PathBuf>,
|
||||
pub head_contents: HashMap<RepoPath, String>,
|
||||
pub index_contents: HashMap<RepoPath, String>,
|
||||
@@ -385,9 +361,9 @@ impl FakeGitRepository {
|
||||
}
|
||||
|
||||
impl FakeGitRepositoryState {
|
||||
pub fn new(path: PathBuf, event_emitter: smol::channel::Sender<PathBuf>) -> Self {
|
||||
pub fn new(dot_git_dir: PathBuf, event_emitter: smol::channel::Sender<PathBuf>) -> Self {
|
||||
FakeGitRepositoryState {
|
||||
path,
|
||||
dot_git_dir,
|
||||
event_emitter,
|
||||
head_contents: Default::default(),
|
||||
index_contents: Default::default(),
|
||||
@@ -429,13 +405,9 @@ impl GitRepository for FakeGitRepository {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn path(&self) -> PathBuf {
|
||||
fn dot_git_dir(&self) -> PathBuf {
|
||||
let state = self.state.lock();
|
||||
state.path.clone()
|
||||
}
|
||||
|
||||
fn main_repository_path(&self) -> PathBuf {
|
||||
self.path()
|
||||
state.dot_git_dir.clone()
|
||||
}
|
||||
|
||||
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
|
||||
@@ -486,7 +458,7 @@ impl GitRepository for FakeGitRepository {
|
||||
state.current_branch_name = Some(name.to_owned());
|
||||
state
|
||||
.event_emitter
|
||||
.try_send(state.path.clone())
|
||||
.try_send(state.dot_git_dir.clone())
|
||||
.expect("Dropped repo change event");
|
||||
Ok(())
|
||||
}
|
||||
@@ -496,7 +468,7 @@ impl GitRepository for FakeGitRepository {
|
||||
state.branches.insert(name.to_owned());
|
||||
state
|
||||
.event_emitter
|
||||
.try_send(state.path.clone())
|
||||
.try_send(state.dot_git_dir.clone())
|
||||
.expect("Dropped repo change event");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ diff.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
@@ -39,7 +38,6 @@ theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
@@ -4,34 +4,36 @@ use crate::ProjectDiff;
|
||||
use crate::{
|
||||
git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::{
|
||||
actions::MoveToEnd, scroll::ScrollbarAutoHide, Editor, EditorElement, EditorMode,
|
||||
EditorSettings, MultiBuffer, ShowScrollbar,
|
||||
};
|
||||
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
||||
use editor::actions::MoveToEnd;
|
||||
use editor::scroll::ScrollbarAutoHide;
|
||||
use editor::{Editor, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar};
|
||||
use git::repository::RepoPath;
|
||||
use git::status::FileStatus;
|
||||
use git::{CommitAllChanges, CommitChanges, ToggleStaged};
|
||||
use gpui::*;
|
||||
use language::{Buffer, File};
|
||||
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||
use multi_buffer::ExcerptInfo;
|
||||
use panel::{panel_editor_container, panel_editor_style, panel_filled_button, PanelHeader};
|
||||
use project::{
|
||||
git::{GitEvent, Repository},
|
||||
Fs, Project, ProjectPath,
|
||||
};
|
||||
use panel::PanelHeader;
|
||||
use project::git::{GitEvent, Repository};
|
||||
use project::{Fs, Project, ProjectPath};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings as _;
|
||||
use std::{collections::HashSet, path::PathBuf, sync::Arc, time::Duration, usize};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, Checkbox, CheckboxWithLabel, Divider, DividerColor, ElevationIndex,
|
||||
IndentGuideColors, ListItem, ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
|
||||
prelude::*, ButtonLike, Checkbox, Divider, DividerColor, ElevationIndex, IndentGuideColors,
|
||||
ListHeader, ListItem, ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
|
||||
};
|
||||
use util::{maybe, ResultExt, TryFutureExt};
|
||||
use workspace::notifications::{DetachAndPromptErr, NotificationId};
|
||||
use workspace::Toast;
|
||||
use workspace::{
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
notifications::{DetachAndPromptErr, NotificationId},
|
||||
Toast, Workspace,
|
||||
Workspace,
|
||||
};
|
||||
|
||||
actions!(
|
||||
@@ -56,17 +58,6 @@ pub fn init(cx: &mut App) {
|
||||
workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
|
||||
workspace.toggle_panel_focus::<GitPanel>(window, cx);
|
||||
});
|
||||
|
||||
workspace.register_action(|workspace, _: &Commit, window, cx| {
|
||||
workspace.open_panel::<GitPanel>(window, cx);
|
||||
if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
|
||||
git_panel
|
||||
.read(cx)
|
||||
.commit_editor
|
||||
.focus_handle(cx)
|
||||
.focus(window);
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
@@ -146,33 +137,34 @@ struct PendingOperation {
|
||||
}
|
||||
|
||||
pub struct GitPanel {
|
||||
active_repository: Option<Entity<Repository>>,
|
||||
commit_editor: Entity<Editor>,
|
||||
conflicted_count: usize,
|
||||
conflicted_staged_count: usize,
|
||||
current_modifiers: Modifiers,
|
||||
enable_auto_coauthors: bool,
|
||||
entries: Vec<GitListEntry>,
|
||||
entries_by_path: collections::HashMap<RepoPath, usize>,
|
||||
focus_handle: FocusHandle,
|
||||
fs: Arc<dyn Fs>,
|
||||
hide_scrollbar_task: Option<Task<()>>,
|
||||
new_count: usize,
|
||||
new_staged_count: usize,
|
||||
pending: Vec<PendingOperation>,
|
||||
pending_commit: Option<Task<()>>,
|
||||
pending_serialization: Task<Option<()>>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
repository_selector: Entity<RepositorySelector>,
|
||||
active_repository: Option<Entity<Repository>>,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
scrollbar_state: ScrollbarState,
|
||||
selected_entry: Option<usize>,
|
||||
show_scrollbar: bool,
|
||||
tracked_count: usize,
|
||||
tracked_staged_count: usize,
|
||||
update_visible_entries_task: Task<()>,
|
||||
repository_selector: Entity<RepositorySelector>,
|
||||
commit_editor: Entity<Editor>,
|
||||
entries: Vec<GitListEntry>,
|
||||
entries_by_path: collections::HashMap<RepoPath, usize>,
|
||||
width: Option<Pixels>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
pending: Vec<PendingOperation>,
|
||||
commit_task: Task<Result<()>>,
|
||||
commit_pending: bool,
|
||||
|
||||
conflicted_staged_count: usize,
|
||||
conflicted_count: usize,
|
||||
tracked_staged_count: usize,
|
||||
tracked_count: usize,
|
||||
new_staged_count: usize,
|
||||
new_count: usize,
|
||||
}
|
||||
|
||||
fn commit_message_editor(
|
||||
@@ -180,10 +172,23 @@ fn commit_message_editor(
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, Editor>,
|
||||
) -> Editor {
|
||||
let theme = ThemeSettings::get_global(cx);
|
||||
|
||||
let mut text_style = window.text_style();
|
||||
let refinement = TextStyleRefinement {
|
||||
font_family: Some(theme.buffer_font.family.clone()),
|
||||
font_features: Some(FontFeatures::disable_ligatures()),
|
||||
font_size: Some(px(12.).into()),
|
||||
color: Some(cx.theme().colors().editor_foreground),
|
||||
background_color: Some(gpui::transparent_black()),
|
||||
..Default::default()
|
||||
};
|
||||
text_style.refine(&refinement);
|
||||
|
||||
let mut commit_editor = if let Some(commit_message_buffer) = commit_message_buffer {
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx));
|
||||
Editor::new(
|
||||
EditorMode::AutoHeight { max_lines: 6 },
|
||||
EditorMode::AutoHeight { max_lines: 10 },
|
||||
buffer,
|
||||
None,
|
||||
false,
|
||||
@@ -191,12 +196,13 @@ fn commit_message_editor(
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Editor::auto_height(6, window, cx)
|
||||
Editor::auto_height(10, window, cx)
|
||||
};
|
||||
commit_editor.set_use_autoclose(false);
|
||||
commit_editor.set_show_gutter(false, cx);
|
||||
commit_editor.set_show_wrap_guides(false, cx);
|
||||
commit_editor.set_show_indent_guides(false, cx);
|
||||
commit_editor.set_text_style_refinement(refinement);
|
||||
commit_editor.set_placeholder_text("Enter commit message", cx);
|
||||
commit_editor
|
||||
}
|
||||
@@ -245,40 +251,38 @@ impl GitPanel {
|
||||
)
|
||||
.detach();
|
||||
|
||||
let scrollbar_state =
|
||||
ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity());
|
||||
|
||||
let repository_selector =
|
||||
cx.new(|cx| RepositorySelector::new(project.clone(), window, cx));
|
||||
|
||||
let mut git_panel = Self {
|
||||
active_repository,
|
||||
commit_editor,
|
||||
conflicted_count: 0,
|
||||
conflicted_staged_count: 0,
|
||||
current_modifiers: window.modifiers(),
|
||||
enable_auto_coauthors: true,
|
||||
focus_handle: cx.focus_handle(),
|
||||
pending_serialization: Task::ready(None),
|
||||
entries: Vec::new(),
|
||||
entries_by_path: HashMap::default(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
fs,
|
||||
hide_scrollbar_task: None,
|
||||
new_count: 0,
|
||||
new_staged_count: 0,
|
||||
pending: Vec::new(),
|
||||
pending_commit: None,
|
||||
pending_serialization: Task::ready(None),
|
||||
project,
|
||||
current_modifiers: window.modifiers(),
|
||||
width: Some(px(360.)),
|
||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone())
|
||||
.parent_entity(&cx.entity()),
|
||||
repository_selector,
|
||||
scroll_handle,
|
||||
scrollbar_state,
|
||||
selected_entry: None,
|
||||
show_scrollbar: false,
|
||||
tracked_count: 0,
|
||||
tracked_staged_count: 0,
|
||||
hide_scrollbar_task: None,
|
||||
update_visible_entries_task: Task::ready(()),
|
||||
width: Some(px(360.)),
|
||||
commit_task: Task::ready(Ok(())),
|
||||
commit_pending: false,
|
||||
active_repository,
|
||||
scroll_handle,
|
||||
fs,
|
||||
commit_editor,
|
||||
project,
|
||||
workspace,
|
||||
conflicted_count: 0,
|
||||
conflicted_staged_count: 0,
|
||||
tracked_staged_count: 0,
|
||||
tracked_count: 0,
|
||||
new_staged_count: 0,
|
||||
new_count: 0,
|
||||
};
|
||||
git_panel.schedule_update(false, window, cx);
|
||||
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
|
||||
@@ -304,12 +308,7 @@ impl GitPanel {
|
||||
git_panel
|
||||
}
|
||||
|
||||
pub fn select_entry_by_path(
|
||||
&mut self,
|
||||
path: ProjectPath,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
pub fn set_focused_path(&mut self, path: ProjectPath, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(git_repo) = self.active_repository.as_ref() else {
|
||||
return;
|
||||
};
|
||||
@@ -319,6 +318,7 @@ impl GitPanel {
|
||||
let Some(ix) = self.entries_by_path.get(&repo_path) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.selected_entry = Some(*ix);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -555,17 +555,10 @@ impl GitPanel {
|
||||
self.selected_entry.and_then(|i| self.entries.get(i))
|
||||
}
|
||||
|
||||
fn open_selected(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
maybe!({
|
||||
let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
|
||||
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
ProjectDiff::deploy_at(workspace, Some(entry.clone()), window, cx);
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
self.focus_handle.focus(window);
|
||||
fn open_selected(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(entry) = self.selected_entry.and_then(|i| self.entries.get(i)) {
|
||||
self.open_entry(entry, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_staged_for_entry(
|
||||
@@ -659,89 +652,135 @@ impl GitPanel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit all staged changes
|
||||
fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let editor = self.commit_editor.read(cx);
|
||||
if editor.is_empty(cx) {
|
||||
if !editor.focus_handle(cx).contains_focused(window, cx) {
|
||||
editor.focus_handle(cx).focus(window);
|
||||
return;
|
||||
}
|
||||
fn open_entry(&self, entry: &GitListEntry, cx: &mut Context<Self>) {
|
||||
let Some(status_entry) = entry.status_entry() else {
|
||||
return;
|
||||
};
|
||||
let Some(active_repository) = self.active_repository.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let Some(path) = active_repository
|
||||
.read(cx)
|
||||
.repo_path_to_project_path(&status_entry.repo_path)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let path_exists = self.project.update(cx, |project, cx| {
|
||||
project.entry_for_path(&path, cx).is_some()
|
||||
});
|
||||
if !path_exists {
|
||||
return;
|
||||
}
|
||||
|
||||
self.commit_changes(window, cx)
|
||||
// TODO maybe move all of this into project?
|
||||
cx.emit(Event::OpenedEntry { path });
|
||||
}
|
||||
|
||||
fn commit_changes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
/// Commit all staged changes
|
||||
fn commit_changes(
|
||||
&mut self,
|
||||
_: &git::CommitChanges,
|
||||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(active_repository) = self.active_repository.clone() else {
|
||||
return;
|
||||
};
|
||||
let error_spawn = |message, window: &mut Window, cx: &mut App| {
|
||||
let prompt = window.prompt(PromptLevel::Warning, message, None, &["Ok"], cx);
|
||||
cx.spawn(|_| async move {
|
||||
prompt.await.ok();
|
||||
if !self.has_staged_changes() {
|
||||
self.commit_tracked_changes(&Default::default(), name_and_email, window, cx);
|
||||
return;
|
||||
}
|
||||
let message = self.commit_editor.read(cx).text(cx);
|
||||
if message.trim().is_empty() {
|
||||
return;
|
||||
}
|
||||
self.commit_pending = true;
|
||||
let commit_editor = self.commit_editor.clone();
|
||||
self.commit_task = cx.spawn_in(window, |git_panel, mut cx| async move {
|
||||
let commit = active_repository.update(&mut cx, |active_repository, _| {
|
||||
active_repository.commit(SharedString::from(message), name_and_email)
|
||||
})?;
|
||||
let result = maybe!(async {
|
||||
commit.await??;
|
||||
cx.update(|window, cx| {
|
||||
commit_editor.update(cx, |editor, cx| editor.clear(window, cx));
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
};
|
||||
.await;
|
||||
|
||||
if self.has_unstaged_conflicts() {
|
||||
error_spawn(
|
||||
"There are still conflicts. You must stage these before committing",
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
git_panel.update(&mut cx, |git_panel, cx| {
|
||||
git_panel.commit_pending = false;
|
||||
result
|
||||
.map_err(|e| {
|
||||
git_panel.show_err_toast(e, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// Commit all changes, regardless of whether they are staged or not
|
||||
fn commit_tracked_changes(
|
||||
&mut self,
|
||||
_: &git::CommitAllChanges,
|
||||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(active_repository) = self.active_repository.clone() else {
|
||||
return;
|
||||
};
|
||||
if !self.has_staged_changes() || !self.has_tracked_changes() {
|
||||
return;
|
||||
}
|
||||
|
||||
let message = self.commit_editor.read(cx).text(cx);
|
||||
if message.trim().is_empty() {
|
||||
self.commit_editor.read(cx).focus_handle(cx).focus(window);
|
||||
return;
|
||||
}
|
||||
|
||||
let task = if self.has_staged_changes() {
|
||||
// Repository serializes all git operations, so we can just send a commit immediately
|
||||
let commit_task = active_repository.read(cx).commit(message.into(), None);
|
||||
cx.background_executor()
|
||||
.spawn(async move { commit_task.await? })
|
||||
} else {
|
||||
let changed_files = self
|
||||
.entries
|
||||
.iter()
|
||||
.filter_map(|entry| entry.status_entry())
|
||||
.filter(|status_entry| !status_entry.status.is_created())
|
||||
.map(|status_entry| status_entry.repo_path.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if changed_files.is_empty() {
|
||||
error_spawn("No changes to commit", window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let stage_task = active_repository.read(cx).stage_entries(changed_files);
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
stage_task.await??;
|
||||
let commit_task = active_repository
|
||||
.update(&mut cx, |repo, _| repo.commit(message.into(), None))?;
|
||||
commit_task.await?
|
||||
self.commit_pending = true;
|
||||
let commit_editor = self.commit_editor.clone();
|
||||
let tracked_files = self
|
||||
.entries
|
||||
.iter()
|
||||
.filter_map(|entry| entry.status_entry())
|
||||
.filter(|status_entry| {
|
||||
!status_entry.status.is_created() && !status_entry.is_staged.unwrap_or(false)
|
||||
})
|
||||
};
|
||||
let task = cx.spawn_in(window, |this, mut cx| async move {
|
||||
let result = task.await;
|
||||
this.update_in(&mut cx, |this, window, cx| {
|
||||
this.pending_commit.take();
|
||||
match result {
|
||||
Ok(()) => {
|
||||
this.commit_editor
|
||||
.update(cx, |editor, cx| editor.clear(window, cx));
|
||||
}
|
||||
Err(e) => this.show_err_toast(e, cx),
|
||||
.map(|status_entry| status_entry.repo_path.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.commit_task = cx.spawn_in(window, |git_panel, mut cx| async move {
|
||||
let result = maybe!(async {
|
||||
cx.update(|_, cx| active_repository.read(cx).stage_entries(tracked_files))?
|
||||
.await??;
|
||||
cx.update(|_, cx| {
|
||||
active_repository
|
||||
.read(cx)
|
||||
.commit(SharedString::from(message), name_and_email)
|
||||
})?
|
||||
.await??;
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
cx.update(|window, cx| match result {
|
||||
Ok(_) => commit_editor.update(cx, |editor, cx| {
|
||||
editor.clear(window, cx);
|
||||
}),
|
||||
|
||||
Err(e) => {
|
||||
git_panel
|
||||
.update(cx, |git_panel, cx| {
|
||||
git_panel.show_err_toast(e, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
})?;
|
||||
|
||||
self.pending_commit = Some(task);
|
||||
git_panel.update(&mut cx, |git_panel, _| {
|
||||
git_panel.commit_pending = false;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn fill_co_authors(&mut self, _: &FillCoAuthors, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -978,26 +1017,6 @@ impl GitPanel {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn toggle_auto_coauthors(&mut self, cx: &mut Context<Self>) {
|
||||
self.enable_auto_coauthors = !self.enable_auto_coauthors;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn header_state(&self, header_type: Section) -> ToggleState {
|
||||
let (staged_count, count) = match header_type {
|
||||
Section::New => (self.new_staged_count, self.new_count),
|
||||
Section::Tracked => (self.tracked_staged_count, self.tracked_count),
|
||||
Section::Conflict => (self.conflicted_staged_count, self.conflicted_count),
|
||||
};
|
||||
if staged_count == 0 {
|
||||
ToggleState::Unselected
|
||||
} else if count == staged_count {
|
||||
ToggleState::Selected
|
||||
} else {
|
||||
ToggleState::Indeterminate
|
||||
}
|
||||
}
|
||||
|
||||
fn update_counts(&mut self, repo: &Repository) {
|
||||
self.conflicted_count = 0;
|
||||
self.conflicted_staged_count = 0;
|
||||
@@ -1038,17 +1057,26 @@ impl GitPanel {
|
||||
}
|
||||
|
||||
fn has_staged_changes(&self) -> bool {
|
||||
self.tracked_staged_count > 0
|
||||
|| self.new_staged_count > 0
|
||||
|| self.conflicted_staged_count > 0
|
||||
self.tracked_staged_count > 0 || self.new_staged_count > 0
|
||||
}
|
||||
|
||||
fn has_tracked_changes(&self) -> bool {
|
||||
self.tracked_count > 0
|
||||
}
|
||||
|
||||
fn has_unstaged_conflicts(&self) -> bool {
|
||||
self.conflicted_count > 0 && self.conflicted_count != self.conflicted_staged_count
|
||||
fn header_state(&self, header_type: Section) -> ToggleState {
|
||||
let (staged_count, count) = match header_type {
|
||||
Section::New => (self.new_staged_count, self.new_count),
|
||||
Section::Tracked => (self.tracked_staged_count, self.tracked_count),
|
||||
Section::Conflict => (self.conflicted_staged_count, self.conflicted_count),
|
||||
};
|
||||
if staged_count == 0 {
|
||||
ToggleState::Unselected
|
||||
} else if count == staged_count {
|
||||
ToggleState::Selected
|
||||
} else {
|
||||
ToggleState::Indeterminate
|
||||
}
|
||||
}
|
||||
|
||||
fn show_err_toast(&self, e: anyhow::Error, cx: &mut App) {
|
||||
@@ -1056,7 +1084,6 @@ impl GitPanel {
|
||||
return;
|
||||
};
|
||||
let notif_id = NotificationId::Named("git-operation-error".into());
|
||||
|
||||
let message = e.to_string();
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let toast = Toast::new(notif_id, message).on_click("Open Zed Log", |window, cx| {
|
||||
@@ -1065,7 +1092,9 @@ impl GitPanel {
|
||||
workspace.show_toast(toast, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl GitPanel {
|
||||
pub fn panel_button(
|
||||
&self,
|
||||
id: impl Into<SharedString>,
|
||||
@@ -1103,43 +1132,33 @@ impl GitPanel {
|
||||
.git_state()
|
||||
.read(cx)
|
||||
.all_repositories();
|
||||
|
||||
let branch = self
|
||||
let entry_count = self
|
||||
.active_repository
|
||||
.as_ref()
|
||||
.and_then(|repository| repository.read(cx).branch())
|
||||
.unwrap_or_else(|| "(no current branch)".into());
|
||||
.map_or(0, |repo| repo.read(cx).entry_count());
|
||||
|
||||
let has_repo_above = all_repositories.iter().any(|repo| {
|
||||
repo.read(cx)
|
||||
.repository_entry
|
||||
.work_directory
|
||||
.is_above_project()
|
||||
});
|
||||
|
||||
let icon_button = Button::new("branch-selector", branch)
|
||||
.color(Color::Muted)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon(IconName::GitBranch)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::Compact)
|
||||
.icon_position(IconPosition::Start)
|
||||
.tooltip(Tooltip::for_action_title(
|
||||
"Switch Branch",
|
||||
&zed_actions::git::Branch,
|
||||
))
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx);
|
||||
}))
|
||||
.style(ButtonStyle::Transparent);
|
||||
let changes_string = match entry_count {
|
||||
0 => "No changes".to_string(),
|
||||
1 => "1 change".to_string(),
|
||||
n => format!("{} changes", n),
|
||||
};
|
||||
|
||||
self.panel_header_container(window, cx)
|
||||
.child(h_flex().pl_1().child(icon_button))
|
||||
.child(h_flex().gap_2().child(if all_repositories.len() <= 1 {
|
||||
div()
|
||||
.id("changes-label")
|
||||
.text_buffer(cx)
|
||||
.text_ui_sm(cx)
|
||||
.child(
|
||||
Label::new(changes_string)
|
||||
.single_line()
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
self.render_repository_selector(cx).into_any_element()
|
||||
}))
|
||||
.child(div().flex_grow())
|
||||
.when(all_repositories.len() > 1 || has_repo_above, |el| {
|
||||
el.child(self.render_repository_selector(cx))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render_repository_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
@@ -1149,30 +1168,46 @@ impl GitPanel {
|
||||
.map(|repo| repo.read(cx).display_name(self.project.read(cx), cx))
|
||||
.unwrap_or_default();
|
||||
|
||||
let entry_count = self.entries.len();
|
||||
|
||||
RepositorySelectorPopoverMenu::new(
|
||||
self.repository_selector.clone(),
|
||||
ButtonLike::new("active-repository")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(Label::new(repository_display_name).size(LabelSize::Small)),
|
||||
Tooltip::text("Select a repository"),
|
||||
.child(
|
||||
h_flex().w_full().gap_0p5().child(
|
||||
div()
|
||||
.overflow_x_hidden()
|
||||
.flex_grow()
|
||||
.whitespace_nowrap()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(repository_display_name).size(LabelSize::Small),
|
||||
)
|
||||
.when(entry_count > 0, |flex| {
|
||||
flex.child(
|
||||
Label::new(format!("({})", entry_count))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
})
|
||||
.into_any_element(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_commit_editor(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let editor = self.commit_editor.clone();
|
||||
let can_commit = (self.has_staged_changes() || self.has_tracked_changes())
|
||||
&& self.pending_commit.is_none()
|
||||
&& !editor.read(cx).is_empty(cx)
|
||||
&& !self.has_unstaged_conflicts()
|
||||
&& self.has_write_access(cx);
|
||||
// let can_commit_all =
|
||||
// !self.commit_pending && self.can_commit_all && !editor.read(cx).is_empty(cx);
|
||||
let panel_editor_style = panel_editor_style(true, window, cx);
|
||||
|
||||
let can_commit =
|
||||
(self.has_staged_changes() || self.has_tracked_changes()) && !self.commit_pending;
|
||||
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
|
||||
|
||||
let focus_handle_1 = self.focus_handle(cx).clone();
|
||||
@@ -1187,60 +1222,42 @@ impl GitPanel {
|
||||
"Commit All"
|
||||
};
|
||||
|
||||
let commit_button = panel_filled_button(title)
|
||||
let commit_button = self
|
||||
.panel_button("commit-changes", title)
|
||||
.tooltip(move |window, cx| {
|
||||
let focus_handle = focus_handle_1.clone();
|
||||
Tooltip::for_action_in(tooltip, &Commit, &focus_handle, window, cx)
|
||||
Tooltip::for_action_in(tooltip, &CommitChanges, &focus_handle, window, cx)
|
||||
})
|
||||
.disabled(!can_commit)
|
||||
.on_click({
|
||||
cx.listener(move |this, _: &ClickEvent, window, cx| this.commit_changes(window, cx))
|
||||
let name_and_email = name_and_email.clone();
|
||||
cx.listener(move |this, _: &ClickEvent, window, cx| {
|
||||
this.commit_changes(&CommitChanges, name_and_email.clone(), window, cx)
|
||||
})
|
||||
});
|
||||
|
||||
let enable_coauthors = CheckboxWithLabel::new(
|
||||
"enable-coauthors",
|
||||
Label::new("Add Co-authors")
|
||||
.color(Color::Disabled)
|
||||
.size(LabelSize::XSmall),
|
||||
self.enable_auto_coauthors.into(),
|
||||
cx.listener(move |this, _, _, cx| this.toggle_auto_coauthors(cx)),
|
||||
);
|
||||
|
||||
let footer_size = px(32.);
|
||||
let gap = px(16.0);
|
||||
|
||||
let max_height = window.line_height() * 6. + gap + footer_size;
|
||||
|
||||
panel_editor_container(window, cx)
|
||||
.id("commit-editor-container")
|
||||
.relative()
|
||||
.h(max_height)
|
||||
.w_full()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
|
||||
window.focus(&editor_focus_handle);
|
||||
}))
|
||||
.child(EditorElement::new(&self.commit_editor, panel_editor_style))
|
||||
.child(
|
||||
h_flex()
|
||||
.absolute()
|
||||
.bottom_0()
|
||||
.left_2()
|
||||
.h(footer_size)
|
||||
.flex_none()
|
||||
.child(enable_coauthors),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.absolute()
|
||||
.bottom_0()
|
||||
.right_2()
|
||||
.h(footer_size)
|
||||
.flex_none()
|
||||
.child(commit_button),
|
||||
)
|
||||
div().w_full().h(px(140.)).px_2().pt_1().pb_2().child(
|
||||
v_flex()
|
||||
.id("commit-editor-container")
|
||||
.relative()
|
||||
.h_full()
|
||||
.py_2p5()
|
||||
.px_3()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
|
||||
window.focus(&editor_focus_handle);
|
||||
}))
|
||||
.child(self.commit_editor.clone())
|
||||
.child(
|
||||
h_flex()
|
||||
.absolute()
|
||||
.bottom_2p5()
|
||||
.right_3()
|
||||
.gap_1p5()
|
||||
.child(div().gap_1().flex_grow())
|
||||
.child(commit_button),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_empty_state(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
@@ -1252,11 +1269,7 @@ impl GitPanel {
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_3()
|
||||
.child(if self.active_repository.is_some() {
|
||||
"No changes to commit"
|
||||
} else {
|
||||
"No Git repositories"
|
||||
})
|
||||
.child("No changes to commit")
|
||||
.text_ui_sm(cx)
|
||||
.mx_auto()
|
||||
.text_color(Color::Placeholder.color(cx)),
|
||||
@@ -1370,7 +1383,6 @@ impl GitPanel {
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
.flex_grow()
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
uniform_list(cx.entity().clone(), "entries", entry_count, {
|
||||
@@ -1476,10 +1488,9 @@ impl GitPanel {
|
||||
ix: usize,
|
||||
header: &GitHeaderEntry,
|
||||
has_write_access: bool,
|
||||
window: &Window,
|
||||
_window: &Window,
|
||||
cx: &Context<Self>,
|
||||
) -> AnyElement {
|
||||
let selected = self.selected_entry == Some(ix);
|
||||
let header_state = if self.has_staged_changes() {
|
||||
self.header_state(header.header)
|
||||
} else {
|
||||
@@ -1488,46 +1499,34 @@ impl GitPanel {
|
||||
Section::New => ToggleState::Unselected,
|
||||
}
|
||||
};
|
||||
|
||||
let checkbox = Checkbox::new(("checkbox", ix), header_state)
|
||||
let checkbox = Checkbox::new(header.title(), header_state)
|
||||
.disabled(!has_write_access)
|
||||
.fill()
|
||||
.placeholder(!self.has_staged_changes())
|
||||
.elevation(ElevationIndex::Surface)
|
||||
.on_click({
|
||||
let header = header.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.toggle_staged_for_entry(&GitListEntry::Header(header.clone()), window, cx);
|
||||
cx.stop_propagation();
|
||||
})
|
||||
});
|
||||
|
||||
let start_slot = h_flex()
|
||||
.id(("start-slot", ix))
|
||||
.gap(DynamicSpacing::Base04.rems(cx))
|
||||
.child(checkbox)
|
||||
.tooltip(|window, cx| Tooltip::for_action("Stage File", &ToggleStaged, window, cx))
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| {
|
||||
// prevent the list item active state triggering when toggling checkbox
|
||||
cx.stop_propagation();
|
||||
});
|
||||
.fill()
|
||||
.elevation(ElevationIndex::Surface);
|
||||
let selected = self.selected_entry == Some(ix);
|
||||
|
||||
div()
|
||||
.w_full()
|
||||
.child(
|
||||
ListItem::new(ix)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.start_slot(start_slot)
|
||||
ListHeader::new(header.title())
|
||||
.start_slot(checkbox)
|
||||
.toggle_state(selected)
|
||||
.focused(selected && self.focus_handle(cx).is_focused(window))
|
||||
.disabled(!has_write_access)
|
||||
.on_click({
|
||||
cx.listener(move |this, _, _, cx| {
|
||||
.on_toggle({
|
||||
let header = header.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
if !has_write_access {
|
||||
return;
|
||||
}
|
||||
this.selected_entry = Some(ix);
|
||||
cx.notify();
|
||||
this.toggle_staged_for_entry(
|
||||
&GitListEntry::Header(header.clone()),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.child(h_flex().child(self.entry_label(header.title(), Color::Muted))),
|
||||
.inset(true),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -1615,6 +1614,7 @@ impl GitPanel {
|
||||
|
||||
div()
|
||||
.w_full()
|
||||
.px_0p5()
|
||||
.child(
|
||||
ListItem::new(id)
|
||||
.indent_level(1)
|
||||
@@ -1622,13 +1622,17 @@ impl GitPanel {
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.start_slot(start_slot)
|
||||
.toggle_state(selected)
|
||||
.focused(selected && self.focus_handle(cx).is_focused(window))
|
||||
.disabled(!has_write_access)
|
||||
.on_click({
|
||||
let entry = entry.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.selected_entry = Some(ix);
|
||||
cx.notify();
|
||||
this.open_selected(&Default::default(), window, cx);
|
||||
let Some(workspace) = this.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
ProjectDiff::deploy_at(workspace, Some(entry.clone()), window, cx);
|
||||
})
|
||||
})
|
||||
})
|
||||
.child(
|
||||
@@ -1656,7 +1660,13 @@ impl GitPanel {
|
||||
}
|
||||
|
||||
fn has_write_access(&self, cx: &App) -> bool {
|
||||
!self.project.read(cx).is_read_only(cx)
|
||||
let room = self
|
||||
.workspace
|
||||
.upgrade()
|
||||
.and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned());
|
||||
|
||||
room.as_ref()
|
||||
.map_or(true, |room| room.read(cx).local_participant().can_write())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1674,14 +1684,43 @@ impl Render for GitPanel {
|
||||
.upgrade()
|
||||
.and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned());
|
||||
|
||||
let has_write_access = self.has_write_access(cx);
|
||||
let has_write_access = room
|
||||
.as_ref()
|
||||
.map_or(true, |room| room.read(cx).local_participant().can_write());
|
||||
let (can_commit, name_and_email) = match &room {
|
||||
Some(room) => {
|
||||
if project.is_via_collab() {
|
||||
if has_write_access {
|
||||
let name_and_email =
|
||||
room.read(cx).local_participant_user(cx).and_then(|user| {
|
||||
let email = SharedString::from(user.email.clone()?);
|
||||
let name = user
|
||||
.name
|
||||
.clone()
|
||||
.map(SharedString::from)
|
||||
.unwrap_or(SharedString::from(user.github_login.clone()));
|
||||
Some((name, email))
|
||||
});
|
||||
(name_and_email.is_some(), name_and_email)
|
||||
} else {
|
||||
(false, None)
|
||||
}
|
||||
} else {
|
||||
(has_write_access, None)
|
||||
}
|
||||
}
|
||||
None => (has_write_access, None),
|
||||
};
|
||||
let can_commit = !self.commit_pending && can_commit;
|
||||
|
||||
let has_co_authors = room.map_or(false, |room| {
|
||||
room.read(cx)
|
||||
.remote_participants()
|
||||
.values()
|
||||
.any(|remote_participant| remote_participant.can_write())
|
||||
});
|
||||
let has_co_authors = can_commit
|
||||
&& has_write_access
|
||||
&& room.map_or(false, |room| {
|
||||
room.read(cx)
|
||||
.remote_participants()
|
||||
.values()
|
||||
.any(|remote_participant| remote_participant.can_write())
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.id("git_panel")
|
||||
@@ -1692,7 +1731,31 @@ impl Render for GitPanel {
|
||||
this.on_action(cx.listener(|this, &ToggleStaged, window, cx| {
|
||||
this.toggle_staged_for_selected(&ToggleStaged, window, cx)
|
||||
}))
|
||||
.on_action(cx.listener(GitPanel::commit))
|
||||
.when(can_commit, |git_panel| {
|
||||
git_panel
|
||||
.on_action({
|
||||
let name_and_email = name_and_email.clone();
|
||||
cx.listener(move |git_panel, &CommitChanges, window, cx| {
|
||||
git_panel.commit_changes(
|
||||
&CommitChanges,
|
||||
name_and_email.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.on_action({
|
||||
let name_and_email = name_and_email.clone();
|
||||
cx.listener(move |git_panel, &CommitAllChanges, window, cx| {
|
||||
git_panel.commit_tracked_changes(
|
||||
&CommitAllChanges,
|
||||
name_and_email.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
.when(self.is_focused(window, cx), |this| {
|
||||
this.on_action(cx.listener(Self::select_first))
|
||||
@@ -1705,7 +1768,7 @@ impl Render for GitPanel {
|
||||
.on_action(cx.listener(Self::focus_changes_list))
|
||||
.on_action(cx.listener(Self::focus_editor))
|
||||
.on_action(cx.listener(Self::toggle_staged_for_selected))
|
||||
.when(has_write_access && has_co_authors, |git_panel| {
|
||||
.when(has_co_authors, |git_panel| {
|
||||
git_panel.on_action(cx.listener(Self::fill_co_authors))
|
||||
})
|
||||
// .on_action(cx.listener(|this, &OpenSelected, cx| this.open_selected(&OpenSelected, cx)))
|
||||
@@ -1728,7 +1791,7 @@ impl Render for GitPanel {
|
||||
} else {
|
||||
self.render_empty_state(cx).into_any_element()
|
||||
})
|
||||
.child(self.render_commit_editor(window, cx))
|
||||
.child(self.render_commit_editor(name_and_email, cx))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,18 +5,14 @@ use gpui::App;
|
||||
use project_diff::ProjectDiff;
|
||||
use ui::{ActiveTheme, Color, Icon, IconName, IntoElement};
|
||||
|
||||
pub mod branch_picker;
|
||||
pub mod git_panel;
|
||||
mod git_panel_settings;
|
||||
pub mod project_diff;
|
||||
// mod quick_commit;
|
||||
pub mod repository_selector;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
GitPanelSettings::register(cx);
|
||||
branch_picker::init(cx);
|
||||
cx.observe_new(ProjectDiff::register).detach();
|
||||
// quick_commit::init(cx);
|
||||
}
|
||||
|
||||
// TODO: Add updated status colors to theme
|
||||
|
||||
@@ -235,7 +235,7 @@ impl ProjectDiff {
|
||||
.update(cx, |workspace, cx| {
|
||||
if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
|
||||
git_panel.update(cx, |git_panel, cx| {
|
||||
git_panel.select_entry_by_path(project_path.into(), window, cx)
|
||||
git_panel.set_focused_path(project_path.into(), window, cx)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,307 +0,0 @@
|
||||
#![allow(unused, dead_code)]
|
||||
|
||||
use crate::repository_selector::RepositorySelector;
|
||||
use anyhow::Result;
|
||||
use git::{CommitAllChanges, CommitChanges};
|
||||
use language::Buffer;
|
||||
use panel::{panel_editor_container, panel_editor_style, panel_filled_button, panel_icon_button};
|
||||
use ui::{prelude::*, Tooltip};
|
||||
|
||||
use editor::{Editor, EditorElement, EditorMode, MultiBuffer};
|
||||
use gpui::*;
|
||||
use project::git::Repository;
|
||||
use project::{Fs, Project};
|
||||
use std::sync::Arc;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
actions!(
|
||||
git,
|
||||
[QuickCommitWithMessage, QuickCommitStaged, QuickCommitAll]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.observe_new(|workspace: &mut Workspace, window, cx| {
|
||||
let Some(window) = window else {
|
||||
return;
|
||||
};
|
||||
QuickCommitModal::register(workspace, window, cx)
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn commit_message_editor(
|
||||
commit_message_buffer: Option<Entity<Buffer>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, Editor>,
|
||||
) -> Editor {
|
||||
let mut commit_editor = if let Some(commit_message_buffer) = commit_message_buffer {
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx));
|
||||
Editor::new(
|
||||
EditorMode::AutoHeight { max_lines: 10 },
|
||||
buffer,
|
||||
None,
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Editor::auto_height(10, window, cx)
|
||||
};
|
||||
commit_editor.set_use_autoclose(false);
|
||||
commit_editor.set_show_gutter(false, cx);
|
||||
commit_editor.set_show_wrap_guides(false, cx);
|
||||
commit_editor.set_show_indent_guides(false, cx);
|
||||
commit_editor.set_placeholder_text("Enter commit message", cx);
|
||||
commit_editor
|
||||
}
|
||||
|
||||
pub struct QuickCommitModal {
|
||||
focus_handle: FocusHandle,
|
||||
fs: Arc<dyn Fs>,
|
||||
project: Entity<Project>,
|
||||
active_repository: Option<Entity<Repository>>,
|
||||
repository_selector: Entity<RepositorySelector>,
|
||||
commit_editor: Entity<Editor>,
|
||||
width: Option<Pixels>,
|
||||
commit_task: Task<Result<()>>,
|
||||
commit_pending: bool,
|
||||
can_commit: bool,
|
||||
can_commit_all: bool,
|
||||
enable_auto_coauthors: bool,
|
||||
}
|
||||
|
||||
impl Focusable for QuickCommitModal {
|
||||
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for QuickCommitModal {}
|
||||
impl ModalView for QuickCommitModal {}
|
||||
|
||||
impl QuickCommitModal {
|
||||
pub fn register(workspace: &mut Workspace, _: &mut Window, cx: &mut Context<Workspace>) {
|
||||
workspace.register_action(|workspace, _: &QuickCommitWithMessage, window, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
|
||||
workspace.toggle_modal(window, cx, move |window, cx| {
|
||||
QuickCommitModal::new(project, fs, window, None, cx)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
project: Entity<Project>,
|
||||
fs: Arc<dyn Fs>,
|
||||
window: &mut Window,
|
||||
commit_message_buffer: Option<Entity<Buffer>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let git_state = project.read(cx).git_state().clone();
|
||||
let active_repository = project.read(cx).active_repository(cx);
|
||||
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
let commit_editor = cx.new(|cx| commit_message_editor(commit_message_buffer, window, cx));
|
||||
commit_editor.update(cx, |editor, cx| {
|
||||
editor.clear(window, cx);
|
||||
});
|
||||
|
||||
let repository_selector = cx.new(|cx| RepositorySelector::new(project.clone(), window, cx));
|
||||
|
||||
Self {
|
||||
focus_handle,
|
||||
fs,
|
||||
project,
|
||||
active_repository,
|
||||
repository_selector,
|
||||
commit_editor,
|
||||
width: None,
|
||||
commit_task: Task::ready(Ok(())),
|
||||
commit_pending: false,
|
||||
can_commit: false,
|
||||
can_commit_all: false,
|
||||
enable_auto_coauthors: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_header(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let all_repositories = self
|
||||
.project
|
||||
.read(cx)
|
||||
.git_state()
|
||||
.read(cx)
|
||||
.all_repositories();
|
||||
let entry_count = self
|
||||
.active_repository
|
||||
.as_ref()
|
||||
.map_or(0, |repo| repo.read(cx).entry_count());
|
||||
|
||||
let changes_string = match entry_count {
|
||||
0 => "No changes".to_string(),
|
||||
1 => "1 change".to_string(),
|
||||
n => format!("{} changes", n),
|
||||
};
|
||||
|
||||
div().absolute().top_0().right_0().child(
|
||||
panel_icon_button("open_change_list", IconName::PanelRight)
|
||||
.disabled(true)
|
||||
.tooltip(Tooltip::text("Changes list coming soon!")),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_commit_editor(
|
||||
&self,
|
||||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let editor = self.commit_editor.clone();
|
||||
let can_commit = !self.commit_pending && self.can_commit && !editor.read(cx).is_empty(cx);
|
||||
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
|
||||
|
||||
let focus_handle_1 = self.focus_handle(cx).clone();
|
||||
let focus_handle_2 = self.focus_handle(cx).clone();
|
||||
|
||||
let panel_editor_style = panel_editor_style(true, window, cx);
|
||||
|
||||
let commit_staged_button = panel_filled_button("Commit")
|
||||
.tooltip(move |window, cx| {
|
||||
let focus_handle = focus_handle_1.clone();
|
||||
Tooltip::for_action_in(
|
||||
"Commit all staged changes",
|
||||
&CommitChanges,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.when(!can_commit, |this| {
|
||||
this.disabled(true).style(ButtonStyle::Transparent)
|
||||
});
|
||||
// .on_click({
|
||||
// let name_and_email = name_and_email.clone();
|
||||
// cx.listener(move |this, _: &ClickEvent, window, cx| {
|
||||
// this.commit_changes(&CommitChanges, name_and_email.clone(), window, cx)
|
||||
// })
|
||||
// });
|
||||
|
||||
let commit_all_button = panel_filled_button("Commit All")
|
||||
.tooltip(move |window, cx| {
|
||||
let focus_handle = focus_handle_2.clone();
|
||||
Tooltip::for_action_in(
|
||||
"Commit all changes, including unstaged changes",
|
||||
&CommitAllChanges,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.when(!can_commit, |this| {
|
||||
this.disabled(true).style(ButtonStyle::Transparent)
|
||||
});
|
||||
// .on_click({
|
||||
// let name_and_email = name_and_email.clone();
|
||||
// cx.listener(move |this, _: &ClickEvent, window, cx| {
|
||||
// this.commit_tracked_changes(
|
||||
// &CommitAllChanges,
|
||||
// name_and_email.clone(),
|
||||
// window,
|
||||
// cx,
|
||||
// )
|
||||
// })
|
||||
// });
|
||||
|
||||
let co_author_button = panel_icon_button("add-co-author", IconName::UserGroup)
|
||||
.icon_color(if self.enable_auto_coauthors {
|
||||
Color::Muted
|
||||
} else {
|
||||
Color::Accent
|
||||
})
|
||||
.icon_size(IconSize::Small)
|
||||
.toggle_state(self.enable_auto_coauthors)
|
||||
// .on_click({
|
||||
// cx.listener(move |this, _: &ClickEvent, _, cx| {
|
||||
// this.toggle_auto_coauthors(cx);
|
||||
// })
|
||||
// })
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Toggle automatic co-authors",
|
||||
None,
|
||||
"Automatically adds current collaborators",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
panel_editor_container(window, cx)
|
||||
.id("commit-editor-container")
|
||||
.relative()
|
||||
.w_full()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.h(px(140.))
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
|
||||
window.focus(&editor_focus_handle);
|
||||
}))
|
||||
.child(EditorElement::new(&self.commit_editor, panel_editor_style))
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.h_8()
|
||||
.justify_between()
|
||||
.gap_1()
|
||||
.child(co_author_button)
|
||||
.child(commit_all_button)
|
||||
.child(commit_staged_button),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(h_flex().child("cmd+esc clear message"))
|
||||
.child(
|
||||
h_flex()
|
||||
.child(panel_filled_button("Commit"))
|
||||
.child(panel_filled_button("Commit All")),
|
||||
)
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for QuickCommitModal {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.id("quick-commit-modal")
|
||||
.key_context("QuickCommit")
|
||||
.on_action(cx.listener(Self::dismiss))
|
||||
.relative()
|
||||
.bg(cx.theme().colors().elevated_surface_background)
|
||||
.rounded(px(16.))
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.py_2()
|
||||
.px_4()
|
||||
.w(self.width.unwrap_or(px(640.)))
|
||||
.h(px(450.))
|
||||
.flex_1()
|
||||
.overflow_hidden()
|
||||
.child(self.render_header(window, cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.flex_1()
|
||||
// TODO: pass name_and_email
|
||||
.child(self.render_commit_editor(None, window, cx)),
|
||||
)
|
||||
.child(self.render_footer(window, cx))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{
|
||||
AnyElement, AnyView, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
Subscription, Task, WeakEntity,
|
||||
AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
|
||||
Task, WeakEntity,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{
|
||||
@@ -34,7 +34,6 @@ impl RepositorySelector {
|
||||
let picker = cx.new(|cx| {
|
||||
Picker::nonsearchable_uniform_list(delegate, window, cx)
|
||||
.max_height(Some(rems(20.).into()))
|
||||
.width(rems(15.))
|
||||
});
|
||||
|
||||
let _subscriptions =
|
||||
@@ -79,27 +78,20 @@ impl Render for RepositorySelector {
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct RepositorySelectorPopoverMenu<T, TT>
|
||||
pub struct RepositorySelectorPopoverMenu<T>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
T: PopoverTrigger,
|
||||
{
|
||||
repository_selector: Entity<RepositorySelector>,
|
||||
trigger: T,
|
||||
tooltip: TT,
|
||||
handle: Option<PopoverMenuHandle<RepositorySelector>>,
|
||||
}
|
||||
|
||||
impl<T, TT> RepositorySelectorPopoverMenu<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
pub fn new(repository_selector: Entity<RepositorySelector>, trigger: T, tooltip: TT) -> Self {
|
||||
impl<T: PopoverTrigger> RepositorySelectorPopoverMenu<T> {
|
||||
pub fn new(repository_selector: Entity<RepositorySelector>, trigger: T) -> Self {
|
||||
Self {
|
||||
repository_selector,
|
||||
trigger,
|
||||
tooltip,
|
||||
handle: None,
|
||||
}
|
||||
}
|
||||
@@ -110,17 +102,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, TT> RenderOnce for RepositorySelectorPopoverMenu<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
impl<T: PopoverTrigger> RenderOnce for RepositorySelectorPopoverMenu<T> {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
let repository_selector = self.repository_selector.clone();
|
||||
|
||||
PopoverMenu::new("repository-switcher")
|
||||
.menu(move |_window, _cx| Some(repository_selector.clone()))
|
||||
.trigger_with_tooltip(self.trigger, self.tooltip)
|
||||
.trigger(self.trigger)
|
||||
.attach(gpui::Corner::BottomLeft)
|
||||
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
|
||||
}
|
||||
|
||||
@@ -305,14 +305,8 @@ pub enum Model {
|
||||
Gemini15Pro,
|
||||
#[serde(rename = "gemini-1.5-flash")]
|
||||
Gemini15Flash,
|
||||
#[serde(rename = "gemini-2.0-pro-exp")]
|
||||
Gemini20Pro,
|
||||
#[serde(rename = "gemini-2.0-flash")]
|
||||
#[serde(rename = "gemini-2.0-flash-exp")]
|
||||
Gemini20Flash,
|
||||
#[serde(rename = "gemini-2.0-flash-thinking-exp")]
|
||||
Gemini20FlashThinking,
|
||||
#[serde(rename = "gemini-2.0-flash-lite-preview")]
|
||||
Gemini20FlashLite,
|
||||
#[serde(rename = "custom")]
|
||||
Custom {
|
||||
name: String,
|
||||
@@ -327,10 +321,7 @@ impl Model {
|
||||
match self {
|
||||
Model::Gemini15Pro => "gemini-1.5-pro",
|
||||
Model::Gemini15Flash => "gemini-1.5-flash",
|
||||
Model::Gemini20Pro => "gemini-2.0-pro-exp",
|
||||
Model::Gemini20Flash => "gemini-2.0-flash",
|
||||
Model::Gemini20FlashThinking => "gemini-2.0-flash-thinking-exp",
|
||||
Model::Gemini20FlashLite => "gemini-2.0-flash-lite-preview",
|
||||
Model::Gemini20Flash => "gemini-2.0-flash-exp",
|
||||
Model::Custom { name, .. } => name,
|
||||
}
|
||||
}
|
||||
@@ -339,10 +330,7 @@ impl Model {
|
||||
match self {
|
||||
Model::Gemini15Pro => "Gemini 1.5 Pro",
|
||||
Model::Gemini15Flash => "Gemini 1.5 Flash",
|
||||
Model::Gemini20Pro => "Gemini 2.0 Pro",
|
||||
Model::Gemini20Flash => "Gemini 2.0 Flash",
|
||||
Model::Gemini20FlashThinking => "Gemini 2.0 Flash Thinking",
|
||||
Model::Gemini20FlashLite => "Gemini 2.0 Flash Lite",
|
||||
Self::Custom {
|
||||
name, display_name, ..
|
||||
} => display_name.as_ref().unwrap_or(name),
|
||||
@@ -353,10 +341,7 @@ impl Model {
|
||||
match self {
|
||||
Model::Gemini15Pro => 2_000_000,
|
||||
Model::Gemini15Flash => 1_000_000,
|
||||
Model::Gemini20Pro => 2_000_000,
|
||||
Model::Gemini20Flash => 1_000_000,
|
||||
Model::Gemini20FlashThinking => 1_000_000,
|
||||
Model::Gemini20FlashLite => 1_000_000,
|
||||
Model::Custom { max_tokens, .. } => *max_tokens,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,479 +0,0 @@
|
||||
use std::{
|
||||
ops::Range,
|
||||
rc::Rc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use gpui::{
|
||||
canvas, div, point, prelude::*, px, rgb, size, uniform_list, App, Application, Bounds, Context,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Render, SharedString,
|
||||
UniformListScrollHandle, Window, WindowBounds, WindowOptions,
|
||||
};
|
||||
|
||||
const TOTAL_ITEMS: usize = 10000;
|
||||
const SCROLLBAR_THUMB_WIDTH: Pixels = px(8.);
|
||||
const SCROLLBAR_THUMB_HEIGHT: Pixels = px(100.);
|
||||
|
||||
pub struct Quote {
|
||||
name: SharedString,
|
||||
symbol: SharedString,
|
||||
last_done: f64,
|
||||
prev_close: f64,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
timestamp: Instant,
|
||||
volume: i64,
|
||||
turnover: f64,
|
||||
ttm: f64,
|
||||
market_cap: f64,
|
||||
float_cap: f64,
|
||||
shares: f64,
|
||||
pb: f64,
|
||||
pe: f64,
|
||||
eps: f64,
|
||||
dividend: f64,
|
||||
dividend_yield: f64,
|
||||
dividend_per_share: f64,
|
||||
dividend_date: SharedString,
|
||||
dividend_payment: f64,
|
||||
}
|
||||
|
||||
impl Quote {
|
||||
pub fn random() -> Self {
|
||||
use rand::Rng;
|
||||
let mut rng = rand::thread_rng();
|
||||
// simulate a base price in a realistic range
|
||||
let prev_close = rng.gen_range(100.0..200.0);
|
||||
let change = rng.gen_range(-5.0..5.0);
|
||||
let last_done = prev_close + change;
|
||||
let open = prev_close + rng.gen_range(-3.0..3.0);
|
||||
let high = (prev_close + rng.gen_range::<f64, _>(0.0..10.0)).max(open);
|
||||
let low = (prev_close - rng.gen_range::<f64, _>(0.0..10.0)).min(open);
|
||||
// Randomize the timestamp in the past 24 hours
|
||||
let timestamp = Instant::now() - Duration::from_secs(rng.gen_range(0..86400));
|
||||
let volume = rng.gen_range(1_000_000..100_000_000);
|
||||
let turnover = last_done * volume as f64;
|
||||
let symbol = {
|
||||
let mut ticker = String::new();
|
||||
if rng.gen_bool(0.5) {
|
||||
ticker.push_str(&format!(
|
||||
"{:03}.{}",
|
||||
rng.gen_range(100..1000),
|
||||
rng.gen_range(0..10)
|
||||
));
|
||||
} else {
|
||||
ticker.push_str(&format!(
|
||||
"{}{}",
|
||||
rng.gen_range('A'..='Z'),
|
||||
rng.gen_range('A'..='Z')
|
||||
));
|
||||
}
|
||||
ticker.push_str(&format!(".{}", rng.gen_range('A'..='Z')));
|
||||
ticker
|
||||
};
|
||||
let name = format!(
|
||||
"{} {} - #{}",
|
||||
symbol,
|
||||
rng.gen_range(1..100),
|
||||
rng.gen_range(10000..100000)
|
||||
);
|
||||
let ttm = rng.gen_range(0.0..10.0);
|
||||
let market_cap = rng.gen_range(1_000_000.0..10_000_000.0);
|
||||
let float_cap = market_cap + rng.gen_range(1_000.0..10_000.0);
|
||||
let shares = rng.gen_range(100.0..1000.0);
|
||||
let pb = market_cap / shares;
|
||||
let pe = market_cap / shares;
|
||||
let eps = market_cap / shares;
|
||||
let dividend = rng.gen_range(0.0..10.0);
|
||||
let dividend_yield = rng.gen_range(0.0..10.0);
|
||||
let dividend_per_share = rng.gen_range(0.0..10.0);
|
||||
let dividend_date = SharedString::new(format!(
|
||||
"{}-{}-{}",
|
||||
rng.gen_range(2000..2023),
|
||||
rng.gen_range(1..12),
|
||||
rng.gen_range(1..28)
|
||||
));
|
||||
let dividend_payment = rng.gen_range(0.0..10.0);
|
||||
|
||||
Self {
|
||||
name: name.into(),
|
||||
symbol: symbol.into(),
|
||||
last_done,
|
||||
prev_close,
|
||||
open,
|
||||
high,
|
||||
low,
|
||||
timestamp,
|
||||
volume,
|
||||
turnover,
|
||||
pb,
|
||||
pe,
|
||||
eps,
|
||||
ttm,
|
||||
market_cap,
|
||||
float_cap,
|
||||
shares,
|
||||
dividend,
|
||||
dividend_yield,
|
||||
dividend_per_share,
|
||||
dividend_date,
|
||||
dividend_payment,
|
||||
}
|
||||
}
|
||||
|
||||
fn change(&self) -> f64 {
|
||||
(self.last_done - self.prev_close) / self.prev_close * 100.0
|
||||
}
|
||||
|
||||
fn change_color(&self) -> gpui::Hsla {
|
||||
if self.change() > 0.0 {
|
||||
gpui::green()
|
||||
} else {
|
||||
gpui::red()
|
||||
}
|
||||
}
|
||||
|
||||
fn turnover_ratio(&self) -> f64 {
|
||||
self.volume as f64 / self.turnover * 100.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
struct TableRow {
|
||||
ix: usize,
|
||||
quote: Rc<Quote>,
|
||||
}
|
||||
impl TableRow {
|
||||
fn new(ix: usize, quote: Rc<Quote>) -> Self {
|
||||
Self { ix, quote }
|
||||
}
|
||||
|
||||
fn render_cell(&self, key: &str, width: Pixels, color: gpui::Hsla) -> impl IntoElement {
|
||||
div()
|
||||
.whitespace_nowrap()
|
||||
.truncate()
|
||||
.w(width)
|
||||
.px_1()
|
||||
.child(match key {
|
||||
"id" => div().child(format!("{}", self.ix)),
|
||||
"symbol" => div().child(self.quote.symbol.clone()),
|
||||
"name" => div().child(self.quote.name.clone()),
|
||||
"last_done" => div()
|
||||
.text_color(color)
|
||||
.child(format!("{:.3}", self.quote.last_done)),
|
||||
"prev_close" => div()
|
||||
.text_color(color)
|
||||
.child(format!("{:.3}", self.quote.prev_close)),
|
||||
"change" => div()
|
||||
.text_color(color)
|
||||
.child(format!("{:.2}%", self.quote.change())),
|
||||
"timestamp" => div()
|
||||
.text_color(color)
|
||||
.child(format!("{:?}", self.quote.timestamp.elapsed().as_secs())),
|
||||
"open" => div()
|
||||
.text_color(color)
|
||||
.child(format!("{:.2}", self.quote.open)),
|
||||
"low" => div()
|
||||
.text_color(color)
|
||||
.child(format!("{:.2}", self.quote.low)),
|
||||
"high" => div()
|
||||
.text_color(color)
|
||||
.child(format!("{:.2}", self.quote.high)),
|
||||
"ttm" => div()
|
||||
.text_color(color)
|
||||
.child(format!("{:.2}", self.quote.ttm)),
|
||||
"eps" => div()
|
||||
.text_color(color)
|
||||
.child(format!("{:.2}", self.quote.eps)),
|
||||
"market_cap" => {
|
||||
div().child(format!("{:.2} M", self.quote.market_cap / 1_000_000.0))
|
||||
}
|
||||
"float_cap" => div().child(format!("{:.2} M", self.quote.float_cap / 1_000_000.0)),
|
||||
"turnover" => div().child(format!("{:.2} M", self.quote.turnover / 1_000_000.0)),
|
||||
"volume" => div().child(format!("{:.2} M", self.quote.volume as f64 / 1_000_000.0)),
|
||||
"turnover_ratio" => div().child(format!("{:.2}%", self.quote.turnover_ratio())),
|
||||
"pe" => div().child(format!("{:.2}", self.quote.pe)),
|
||||
"pb" => div().child(format!("{:.2}", self.quote.pb)),
|
||||
"shares" => div().child(format!("{:.2}", self.quote.shares)),
|
||||
"dividend" => div().child(format!("{:.2}", self.quote.dividend)),
|
||||
"yield" => div().child(format!("{:.2}%", self.quote.dividend_yield)),
|
||||
"dividend_per_share" => {
|
||||
div().child(format!("{:.2}", self.quote.dividend_per_share))
|
||||
}
|
||||
"dividend_date" => div().child(format!("{}", self.quote.dividend_date)),
|
||||
"dividend_payment" => div().child(format!("{:.2}", self.quote.dividend_payment)),
|
||||
_ => div().child("--"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const FIELDS: [(&str, f32); 24] = [
|
||||
("id", 64.),
|
||||
("symbol", 64.),
|
||||
("name", 180.),
|
||||
("last_done", 80.),
|
||||
("prev_close", 80.),
|
||||
("open", 80.),
|
||||
("low", 80.),
|
||||
("high", 80.),
|
||||
("ttm", 50.),
|
||||
("market_cap", 96.),
|
||||
("float_cap", 96.),
|
||||
("turnover", 120.),
|
||||
("volume", 100.),
|
||||
("turnover_ratio", 96.),
|
||||
("pe", 64.),
|
||||
("pb", 64.),
|
||||
("eps", 64.),
|
||||
("shares", 96.),
|
||||
("dividend", 64.),
|
||||
("yield", 64.),
|
||||
("dividend_per_share", 64.),
|
||||
("dividend_date", 96.),
|
||||
("dividend_payment", 64.),
|
||||
("timestamp", 120.),
|
||||
];
|
||||
|
||||
impl RenderOnce for TableRow {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
let color = self.quote.change_color();
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.border_b_1()
|
||||
.border_color(rgb(0xE0E0E0))
|
||||
.bg(if self.ix % 2 == 0 {
|
||||
rgb(0xFFFFFF)
|
||||
} else {
|
||||
rgb(0xFAFAFA)
|
||||
})
|
||||
.py_0p5()
|
||||
.px_2()
|
||||
.children(FIELDS.map(|(key, width)| self.render_cell(key, px(width), color)))
|
||||
}
|
||||
}
|
||||
|
||||
struct DataTable {
|
||||
/// Use `Rc` to share the same quote data across multiple items, avoid cloning.
|
||||
quotes: Vec<Rc<Quote>>,
|
||||
visible_range: Range<usize>,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
/// The position in thumb bounds when dragging start mouse down.
|
||||
drag_position: Option<Point<Pixels>>,
|
||||
}
|
||||
|
||||
impl DataTable {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
quotes: Vec::new(),
|
||||
visible_range: 0..0,
|
||||
scroll_handle: UniformListScrollHandle::new(),
|
||||
drag_position: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate(&mut self) {
|
||||
self.quotes = (0..TOTAL_ITEMS).map(|_| Rc::new(Quote::random())).collect();
|
||||
}
|
||||
|
||||
fn table_bounds(&self) -> Bounds<Pixels> {
|
||||
self.scroll_handle.0.borrow().base_handle.bounds()
|
||||
}
|
||||
|
||||
fn scroll_top(&self) -> Pixels {
|
||||
self.scroll_handle.0.borrow().base_handle.offset().y
|
||||
}
|
||||
|
||||
fn scroll_height(&self) -> Pixels {
|
||||
self.scroll_handle
|
||||
.0
|
||||
.borrow()
|
||||
.last_item_size
|
||||
.unwrap_or_default()
|
||||
.contents
|
||||
.height
|
||||
}
|
||||
|
||||
fn render_scrollbar(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let scroll_height = self.scroll_height();
|
||||
let table_bounds = self.table_bounds();
|
||||
let table_height = table_bounds.size.height;
|
||||
if table_height == px(0.) {
|
||||
return div().id("scrollbar");
|
||||
}
|
||||
|
||||
let percentage = -self.scroll_top() / scroll_height;
|
||||
let offset_top = (table_height * percentage).clamp(
|
||||
px(4.),
|
||||
(table_height - SCROLLBAR_THUMB_HEIGHT - px(4.)).max(px(4.)),
|
||||
);
|
||||
let entity = cx.entity();
|
||||
let scroll_handle = self.scroll_handle.0.borrow().base_handle.clone();
|
||||
|
||||
div()
|
||||
.id("scrollbar")
|
||||
.absolute()
|
||||
.top(offset_top)
|
||||
.right_1()
|
||||
.h(SCROLLBAR_THUMB_HEIGHT)
|
||||
.w(SCROLLBAR_THUMB_WIDTH)
|
||||
.bg(rgb(0xC0C0C0))
|
||||
.hover(|this| this.bg(rgb(0xA0A0A0)))
|
||||
.rounded_lg()
|
||||
.child(
|
||||
canvas(
|
||||
|_, _, _| (),
|
||||
move |thumb_bounds, _, window, _| {
|
||||
window.on_mouse_event({
|
||||
let entity = entity.clone();
|
||||
move |ev: &MouseDownEvent, _, _, cx| {
|
||||
if !thumb_bounds.contains(&ev.position) {
|
||||
return;
|
||||
}
|
||||
|
||||
entity.update(cx, |this, _| {
|
||||
this.drag_position = Some(
|
||||
ev.position - thumb_bounds.origin - table_bounds.origin,
|
||||
);
|
||||
})
|
||||
}
|
||||
});
|
||||
window.on_mouse_event({
|
||||
let entity = entity.clone();
|
||||
move |_: &MouseUpEvent, _, _, cx| {
|
||||
entity.update(cx, |this, _| {
|
||||
this.drag_position = None;
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
window.on_mouse_event(move |ev: &MouseMoveEvent, _, _, cx| {
|
||||
if !ev.dragging() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(drag_pos) = entity.read(cx).drag_position else {
|
||||
return;
|
||||
};
|
||||
|
||||
let inside_offset = drag_pos.y;
|
||||
let percentage = ((ev.position.y - table_bounds.origin.y
|
||||
+ inside_offset)
|
||||
/ (table_bounds.size.height))
|
||||
.clamp(0., 1.);
|
||||
|
||||
let offset_y = ((scroll_height - table_bounds.size.height)
|
||||
* percentage)
|
||||
.clamp(px(0.), scroll_height - SCROLLBAR_THUMB_HEIGHT);
|
||||
scroll_handle.set_offset(point(px(0.), -offset_y));
|
||||
cx.notify(entity.entity_id());
|
||||
})
|
||||
},
|
||||
)
|
||||
.size_full(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for DataTable {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let entity = cx.entity();
|
||||
|
||||
div()
|
||||
.font_family(".SystemUIFont")
|
||||
.bg(gpui::white())
|
||||
.text_sm()
|
||||
.size_full()
|
||||
.p_4()
|
||||
.gap_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(format!(
|
||||
"Total {} items, visible range: {:?}",
|
||||
self.quotes.len(),
|
||||
self.visible_range
|
||||
))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.flex_1()
|
||||
.overflow_hidden()
|
||||
.border_1()
|
||||
.border_color(rgb(0xE0E0E0))
|
||||
.rounded_md()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.w_full()
|
||||
.overflow_hidden()
|
||||
.border_b_1()
|
||||
.border_color(rgb(0xE0E0E0))
|
||||
.text_color(rgb(0x555555))
|
||||
.bg(rgb(0xF0F0F0))
|
||||
.py_1()
|
||||
.px_2()
|
||||
.text_xs()
|
||||
.children(FIELDS.map(|(key, width)| {
|
||||
div()
|
||||
.whitespace_nowrap()
|
||||
.flex_shrink_0()
|
||||
.truncate()
|
||||
.px_1()
|
||||
.w(px(width))
|
||||
.child(key.replace("_", " ").to_uppercase())
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.relative()
|
||||
.size_full()
|
||||
.child(
|
||||
uniform_list(entity, "items", self.quotes.len(), {
|
||||
move |this, range, _, _| {
|
||||
this.visible_range = range.clone();
|
||||
let mut items = Vec::with_capacity(range.end - range.start);
|
||||
for i in range {
|
||||
if let Some(quote) = this.quotes.get(i) {
|
||||
items.push(TableRow::new(i, quote.clone()));
|
||||
}
|
||||
}
|
||||
items
|
||||
}
|
||||
})
|
||||
.size_full()
|
||||
.track_scroll(self.scroll_handle.clone()),
|
||||
)
|
||||
.child(self.render_scrollbar(window, cx)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
focus: true,
|
||||
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
|
||||
None,
|
||||
size(px(1280.0), px(1000.0)),
|
||||
cx,
|
||||
))),
|
||||
..Default::default()
|
||||
},
|
||||
|_, cx| {
|
||||
cx.new(|_| {
|
||||
let mut table = DataTable::new();
|
||||
table.generate();
|
||||
table
|
||||
})
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
cx.activate(true);
|
||||
});
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
use gpui::{
|
||||
div, prelude::*, px, size, App, Application, Bounds, Context, Window, WindowBounds,
|
||||
WindowOptions,
|
||||
};
|
||||
|
||||
struct HelloWorld {}
|
||||
|
||||
impl Render for HelloWorld {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.bg(gpui::white())
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.p_4()
|
||||
.size_full()
|
||||
.child(div().child("Text left"))
|
||||
.child(div().text_center().child("Text center"))
|
||||
.child(div().text_right().child("Text right"))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(
|
||||
div()
|
||||
.w(px(400.))
|
||||
.border_1()
|
||||
.border_color(gpui::blue())
|
||||
.p_1()
|
||||
.whitespace_nowrap()
|
||||
.overflow_hidden()
|
||||
.text_center()
|
||||
.child("A long non-wrapping text align center"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_32()
|
||||
.border_1()
|
||||
.border_color(gpui::blue())
|
||||
.p_1()
|
||||
.whitespace_nowrap()
|
||||
.overflow_hidden()
|
||||
.text_right()
|
||||
.child("100%"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
..Default::default()
|
||||
},
|
||||
|_, cx| cx.new(|_| HelloWorld {}),
|
||||
)
|
||||
.unwrap();
|
||||
cx.activate(true);
|
||||
});
|
||||
}
|
||||
@@ -116,7 +116,6 @@ impl<T: ?Sized> ArenaBox<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn validate(&self) {
|
||||
assert!(
|
||||
self.valid.get(),
|
||||
|
||||
@@ -1684,7 +1684,7 @@ impl Interactivity {
|
||||
.ok()
|
||||
.and_then(|mut text| text.pop())
|
||||
{
|
||||
text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
|
||||
text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, window, cx)
|
||||
.ok();
|
||||
|
||||
let text_bounds = crate::Bounds {
|
||||
|
||||
@@ -392,15 +392,8 @@ impl TextLayout {
|
||||
let mut line_origin = bounds.origin;
|
||||
let text_style = window.text_style();
|
||||
for line in &element_state.lines {
|
||||
line.paint(
|
||||
line_origin,
|
||||
line_height,
|
||||
text_style.text_align,
|
||||
Some(bounds),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
line.paint(line_origin, line_height, text_style.text_align, window, cx)
|
||||
.log_err();
|
||||
line_origin.y += line.size(line_height).height;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1132,10 +1132,11 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
size,
|
||||
..
|
||||
} => {
|
||||
if format != wl_keyboard::KeymapFormat::XkbV1 {
|
||||
log::error!("Received keymap format {:?}, expected XkbV1", format);
|
||||
return;
|
||||
}
|
||||
assert_eq!(
|
||||
format,
|
||||
wl_keyboard::KeymapFormat::XkbV1,
|
||||
"Unsupported keymap format"
|
||||
);
|
||||
let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||
let keymap = unsafe {
|
||||
xkb::Keymap::new_from_fd(
|
||||
|
||||
@@ -107,21 +107,15 @@ impl WrappedLine {
|
||||
origin: Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
align: TextAlign,
|
||||
bounds: Option<Bounds<Pixels>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Result<()> {
|
||||
let align_width = match bounds {
|
||||
Some(bounds) => Some(bounds.size.width),
|
||||
None => self.layout.wrap_width,
|
||||
};
|
||||
|
||||
paint_line(
|
||||
origin,
|
||||
&self.layout.unwrapped_layout,
|
||||
line_height,
|
||||
align,
|
||||
align_width,
|
||||
self.layout.wrap_width,
|
||||
&self.decoration_runs,
|
||||
&self.wrap_boundaries,
|
||||
window,
|
||||
@@ -228,7 +222,7 @@ fn paint_line(
|
||||
glyph_origin.x = aligned_origin_x(
|
||||
origin,
|
||||
align_width.unwrap_or(layout.width),
|
||||
glyph.position.x,
|
||||
prev_glyph_position.x,
|
||||
&align,
|
||||
layout,
|
||||
wraps.peek(),
|
||||
@@ -432,7 +426,17 @@ fn aligned_origin_x(
|
||||
wrap_boundary: Option<&&WrapBoundary>,
|
||||
) -> Pixels {
|
||||
let end_of_line = if let Some(WrapBoundary { run_ix, glyph_ix }) = wrap_boundary {
|
||||
layout.runs[*run_ix].glyphs[*glyph_ix].position.x
|
||||
if layout.runs[*run_ix].glyphs.len() == glyph_ix + 1 {
|
||||
// Next glyph is in next run
|
||||
layout
|
||||
.runs
|
||||
.get(run_ix + 1)
|
||||
.and_then(|run| run.glyphs.first())
|
||||
.map_or(layout.width, |glyph| glyph.position.x)
|
||||
} else {
|
||||
// Get next glyph
|
||||
layout.runs[*run_ix].glyphs[*glyph_ix + 1].position.x
|
||||
}
|
||||
} else {
|
||||
layout.width
|
||||
};
|
||||
|
||||
@@ -12,9 +12,6 @@ workspace = true
|
||||
path = "src/image_viewer.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = ["gpui/test-support", "editor/test-support"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
db.workspace = true
|
||||
@@ -22,13 +19,11 @@ editor.workspace = true
|
||||
file_icons.workspace = true
|
||||
gpui.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
[features]
|
||||
test-support = ["gpui/test-support"]
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
use gpui::{div, Context, Entity, IntoElement, ParentElement, Render, Subscription};
|
||||
use project::image_store::{ImageFormat, ImageMetadata};
|
||||
use settings::Settings;
|
||||
use ui::prelude::*;
|
||||
use workspace::{ItemHandle, StatusItemView, Workspace};
|
||||
|
||||
use crate::{ImageFileSizeUnit, ImageView, ImageViewerSettings};
|
||||
|
||||
pub struct ImageInfo {
|
||||
metadata: Option<ImageMetadata>,
|
||||
_observe_active_image: Option<Subscription>,
|
||||
observe_image_item: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl ImageInfo {
|
||||
pub fn new(_workspace: &Workspace) -> Self {
|
||||
Self {
|
||||
metadata: None,
|
||||
_observe_active_image: None,
|
||||
observe_image_item: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_metadata(&mut self, image_view: &Entity<ImageView>, cx: &mut Context<Self>) {
|
||||
let image_item = image_view.read(cx).image_item.clone();
|
||||
let current_metadata = image_item.read(cx).image_metadata;
|
||||
if current_metadata.is_some() {
|
||||
self.metadata = current_metadata;
|
||||
cx.notify();
|
||||
} else {
|
||||
self.observe_image_item = Some(cx.observe(&image_item, |this, item, cx| {
|
||||
this.metadata = item.read(cx).image_metadata;
|
||||
cx.notify();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_file_size(size: u64, image_unit_type: ImageFileSizeUnit) -> String {
|
||||
match image_unit_type {
|
||||
ImageFileSizeUnit::Binary => {
|
||||
if size < 1024 {
|
||||
format!("{size}B")
|
||||
} else if size < 1024 * 1024 {
|
||||
format!("{:.1}KiB", size as f64 / 1024.0)
|
||||
} else {
|
||||
format!("{:.1}MiB", size as f64 / (1024.0 * 1024.0))
|
||||
}
|
||||
}
|
||||
ImageFileSizeUnit::Decimal => {
|
||||
if size < 1000 {
|
||||
format!("{size}B")
|
||||
} else if size < 1000 * 1000 {
|
||||
format!("{:.1}KB", size as f64 / 1000.0)
|
||||
} else {
|
||||
format!("{:.1}MB", size as f64 / (1000.0 * 1000.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ImageInfo {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let settings = ImageViewerSettings::get_global(cx);
|
||||
|
||||
let Some(metadata) = self.metadata.as_ref() else {
|
||||
return div();
|
||||
};
|
||||
|
||||
let mut components = Vec::new();
|
||||
components.push(format!("{}x{}", metadata.width, metadata.height));
|
||||
components.push(format_file_size(metadata.file_size, settings.unit));
|
||||
|
||||
if let Some(colors) = metadata.colors {
|
||||
components.push(format!(
|
||||
"{} channels, {} bits per pixel",
|
||||
colors.channels,
|
||||
colors.bits_per_pixel()
|
||||
));
|
||||
}
|
||||
|
||||
components.push(
|
||||
match metadata.format {
|
||||
ImageFormat::Png => "PNG",
|
||||
ImageFormat::Jpeg => "JPEG",
|
||||
ImageFormat::Gif => "GIF",
|
||||
ImageFormat::WebP => "WebP",
|
||||
ImageFormat::Tiff => "TIFF",
|
||||
ImageFormat::Bmp => "BMP",
|
||||
ImageFormat::Ico => "ICO",
|
||||
ImageFormat::Avif => "Avif",
|
||||
_ => "Unknown",
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
div().child(
|
||||
Button::new("image-metadata", components.join(" • ")).label_size(LabelSize::Small),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl StatusItemView for ImageInfo {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
active_pane_item: Option<&dyn ItemHandle>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self._observe_active_image = None;
|
||||
self.observe_image_item = None;
|
||||
|
||||
if let Some(image_view) = active_pane_item.and_then(|item| item.act_as::<ImageView>(cx)) {
|
||||
self.update_metadata(&image_view, cx);
|
||||
|
||||
self._observe_active_image = Some(cx.observe(&image_view, |this, view, cx| {
|
||||
this.update_metadata(&view, cx);
|
||||
}));
|
||||
} else {
|
||||
self.metadata = None;
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
mod image_info;
|
||||
mod image_viewer_settings;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Context as _;
|
||||
@@ -22,8 +19,7 @@ use workspace::{
|
||||
ItemId, ItemSettings, ToolbarItemLocation, Workspace, WorkspaceId,
|
||||
};
|
||||
|
||||
pub use crate::image_info::*;
|
||||
pub use crate::image_viewer_settings::*;
|
||||
const IMAGE_VIEWER_KIND: &str = "ImageView";
|
||||
|
||||
pub struct ImageView {
|
||||
image_item: Entity<ImageItem>,
|
||||
@@ -35,6 +31,7 @@ impl ImageView {
|
||||
pub fn new(
|
||||
image_item: Entity<ImageItem>,
|
||||
project: Entity<Project>,
|
||||
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
cx.subscribe(&image_item, Self::on_image_event).detach();
|
||||
@@ -52,9 +49,7 @@ impl ImageView {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
ImageItemEvent::MetadataUpdated
|
||||
| ImageItemEvent::FileHandleChanged
|
||||
| ImageItemEvent::Reloaded => {
|
||||
ImageItemEvent::FileHandleChanged | ImageItemEvent::Reloaded => {
|
||||
cx.emit(ImageViewEvent::TitleChanged);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -193,7 +188,7 @@ fn breadcrumbs_text_for_image(project: &Project, image: &ImageItem, cx: &App) ->
|
||||
|
||||
impl SerializableItem for ImageView {
|
||||
fn serialized_item_kind() -> &'static str {
|
||||
"ImageView"
|
||||
IMAGE_VIEWER_KIND
|
||||
}
|
||||
|
||||
fn deserialize(
|
||||
@@ -362,9 +357,8 @@ impl ProjectItem for ImageView {
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
ImageViewerSettings::register(cx);
|
||||
workspace::register_project_item::<ImageView>(cx);
|
||||
workspace::register_serializable_item::<ImageView>(cx);
|
||||
workspace::register_serializable_item::<ImageView>(cx)
|
||||
}
|
||||
|
||||
mod persistence {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
use gpui::App;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
|
||||
/// The settings for the image viewer.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default)]
|
||||
pub struct ImageViewerSettings {
|
||||
/// The unit to use for displaying image file sizes.
|
||||
///
|
||||
/// Default: "binary"
|
||||
#[serde(default)]
|
||||
pub unit: ImageFileSizeUnit,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ImageFileSizeUnit {
|
||||
/// Displays file size in binary units (e.g., KiB, MiB).
|
||||
#[default]
|
||||
Binary,
|
||||
/// Displays file size in decimal units (e.g., KB, MB).
|
||||
Decimal,
|
||||
}
|
||||
|
||||
impl Settings for ImageViewerSettings {
|
||||
const KEY: Option<&'static str> = Some("image_viewer");
|
||||
|
||||
type FileContent = Self;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
_: &mut App,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
SettingsSources::<Self::FileContent>::json_merge_with(
|
||||
[sources.default]
|
||||
.into_iter()
|
||||
.chain(sources.user)
|
||||
.chain(sources.server),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use gpui::{App, Context, Entity, SharedString};
|
||||
use gpui::{App, Context, Entity};
|
||||
use language::Buffer;
|
||||
use project::Project;
|
||||
use std::ops::Range;
|
||||
@@ -15,8 +15,6 @@ pub enum Direction {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InlineCompletion {
|
||||
/// The ID of the completion, if it has one.
|
||||
pub id: Option<SharedString>,
|
||||
pub edits: Vec<(Range<language::Anchor>, String)>,
|
||||
pub edit_preview: Option<language::EditPreview>,
|
||||
}
|
||||
@@ -24,7 +22,7 @@ pub struct InlineCompletion {
|
||||
pub enum DataCollectionState {
|
||||
/// The provider doesn't support data collection.
|
||||
Unsupported,
|
||||
/// Data collection is enabled.
|
||||
/// Data collection is enabled
|
||||
Enabled,
|
||||
/// Data collection is disabled or unanswered.
|
||||
Disabled,
|
||||
@@ -40,10 +38,11 @@ impl DataCollectionState {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EditPredictionProvider: 'static + Sized {
|
||||
pub trait InlineCompletionProvider: 'static + Sized {
|
||||
fn name() -> &'static str;
|
||||
fn display_name() -> &'static str;
|
||||
fn show_completions_in_menu() -> bool;
|
||||
fn show_completions_in_normal_mode() -> bool;
|
||||
fn show_tab_accept_marker() -> bool {
|
||||
false
|
||||
}
|
||||
@@ -96,6 +95,7 @@ pub trait InlineCompletionProviderHandle {
|
||||
cx: &App,
|
||||
) -> bool;
|
||||
fn show_completions_in_menu(&self) -> bool;
|
||||
fn show_completions_in_normal_mode(&self) -> bool;
|
||||
fn show_tab_accept_marker(&self) -> bool;
|
||||
fn data_collection_state(&self, cx: &App) -> DataCollectionState;
|
||||
fn toggle_data_collection(&self, cx: &mut App);
|
||||
@@ -128,7 +128,7 @@ pub trait InlineCompletionProviderHandle {
|
||||
|
||||
impl<T> InlineCompletionProviderHandle for Entity<T>
|
||||
where
|
||||
T: EditPredictionProvider,
|
||||
T: InlineCompletionProvider,
|
||||
{
|
||||
fn name(&self) -> &'static str {
|
||||
T::name()
|
||||
@@ -142,6 +142,10 @@ where
|
||||
T::show_completions_in_menu()
|
||||
}
|
||||
|
||||
fn show_completions_in_normal_mode(&self) -> bool {
|
||||
T::show_completions_in_normal_mode()
|
||||
}
|
||||
|
||||
fn show_tab_accept_marker(&self) -> bool {
|
||||
T::show_tab_accept_marker()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
gpui.workspace = true
|
||||
indoc.workspace = true
|
||||
inline_completion.workspace = true
|
||||
language.workspace = true
|
||||
paths.workspace = true
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
use anyhow::Result;
|
||||
use client::UserStore;
|
||||
use copilot::{Copilot, Status};
|
||||
use editor::{
|
||||
actions::{ShowEditPrediction, ToggleEditPrediction},
|
||||
scroll::Autoscroll,
|
||||
Editor,
|
||||
};
|
||||
use editor::{actions::ShowInlineCompletion, scroll::Autoscroll, Editor};
|
||||
use feature_flags::{
|
||||
FeatureFlagAppExt, PredictEditsFeatureFlag, PredictEditsRateCompletionsFeatureFlag,
|
||||
};
|
||||
@@ -15,9 +11,10 @@ use gpui::{
|
||||
Corner, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render, Subscription,
|
||||
WeakEntity,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
language_settings::{self, all_language_settings, AllLanguageSettings, EditPredictionProvider},
|
||||
language_settings::{
|
||||
self, all_language_settings, AllLanguageSettings, InlineCompletionProvider,
|
||||
},
|
||||
File, Language,
|
||||
};
|
||||
use regex::Regex;
|
||||
@@ -28,8 +25,8 @@ use std::{
|
||||
};
|
||||
use supermaven::{AccountStatus, Supermaven};
|
||||
use ui::{
|
||||
prelude::*, Clickable, ContextMenu, ContextMenuEntry, IconButton, IconButtonShape, Indicator,
|
||||
PopoverMenu, PopoverMenuHandle, Tooltip,
|
||||
prelude::*, Clickable, ContextMenu, ContextMenuEntry, IconButton, IconButtonShape, PopoverMenu,
|
||||
PopoverMenuHandle, Tooltip,
|
||||
};
|
||||
use workspace::{
|
||||
create_and_open_local_file, item::ItemHandle, notifications::NotificationId, StatusItemView,
|
||||
@@ -39,7 +36,7 @@ use zed_actions::OpenBrowser;
|
||||
use zeta::RateCompletionModal;
|
||||
|
||||
actions!(zeta, [RateCompletions]);
|
||||
actions!(edit_prediction, [ToggleMenu]);
|
||||
actions!(inline_completion, [ToggleMenu]);
|
||||
|
||||
const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
|
||||
|
||||
@@ -48,11 +45,10 @@ struct CopilotErrorToast;
|
||||
pub struct InlineCompletionButton {
|
||||
editor_subscription: Option<(Subscription, usize)>,
|
||||
editor_enabled: Option<bool>,
|
||||
editor_show_predictions: bool,
|
||||
editor_focus_handle: Option<FocusHandle>,
|
||||
language: Option<Arc<Language>>,
|
||||
file: Option<Arc<dyn File>>,
|
||||
edit_prediction_provider: Option<Arc<dyn inline_completion::InlineCompletionProviderHandle>>,
|
||||
inline_completion_provider: Option<Arc<dyn inline_completion::InlineCompletionProviderHandle>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
user_store: Entity<UserStore>,
|
||||
@@ -70,10 +66,10 @@ impl Render for InlineCompletionButton {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let all_language_settings = all_language_settings(None, cx);
|
||||
|
||||
match all_language_settings.edit_predictions.provider {
|
||||
EditPredictionProvider::None => div(),
|
||||
match all_language_settings.inline_completions.provider {
|
||||
InlineCompletionProvider::None => div(),
|
||||
|
||||
EditPredictionProvider::Copilot => {
|
||||
InlineCompletionProvider::Copilot => {
|
||||
let Some(copilot) = Copilot::global(cx) else {
|
||||
return div();
|
||||
};
|
||||
@@ -142,17 +138,14 @@ impl Render for InlineCompletionButton {
|
||||
})
|
||||
})
|
||||
.anchor(Corner::BottomRight)
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("copilot-icon", icon),
|
||||
|window, cx| {
|
||||
Tooltip::for_action("GitHub Copilot", &ToggleMenu, window, cx)
|
||||
},
|
||||
)
|
||||
.trigger(IconButton::new("copilot-icon", icon).tooltip(|window, cx| {
|
||||
Tooltip::for_action("GitHub Copilot", &ToggleMenu, window, cx)
|
||||
}))
|
||||
.with_handle(self.popover_menu_handle.clone()),
|
||||
)
|
||||
}
|
||||
|
||||
EditPredictionProvider::Supermaven => {
|
||||
InlineCompletionProvider::Supermaven => {
|
||||
let Some(supermaven) = Supermaven::global(cx) else {
|
||||
return div();
|
||||
};
|
||||
@@ -202,7 +195,7 @@ impl Render for InlineCompletionButton {
|
||||
set_completion_provider(
|
||||
fs.clone(),
|
||||
cx,
|
||||
EditPredictionProvider::Copilot,
|
||||
InlineCompletionProvider::Copilot,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -214,8 +207,7 @@ impl Render for InlineCompletionButton {
|
||||
_ => None,
|
||||
})
|
||||
.anchor(Corner::BottomRight)
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("supermaven-icon", icon),
|
||||
.trigger(IconButton::new("supermaven-icon", icon).tooltip(
|
||||
move |window, cx| {
|
||||
if has_menu {
|
||||
Tooltip::for_action(
|
||||
@@ -228,17 +220,17 @@ impl Render for InlineCompletionButton {
|
||||
Tooltip::text(tooltip_text.clone())(window, cx)
|
||||
}
|
||||
},
|
||||
)
|
||||
))
|
||||
.with_handle(self.popover_menu_handle.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
EditPredictionProvider::Zed => {
|
||||
InlineCompletionProvider::Zed => {
|
||||
if !cx.has_flag::<PredictEditsFeatureFlag>() {
|
||||
return div();
|
||||
}
|
||||
|
||||
let enabled = self.editor_enabled.unwrap_or(true);
|
||||
let enabled = self.editor_enabled.unwrap_or(false);
|
||||
|
||||
let zeta_icon = if enabled {
|
||||
IconName::ZedPredict
|
||||
@@ -249,20 +241,24 @@ impl Render for InlineCompletionButton {
|
||||
let current_user_terms_accepted =
|
||||
self.user_store.read(cx).current_user_has_accepted_terms();
|
||||
|
||||
if !current_user_terms_accepted.unwrap_or(false) {
|
||||
let signed_in = current_user_terms_accepted.is_some();
|
||||
let tooltip_meta = if signed_in {
|
||||
"Read Terms of Service"
|
||||
} else {
|
||||
"Sign in to use"
|
||||
};
|
||||
let icon_button = || {
|
||||
let base = IconButton::new("zed-predict-pending-button", zeta_icon)
|
||||
.shape(IconButtonShape::Square);
|
||||
|
||||
return div().child(
|
||||
IconButton::new("zed-predict-pending-button", zeta_icon)
|
||||
.shape(IconButtonShape::Square)
|
||||
.indicator(Indicator::dot().color(Color::Error))
|
||||
.indicator_border_color(Some(cx.theme().colors().status_bar_background))
|
||||
.tooltip(move |window, cx| {
|
||||
match (
|
||||
current_user_terms_accepted,
|
||||
self.popover_menu_handle.is_deployed(),
|
||||
enabled,
|
||||
) {
|
||||
(Some(false) | None, _, _) => {
|
||||
let signed_in = current_user_terms_accepted.is_some();
|
||||
let tooltip_meta = if signed_in {
|
||||
"Read Terms of Service"
|
||||
} else {
|
||||
"Sign in to use"
|
||||
};
|
||||
|
||||
base.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Edit Predictions",
|
||||
None,
|
||||
@@ -271,52 +267,34 @@ impl Render for InlineCompletionButton {
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(move |_, _, window, cx| {
|
||||
telemetry::event!(
|
||||
"Pending ToS Clicked",
|
||||
source = "Edit Prediction Status Button"
|
||||
);
|
||||
window.dispatch_action(
|
||||
zed_actions::OpenZedPredictOnboarding.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
let show_editor_predictions = self.editor_show_predictions;
|
||||
|
||||
let icon_button = IconButton::new("zed-predict-pending-button", zeta_icon)
|
||||
.shape(IconButtonShape::Square)
|
||||
.when(enabled && !show_editor_predictions, |this| {
|
||||
this.indicator(Indicator::dot().color(Color::Muted))
|
||||
.indicator_border_color(Some(cx.theme().colors().status_bar_background))
|
||||
})
|
||||
.when(!self.popover_menu_handle.is_deployed(), |element| {
|
||||
element.tooltip(move |window, cx| {
|
||||
if enabled {
|
||||
if show_editor_predictions {
|
||||
Tooltip::for_action("Edit Prediction", &ToggleMenu, window, cx)
|
||||
} else {
|
||||
Tooltip::with_meta(
|
||||
"Edit Prediction",
|
||||
Some(&ToggleMenu),
|
||||
"Hidden For This File",
|
||||
window,
|
||||
.on_click(cx.listener(
|
||||
move |_, _, window, cx| {
|
||||
telemetry::event!(
|
||||
"Pending ToS Clicked",
|
||||
source = "Edit Prediction Status Button"
|
||||
);
|
||||
window.dispatch_action(
|
||||
zed_actions::OpenZedPredictOnboarding.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Tooltip::with_meta(
|
||||
"Edit Prediction",
|
||||
Some(&ToggleMenu),
|
||||
"Disabled For This File",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
});
|
||||
);
|
||||
},
|
||||
))
|
||||
}
|
||||
(Some(true), true, _) => base,
|
||||
(Some(true), false, true) => base.tooltip(|window, cx| {
|
||||
Tooltip::for_action("Edit Prediction", &ToggleMenu, window, cx)
|
||||
}),
|
||||
(Some(true), false, false) => base.tooltip(|window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Edit Prediction",
|
||||
Some(&ToggleMenu),
|
||||
"Disabled For This File",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
let this = cx.entity().clone();
|
||||
|
||||
@@ -328,13 +306,13 @@ impl Render for InlineCompletionButton {
|
||||
.with_handle(self.popover_menu_handle.clone());
|
||||
|
||||
let is_refreshing = self
|
||||
.edit_prediction_provider
|
||||
.inline_completion_provider
|
||||
.as_ref()
|
||||
.map_or(false, |provider| provider.is_refreshing(cx));
|
||||
|
||||
if is_refreshing {
|
||||
popover_menu = popover_menu.trigger(
|
||||
icon_button.with_animation(
|
||||
icon_button().with_animation(
|
||||
"pulsating-label",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
@@ -343,7 +321,7 @@ impl Render for InlineCompletionButton {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
popover_menu = popover_menu.trigger(icon_button);
|
||||
popover_menu = popover_menu.trigger(icon_button());
|
||||
}
|
||||
|
||||
div().child(popover_menu.into_any_element())
|
||||
@@ -370,11 +348,10 @@ impl InlineCompletionButton {
|
||||
Self {
|
||||
editor_subscription: None,
|
||||
editor_enabled: None,
|
||||
editor_show_predictions: true,
|
||||
editor_focus_handle: None,
|
||||
language: None,
|
||||
file: None,
|
||||
edit_prediction_provider: None,
|
||||
inline_completion_provider: None,
|
||||
popover_menu_handle,
|
||||
workspace,
|
||||
fs,
|
||||
@@ -397,7 +374,11 @@ impl InlineCompletionButton {
|
||||
.entry("Use Supermaven", None, {
|
||||
let fs = fs.clone();
|
||||
move |_window, cx| {
|
||||
set_completion_provider(fs.clone(), cx, EditPredictionProvider::Supermaven)
|
||||
set_completion_provider(
|
||||
fs.clone(),
|
||||
cx,
|
||||
InlineCompletionProvider::Supermaven,
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -408,31 +389,16 @@ impl InlineCompletionButton {
|
||||
|
||||
menu = menu.header("Show Edit Predictions For");
|
||||
|
||||
if let Some(editor_focus_handle) = self.editor_focus_handle.clone() {
|
||||
menu = menu.toggleable_entry(
|
||||
"This File",
|
||||
self.editor_show_predictions,
|
||||
IconPosition::Start,
|
||||
Some(Box::new(ToggleEditPrediction)),
|
||||
{
|
||||
let editor_focus_handle = editor_focus_handle.clone();
|
||||
move |window, cx| {
|
||||
editor_focus_handle.dispatch_action(&ToggleEditPrediction, window, cx);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(language) = self.language.clone() {
|
||||
let fs = fs.clone();
|
||||
let language_enabled =
|
||||
language_settings::language_settings(Some(language.name()), None, cx)
|
||||
.show_edit_predictions;
|
||||
.show_inline_completions;
|
||||
|
||||
menu = menu.toggleable_entry(
|
||||
language.name(),
|
||||
language_enabled,
|
||||
IconPosition::Start,
|
||||
IconPosition::End,
|
||||
None,
|
||||
move |_, cx| {
|
||||
toggle_show_inline_completions_for_language(language.clone(), fs.clone(), cx)
|
||||
@@ -445,13 +411,13 @@ impl InlineCompletionButton {
|
||||
menu = menu.toggleable_entry(
|
||||
"All Files",
|
||||
globally_enabled,
|
||||
IconPosition::Start,
|
||||
IconPosition::End,
|
||||
None,
|
||||
move |_, cx| toggle_inline_completions_globally(fs.clone(), cx),
|
||||
);
|
||||
menu = menu.separator().header("Privacy Settings");
|
||||
|
||||
if let Some(provider) = &self.edit_prediction_provider {
|
||||
if let Some(provider) = &self.inline_completion_provider {
|
||||
let data_collection = provider.data_collection_state(cx);
|
||||
if data_collection.is_supported() {
|
||||
let provider = provider.clone();
|
||||
@@ -461,12 +427,9 @@ impl InlineCompletionButton {
|
||||
// TODO: We want to add something later that communicates whether
|
||||
// the current project is open-source.
|
||||
ContextMenuEntry::new("Share Training Data")
|
||||
.toggleable(IconPosition::Start, data_collection.is_enabled())
|
||||
.toggleable(IconPosition::End, data_collection.is_enabled())
|
||||
.documentation_aside(|_| {
|
||||
Label::new(indoc!{"
|
||||
Help us improve our open model by sharing data from open source repositories. \
|
||||
Zed must detect a license file in your repo for this setting to take effect.\
|
||||
"}).into_any_element()
|
||||
Label::new("Zed automatically detects if your project is open-source. This setting is only applicable in such cases.").into_any_element()
|
||||
})
|
||||
.handler(move |_, cx| {
|
||||
provider.toggle_data_collection(cx);
|
||||
@@ -489,11 +452,8 @@ impl InlineCompletionButton {
|
||||
|
||||
menu = menu.item(
|
||||
ContextMenuEntry::new("Configure Excluded Files")
|
||||
.icon(IconName::LockOutlined)
|
||||
.icon_color(Color::Muted)
|
||||
.documentation_aside(|_| {
|
||||
Label::new(indoc!{"
|
||||
Open your settings to add sensitive paths for which Zed will never predict edits."}).into_any_element()
|
||||
Label::new("This item takes you to the settings where you can specify files that will never be captured by any edit prediction model. List both specific file extensions and individual file names.").into_any_element()
|
||||
})
|
||||
.handler(move |window, cx| {
|
||||
if let Some(workspace) = window.root().flatten() {
|
||||
@@ -510,7 +470,9 @@ impl InlineCompletionButton {
|
||||
}),
|
||||
);
|
||||
|
||||
if !self.editor_enabled.unwrap_or(true) {
|
||||
if self.file.as_ref().map_or(false, |file| {
|
||||
!all_language_settings(Some(file), cx).inline_completions_enabled_for_path(file.path())
|
||||
}) {
|
||||
menu = menu.item(
|
||||
ContextMenuEntry::new("This file is excluded.")
|
||||
.disabled(true)
|
||||
@@ -524,11 +486,12 @@ impl InlineCompletionButton {
|
||||
.separator()
|
||||
.entry(
|
||||
"Predict Edit at Cursor",
|
||||
Some(Box::new(ShowEditPrediction)),
|
||||
Some(Box::new(ShowInlineCompletion)),
|
||||
{
|
||||
let editor_focus_handle = editor_focus_handle.clone();
|
||||
|
||||
move |window, cx| {
|
||||
editor_focus_handle.dispatch_action(&ShowEditPrediction, window, cx);
|
||||
editor_focus_handle.dispatch_action(&ShowInlineCompletion, window, cx);
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -611,8 +574,7 @@ impl InlineCompletionButton {
|
||||
.unwrap_or(true),
|
||||
)
|
||||
};
|
||||
self.editor_show_predictions = editor.should_show_inline_completions(cx);
|
||||
self.edit_prediction_provider = editor.edit_prediction_provider();
|
||||
self.inline_completion_provider = editor.inline_completion_provider();
|
||||
self.language = language.cloned();
|
||||
self.file = file;
|
||||
self.editor_focus_handle = Some(editor.focus_handle(cx));
|
||||
@@ -697,7 +659,7 @@ async fn open_disabled_globs_setting_in_editor(
|
||||
|
||||
// Ensure that we always have "inline_completions { "disabled_globs": [] }"
|
||||
let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
|
||||
file.edit_predictions
|
||||
file.inline_completions
|
||||
.get_or_insert_with(Default::default)
|
||||
.disabled_globs
|
||||
.get_or_insert_with(Vec::new);
|
||||
@@ -729,17 +691,17 @@ async fn open_disabled_globs_setting_in_editor(
|
||||
}
|
||||
|
||||
fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
let show_edit_predictions = all_language_settings(None, cx).show_inline_completions(None, cx);
|
||||
let show_inline_completions = all_language_settings(None, cx).show_inline_completions(None, cx);
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
|
||||
file.defaults.show_edit_predictions = Some(!show_edit_predictions)
|
||||
file.defaults.show_inline_completions = Some(!show_inline_completions)
|
||||
});
|
||||
}
|
||||
|
||||
fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: EditPredictionProvider) {
|
||||
fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: InlineCompletionProvider) {
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
|
||||
file.features
|
||||
.get_or_insert(Default::default())
|
||||
.edit_prediction_provider = Some(provider);
|
||||
.inline_completion_provider = Some(provider);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -748,13 +710,13 @@ fn toggle_show_inline_completions_for_language(
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let show_edit_predictions =
|
||||
let show_inline_completions =
|
||||
all_language_settings(None, cx).show_inline_completions(Some(&language), cx);
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
|
||||
file.languages
|
||||
.entry(language.name())
|
||||
.or_default()
|
||||
.show_edit_predictions = Some(!show_edit_predictions);
|
||||
.show_inline_completions = Some(!show_inline_completions);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -762,6 +724,6 @@ fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
|
||||
file.features
|
||||
.get_or_insert(Default::default())
|
||||
.edit_prediction_provider = Some(EditPredictionProvider::None);
|
||||
.inline_completion_provider = Some(InlineCompletionProvider::None);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ use sum_tree::Bias;
|
||||
use text::{Point, Rope};
|
||||
use theme::Theme;
|
||||
use unicase::UniCase;
|
||||
use util::{maybe, post_inc, ResultExt};
|
||||
use util::{maybe, paths::PathExt, post_inc, ResultExt};
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
|
||||
@@ -659,10 +659,7 @@ impl LanguageRegistry {
|
||||
user_file_types: Option<&HashMap<Arc<str>, GlobSet>>,
|
||||
) -> Option<AvailableLanguage> {
|
||||
let filename = path.file_name().and_then(|name| name.to_str());
|
||||
// `Path.extension()` returns None for files with a leading '.'
|
||||
// and no other extension which is not the desired behavior here,
|
||||
// as we want `.zshrc` to result in extension being `Some("zshrc")`
|
||||
let extension = filename.and_then(|filename| filename.split('.').last());
|
||||
let extension = path.extension_or_hidden_file_name();
|
||||
let path_suffixes = [extension, filename, path.to_str()];
|
||||
let empty = GlobSet::empty();
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ pub fn all_language_settings<'a>(
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AllLanguageSettings {
|
||||
/// The edit prediction settings.
|
||||
pub edit_predictions: EditPredictionSettings,
|
||||
pub inline_completions: InlineCompletionSettings,
|
||||
defaults: LanguageSettings,
|
||||
languages: HashMap<LanguageName, LanguageSettings>,
|
||||
pub(crate) file_types: HashMap<Arc<str>, GlobSet>,
|
||||
@@ -110,11 +110,11 @@ pub struct LanguageSettings {
|
||||
/// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
|
||||
pub language_servers: Vec<String>,
|
||||
/// Controls whether edit predictions are shown immediately (true)
|
||||
/// or manually by triggering `editor::ShowEditPrediction` (false).
|
||||
pub show_edit_predictions: bool,
|
||||
/// or manually by triggering `editor::ShowInlineCompletion` (false).
|
||||
pub show_inline_completions: bool,
|
||||
/// Controls whether edit predictions are shown in the given language
|
||||
/// scopes.
|
||||
pub edit_predictions_disabled_in: Vec<String>,
|
||||
pub inline_completions_disabled_in: Vec<String>,
|
||||
/// Whether to show tabs and spaces in the editor.
|
||||
pub show_whitespaces: ShowWhitespaceSetting,
|
||||
/// Whether to start a new line with a comment when a previous line is a comment as well.
|
||||
@@ -198,7 +198,7 @@ impl LanguageSettings {
|
||||
/// The provider that supplies edit predictions.
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EditPredictionProvider {
|
||||
pub enum InlineCompletionProvider {
|
||||
None,
|
||||
#[default]
|
||||
Copilot,
|
||||
@@ -206,26 +206,13 @@ pub enum EditPredictionProvider {
|
||||
Zed,
|
||||
}
|
||||
|
||||
impl EditPredictionProvider {
|
||||
pub fn is_zed(&self) -> bool {
|
||||
match self {
|
||||
EditPredictionProvider::Zed => true,
|
||||
EditPredictionProvider::None
|
||||
| EditPredictionProvider::Copilot
|
||||
| EditPredictionProvider::Supermaven => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot)
|
||||
/// or [Supermaven](https://supermaven.com).
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct EditPredictionSettings {
|
||||
pub struct InlineCompletionSettings {
|
||||
/// The provider that supplies edit predictions.
|
||||
pub provider: EditPredictionProvider,
|
||||
pub provider: InlineCompletionProvider,
|
||||
/// A list of globs representing files that edit predictions should be disabled for.
|
||||
/// This list adds to a pre-existing, sensible default set of globs.
|
||||
/// Any additional ones you add are combined with them.
|
||||
pub disabled_globs: Vec<GlobMatcher>,
|
||||
/// When to show edit predictions previews in buffer.
|
||||
pub inline_preview: InlineCompletionPreviewMode,
|
||||
@@ -250,7 +237,7 @@ pub struct AllLanguageSettingsContent {
|
||||
pub features: Option<FeaturesContent>,
|
||||
/// The edit prediction settings.
|
||||
#[serde(default)]
|
||||
pub edit_predictions: Option<InlineCompletionSettingsContent>,
|
||||
pub inline_completions: Option<InlineCompletionSettingsContent>,
|
||||
/// The default language settings.
|
||||
#[serde(flatten)]
|
||||
pub defaults: LanguageSettingsContent,
|
||||
@@ -349,11 +336,11 @@ pub struct LanguageSettingsContent {
|
||||
#[serde(default)]
|
||||
pub language_servers: Option<Vec<String>>,
|
||||
/// Controls whether edit predictions are shown immediately (true)
|
||||
/// or manually by triggering `editor::ShowEditPrediction` (false).
|
||||
/// or manually by triggering `editor::ShowInlineCompletion` (false).
|
||||
///
|
||||
/// Default: true
|
||||
#[serde(default)]
|
||||
pub show_edit_predictions: Option<bool>,
|
||||
pub show_inline_completions: Option<bool>,
|
||||
/// Controls whether edit predictions are shown in the given language
|
||||
/// scopes.
|
||||
///
|
||||
@@ -361,7 +348,7 @@ pub struct LanguageSettingsContent {
|
||||
///
|
||||
/// Default: []
|
||||
#[serde(default)]
|
||||
pub edit_predictions_disabled_in: Option<Vec<String>>,
|
||||
pub inline_completions_disabled_in: Option<Vec<String>>,
|
||||
/// Whether to show tabs and spaces in the editor.
|
||||
#[serde(default)]
|
||||
pub show_whitespaces: Option<ShowWhitespaceSetting>,
|
||||
@@ -430,8 +417,6 @@ pub struct LanguageSettingsContent {
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct InlineCompletionSettingsContent {
|
||||
/// A list of globs representing files that edit predictions should be disabled for.
|
||||
/// This list adds to a pre-existing, sensible default set of globs.
|
||||
/// Any additional ones you add are combined with them.
|
||||
#[serde(default)]
|
||||
pub disabled_globs: Option<Vec<String>>,
|
||||
/// When to show edit predictions previews in buffer.
|
||||
@@ -446,7 +431,7 @@ pub struct FeaturesContent {
|
||||
/// Whether the GitHub Copilot feature is enabled.
|
||||
pub copilot: Option<bool>,
|
||||
/// Determines which edit prediction provider to use.
|
||||
pub edit_prediction_provider: Option<EditPredictionProvider>,
|
||||
pub inline_completion_provider: Option<InlineCompletionProvider>,
|
||||
}
|
||||
|
||||
/// Controls the soft-wrapping behavior in the editor.
|
||||
@@ -910,7 +895,7 @@ impl AllLanguageSettings {
|
||||
/// Returns whether edit predictions are enabled for the given path.
|
||||
pub fn inline_completions_enabled_for_path(&self, path: &Path) -> bool {
|
||||
!self
|
||||
.edit_predictions
|
||||
.inline_completions
|
||||
.disabled_globs
|
||||
.iter()
|
||||
.any(|glob| glob.is_match(path))
|
||||
@@ -919,12 +904,12 @@ impl AllLanguageSettings {
|
||||
/// Returns whether edit predictions are enabled for the given language and path.
|
||||
pub fn show_inline_completions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
|
||||
self.language(None, language.map(|l| l.name()).as_ref(), cx)
|
||||
.show_edit_predictions
|
||||
.show_inline_completions
|
||||
}
|
||||
|
||||
/// Returns the edit predictions preview mode for the given language and path.
|
||||
pub fn inline_completions_preview_mode(&self) -> InlineCompletionPreviewMode {
|
||||
self.edit_predictions.inline_preview
|
||||
self.inline_completions.inline_preview
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1019,18 +1004,18 @@ impl settings::Settings for AllLanguageSettings {
|
||||
}
|
||||
|
||||
let mut copilot_enabled = default_value.features.as_ref().and_then(|f| f.copilot);
|
||||
let mut edit_prediction_provider = default_value
|
||||
let mut inline_completion_provider = default_value
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|f| f.edit_prediction_provider);
|
||||
.and_then(|f| f.inline_completion_provider);
|
||||
let mut inline_completions_preview = default_value
|
||||
.edit_predictions
|
||||
.inline_completions
|
||||
.as_ref()
|
||||
.map(|inline_completions| inline_completions.inline_preview)
|
||||
.ok_or_else(Self::missing_default)?;
|
||||
|
||||
let mut completion_globs: HashSet<&String> = default_value
|
||||
.edit_predictions
|
||||
.inline_completions
|
||||
.as_ref()
|
||||
.and_then(|c| c.disabled_globs.as_ref())
|
||||
.map(|globs| globs.iter().collect())
|
||||
@@ -1055,12 +1040,12 @@ impl settings::Settings for AllLanguageSettings {
|
||||
if let Some(provider) = user_settings
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|f| f.edit_prediction_provider)
|
||||
.and_then(|f| f.inline_completion_provider)
|
||||
{
|
||||
edit_prediction_provider = Some(provider);
|
||||
inline_completion_provider = Some(provider);
|
||||
}
|
||||
|
||||
if let Some(inline_completions) = user_settings.edit_predictions.as_ref() {
|
||||
if let Some(inline_completions) = user_settings.inline_completions.as_ref() {
|
||||
inline_completions_preview = inline_completions.inline_preview;
|
||||
|
||||
if let Some(disabled_globs) = inline_completions.disabled_globs.as_ref() {
|
||||
@@ -1106,13 +1091,13 @@ impl settings::Settings for AllLanguageSettings {
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
edit_predictions: EditPredictionSettings {
|
||||
provider: if let Some(provider) = edit_prediction_provider {
|
||||
inline_completions: InlineCompletionSettings {
|
||||
provider: if let Some(provider) = inline_completion_provider {
|
||||
provider
|
||||
} else if copilot_enabled.unwrap_or(true) {
|
||||
EditPredictionProvider::Copilot
|
||||
InlineCompletionProvider::Copilot
|
||||
} else {
|
||||
EditPredictionProvider::None
|
||||
InlineCompletionProvider::None
|
||||
},
|
||||
disabled_globs: completion_globs
|
||||
.iter()
|
||||
@@ -1223,12 +1208,12 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
|
||||
);
|
||||
merge(&mut settings.language_servers, src.language_servers.clone());
|
||||
merge(
|
||||
&mut settings.show_edit_predictions,
|
||||
src.show_edit_predictions,
|
||||
&mut settings.show_inline_completions,
|
||||
src.show_inline_completions,
|
||||
);
|
||||
merge(
|
||||
&mut settings.edit_predictions_disabled_in,
|
||||
src.edit_predictions_disabled_in.clone(),
|
||||
&mut settings.inline_completions_disabled_in,
|
||||
src.inline_completions_disabled_in.clone(),
|
||||
);
|
||||
merge(&mut settings.show_whitespaces, src.show_whitespaces);
|
||||
merge(
|
||||
|
||||
@@ -20,16 +20,16 @@ anthropic = { workspace = true, features = ["schemars"] }
|
||||
anyhow.workspace = true
|
||||
base64.workspace = true
|
||||
collections.workspace = true
|
||||
deepseek = { workspace = true, features = ["schemars"] }
|
||||
futures.workspace = true
|
||||
google_ai = { workspace = true, features = ["schemars"] }
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
image.workspace = true
|
||||
lmstudio = { workspace = true, features = ["schemars"] }
|
||||
log.workspace = true
|
||||
ollama = { workspace = true, features = ["schemars"] }
|
||||
open_ai = { workspace = true, features = ["schemars"] }
|
||||
lmstudio = { workspace = true, features = ["schemars"] }
|
||||
deepseek = { workspace = true, features = ["schemars"] }
|
||||
parking_lot.workspace = true
|
||||
proto.workspace = true
|
||||
schemars.workspace = true
|
||||
|
||||
@@ -90,10 +90,7 @@ impl CloudModel {
|
||||
Self::Google(model) => match model {
|
||||
google_ai::Model::Gemini15Pro
|
||||
| google_ai::Model::Gemini15Flash
|
||||
| google_ai::Model::Gemini20Pro
|
||||
| google_ai::Model::Gemini20Flash
|
||||
| google_ai::Model::Gemini20FlashThinking
|
||||
| google_ai::Model::Gemini20FlashLite
|
||||
| google_ai::Model::Custom { .. } => {
|
||||
LanguageModelAvailability::RequiresPlan(Plan::ZedPro)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use feature_flags::ZedPro;
|
||||
use gpui::{
|
||||
Action, AnyElement, AnyView, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
Action, AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
Subscription, Task, WeakEntity,
|
||||
};
|
||||
use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry};
|
||||
@@ -115,31 +115,20 @@ impl Render for LanguageModelSelector {
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct LanguageModelSelectorPopoverMenu<T, TT>
|
||||
pub struct LanguageModelSelectorPopoverMenu<T>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
T: PopoverTrigger,
|
||||
{
|
||||
language_model_selector: Entity<LanguageModelSelector>,
|
||||
trigger: T,
|
||||
tooltip: TT,
|
||||
handle: Option<PopoverMenuHandle<LanguageModelSelector>>,
|
||||
}
|
||||
|
||||
impl<T, TT> LanguageModelSelectorPopoverMenu<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
language_model_selector: Entity<LanguageModelSelector>,
|
||||
trigger: T,
|
||||
tooltip: TT,
|
||||
) -> Self {
|
||||
impl<T: PopoverTrigger> LanguageModelSelectorPopoverMenu<T> {
|
||||
pub fn new(language_model_selector: Entity<LanguageModelSelector>, trigger: T) -> Self {
|
||||
Self {
|
||||
language_model_selector,
|
||||
trigger,
|
||||
tooltip,
|
||||
handle: None,
|
||||
}
|
||||
}
|
||||
@@ -150,17 +139,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, TT> RenderOnce for LanguageModelSelectorPopoverMenu<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
impl<T: PopoverTrigger> RenderOnce for LanguageModelSelectorPopoverMenu<T> {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
let language_model_selector = self.language_model_selector.clone();
|
||||
|
||||
PopoverMenu::new("model-switcher")
|
||||
.menu(move |_window, _cx| Some(language_model_selector.clone()))
|
||||
.trigger_with_tooltip(self.trigger, self.tooltip)
|
||||
.trigger(self.trigger)
|
||||
.anchor(gpui::Corner::BottomRight)
|
||||
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
|
||||
.offset(gpui::Point {
|
||||
|
||||
@@ -17,7 +17,6 @@ anyhow.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
copilot = { workspace = true, features = ["schemars"] }
|
||||
deepseek = { workspace = true, features = ["schemars"] }
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
@@ -26,10 +25,11 @@ google_ai = { workspace = true, features = ["schemars"] }
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
language_model.workspace = true
|
||||
lmstudio = { workspace = true, features = ["schemars"] }
|
||||
menu.workspace = true
|
||||
ollama = { workspace = true, features = ["schemars"] }
|
||||
lmstudio = { workspace = true, features = ["schemars"] }
|
||||
open_ai = { workspace = true, features = ["schemars"] }
|
||||
deepseek = { workspace = true, features = ["schemars"] }
|
||||
project.workspace = true
|
||||
proto.workspace = true
|
||||
schemars.workspace = true
|
||||
|
||||
@@ -11,7 +11,8 @@ use ui::{
|
||||
Window,
|
||||
};
|
||||
use ui::{Button, ButtonStyle};
|
||||
use workspace::{Item, SplitDirection, Workspace};
|
||||
use workspace::Item;
|
||||
use workspace::Workspace;
|
||||
|
||||
actions!(debug, [OpenKeyContextView]);
|
||||
|
||||
@@ -19,12 +20,7 @@ pub fn init(cx: &mut App) {
|
||||
cx.observe_new(|workspace: &mut Workspace, _, _| {
|
||||
workspace.register_action(|workspace, _: &OpenKeyContextView, window, cx| {
|
||||
let key_context_view = cx.new(|cx| KeyContextView::new(window, cx));
|
||||
workspace.split_item(
|
||||
SplitDirection::Right,
|
||||
Box::new(key_context_view),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
workspace.add_item_to_active_pane(Box::new(key_context_view), None, true, window, cx)
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -293,7 +293,7 @@ impl SyntaxTreeView {
|
||||
|
||||
impl Render for SyntaxTreeView {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let mut rendered = div().flex_1().bg(cx.theme().colors().editor_background);
|
||||
let mut rendered = div().flex_1();
|
||||
|
||||
if let Some(layer) = self
|
||||
.editor
|
||||
|
||||
@@ -70,7 +70,7 @@ tree-sitter-c = { workspace = true, optional = true }
|
||||
tree-sitter-cpp = { workspace = true, optional = true }
|
||||
tree-sitter-css = { workspace = true, optional = true }
|
||||
tree-sitter-diff = { workspace = true, optional = true }
|
||||
tree-sitter-gitcommit = { workspace = true, optional = true }
|
||||
tree-sitter-gitcommit = {workspace = true, optional = true }
|
||||
tree-sitter-go = { workspace = true, optional = true }
|
||||
tree-sitter-go-mod = { workspace = true, optional = true }
|
||||
tree-sitter-gowork = { workspace = true, optional = true }
|
||||
|
||||
@@ -83,6 +83,7 @@ pub fn main() {
|
||||
selection.fade_out(0.7);
|
||||
selection
|
||||
},
|
||||
break_style: Default::default(),
|
||||
heading: Default::default(),
|
||||
};
|
||||
let markdown = cx.new(|cx| {
|
||||
|
||||
@@ -28,6 +28,7 @@ pub struct MarkdownStyle {
|
||||
pub block_quote_border_color: Hsla,
|
||||
pub syntax: Arc<SyntaxTheme>,
|
||||
pub selection_background_color: Hsla,
|
||||
pub break_style: StyleRefinement,
|
||||
pub heading: StyleRefinement,
|
||||
}
|
||||
|
||||
@@ -43,11 +44,11 @@ impl Default for MarkdownStyle {
|
||||
block_quote_border_color: Default::default(),
|
||||
syntax: Arc::new(SyntaxTheme::default()),
|
||||
selection_background_color: Default::default(),
|
||||
break_style: Default::default(),
|
||||
heading: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Markdown {
|
||||
source: String,
|
||||
selection: Selection,
|
||||
@@ -750,8 +751,8 @@ impl Element for MarkdownElement {
|
||||
}
|
||||
_ => log::error!("unsupported markdown tag end: {:?}", tag),
|
||||
},
|
||||
MarkdownEvent::Text(parsed) => {
|
||||
builder.push_text(parsed, range.start);
|
||||
MarkdownEvent::Text => {
|
||||
builder.push_text(&parsed_markdown.source[range.clone()], range.start);
|
||||
}
|
||||
MarkdownEvent::Code => {
|
||||
builder.push_text_style(self.style.inline_code.clone());
|
||||
@@ -776,7 +777,12 @@ impl Element for MarkdownElement {
|
||||
builder.pop_div()
|
||||
}
|
||||
MarkdownEvent::SoftBreak => builder.push_text(" ", range.start),
|
||||
MarkdownEvent::HardBreak => builder.push_text("\n", range.start),
|
||||
MarkdownEvent::HardBreak => {
|
||||
let mut d = div().py_3();
|
||||
d.style().refine(&self.style.break_style);
|
||||
builder.push_div(d, range, markdown_end);
|
||||
builder.pop_div()
|
||||
}
|
||||
_ => log::error!("unsupported markdown event {:?}", event),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,10 +37,9 @@ pub fn parse_markdown(text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
||||
}
|
||||
events.push((range, MarkdownEvent::End(tag)));
|
||||
}
|
||||
pulldown_cmark::Event::Text(parsed) => {
|
||||
pulldown_cmark::Event::Text(_) => {
|
||||
// Automatically detect links in text if we're not already within a markdown
|
||||
// link.
|
||||
let mut parsed = parsed.as_ref();
|
||||
if !within_link {
|
||||
let mut finder = LinkFinder::new();
|
||||
finder.kinds(&[linkify::LinkKind::Url]);
|
||||
@@ -50,12 +49,7 @@ pub fn parse_markdown(text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
||||
text_range.start + link.start()..text_range.start + link.end();
|
||||
|
||||
if link_range.start > range.start {
|
||||
let (text, tail) = parsed.split_at(link_range.start - range.start);
|
||||
events.push((
|
||||
range.start..link_range.start,
|
||||
MarkdownEvent::Text(SharedString::new(text)),
|
||||
));
|
||||
parsed = tail;
|
||||
events.push((range.start..link_range.start, MarkdownEvent::Text));
|
||||
}
|
||||
|
||||
events.push((
|
||||
@@ -67,20 +61,15 @@ pub fn parse_markdown(text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
||||
id: SharedString::default(),
|
||||
}),
|
||||
));
|
||||
|
||||
let (link_text, tail) = parsed.split_at(link_range.end - link_range.start);
|
||||
events.push((
|
||||
link_range.clone(),
|
||||
MarkdownEvent::Text(SharedString::new(link_text)),
|
||||
));
|
||||
events.push((link_range.clone(), MarkdownEvent::Text));
|
||||
events.push((link_range.clone(), MarkdownEvent::End(MarkdownTagEnd::Link)));
|
||||
|
||||
range.start = link_range.end;
|
||||
parsed = tail;
|
||||
}
|
||||
}
|
||||
|
||||
if range.start < range.end {
|
||||
events.push((range, MarkdownEvent::Text(SharedString::new(parsed))));
|
||||
events.push((range, MarkdownEvent::Text));
|
||||
}
|
||||
}
|
||||
pulldown_cmark::Event::Code(_) => {
|
||||
@@ -105,7 +94,7 @@ pub fn parse_markdown(text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
||||
events
|
||||
}
|
||||
|
||||
pub fn parse_links_only(mut text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
||||
pub fn parse_links_only(text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
||||
let mut events = Vec::new();
|
||||
let mut finder = LinkFinder::new();
|
||||
finder.kinds(&[linkify::LinkKind::Url]);
|
||||
@@ -117,15 +106,9 @@ pub fn parse_links_only(mut text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
||||
let link_range = link.start()..link.end();
|
||||
|
||||
if link_range.start > text_range.start {
|
||||
let (head, tail) = text.split_at(link_range.start - text_range.start);
|
||||
events.push((
|
||||
text_range.start..link_range.start,
|
||||
MarkdownEvent::Text(SharedString::new(head)),
|
||||
));
|
||||
text = tail;
|
||||
events.push((text_range.start..link_range.start, MarkdownEvent::Text));
|
||||
}
|
||||
|
||||
let (link_text, tail) = text.split_at(link_range.end - link_range.start);
|
||||
events.push((
|
||||
link_range.clone(),
|
||||
MarkdownEvent::Start(MarkdownTag::Link {
|
||||
@@ -135,18 +118,14 @@ pub fn parse_links_only(mut text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
||||
id: SharedString::default(),
|
||||
}),
|
||||
));
|
||||
events.push((
|
||||
link_range.clone(),
|
||||
MarkdownEvent::Text(SharedString::new(link_text)),
|
||||
));
|
||||
events.push((link_range.clone(), MarkdownEvent::Text));
|
||||
events.push((link_range.clone(), MarkdownEvent::End(MarkdownTagEnd::Link)));
|
||||
|
||||
text_range.start = link_range.end;
|
||||
text = tail;
|
||||
}
|
||||
|
||||
if text_range.end > text_range.start {
|
||||
events.push((text_range, MarkdownEvent::Text(SharedString::new(text))));
|
||||
events.push((text_range, MarkdownEvent::Text));
|
||||
}
|
||||
|
||||
events
|
||||
@@ -163,7 +142,7 @@ pub enum MarkdownEvent {
|
||||
/// End of a tagged element.
|
||||
End(MarkdownTagEnd),
|
||||
/// A text node.
|
||||
Text(SharedString),
|
||||
Text,
|
||||
/// An inline code node.
|
||||
Code,
|
||||
/// An HTML node.
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "migrator"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/migrator.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
collections.workspace = true
|
||||
convert_case.workspace = true
|
||||
tree-sitter-json.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,863 +0,0 @@
|
||||
use collections::HashMap;
|
||||
use convert_case::{Case, Casing};
|
||||
use std::{cmp::Reverse, ops::Range, sync::LazyLock};
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
fn migrate(text: &str, patterns: MigrationPatterns, query: &Query) -> Option<String> {
|
||||
let mut parser = tree_sitter::Parser::new();
|
||||
parser
|
||||
.set_language(&tree_sitter_json::LANGUAGE.into())
|
||||
.unwrap();
|
||||
let syntax_tree = parser.parse(&text, None).unwrap();
|
||||
|
||||
let mut cursor = tree_sitter::QueryCursor::new();
|
||||
let matches = cursor.matches(query, syntax_tree.root_node(), text.as_bytes());
|
||||
|
||||
let mut edits = vec![];
|
||||
for mat in matches {
|
||||
if let Some((_, callback)) = patterns.get(mat.pattern_index) {
|
||||
edits.extend(callback(&text, &mat, query));
|
||||
}
|
||||
}
|
||||
|
||||
edits.sort_by_key(|(range, _)| (range.start, Reverse(range.end)));
|
||||
edits.dedup_by(|(range_b, _), (range_a, _)| {
|
||||
range_a.contains(&range_b.start) || range_a.contains(&range_b.end)
|
||||
});
|
||||
|
||||
if edits.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut text = text.to_string();
|
||||
for (range, replacement) in edits.into_iter().rev() {
|
||||
text.replace_range(range, &replacement);
|
||||
}
|
||||
Some(text)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn migrate_keymap(text: &str) -> Option<String> {
|
||||
let transformed_text = migrate(
|
||||
text,
|
||||
KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS,
|
||||
&KEYMAP_MIGRATION_TRANSFORMATION_QUERY,
|
||||
);
|
||||
let replacement_text = migrate(
|
||||
&transformed_text.as_ref().unwrap_or(&text.to_string()),
|
||||
KEYMAP_MIGRATION_REPLACEMENT_PATTERNS,
|
||||
&KEYMAP_MIGRATION_REPLACEMENT_QUERY,
|
||||
);
|
||||
replacement_text.or(transformed_text)
|
||||
}
|
||||
|
||||
pub fn migrate_settings(text: &str) -> Option<String> {
|
||||
migrate(
|
||||
&text,
|
||||
SETTINGS_MIGRATION_PATTERNS,
|
||||
&SETTINGS_MIGRATION_QUERY,
|
||||
)
|
||||
}
|
||||
|
||||
type MigrationPatterns = &'static [(
|
||||
&'static str,
|
||||
fn(&str, &QueryMatch, &Query) -> Option<(Range<usize>, String)>,
|
||||
)];
|
||||
|
||||
static KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS: MigrationPatterns = &[
|
||||
(ACTION_ARRAY_PATTERN, replace_array_with_single_string),
|
||||
(
|
||||
ACTION_ARGUMENT_OBJECT_PATTERN,
|
||||
replace_action_argument_object_with_single_value,
|
||||
),
|
||||
(ACTION_STRING_PATTERN, rename_string_action),
|
||||
(CONTEXT_PREDICATE_PATTERN, rename_context_key),
|
||||
];
|
||||
|
||||
static KEYMAP_MIGRATION_TRANSFORMATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
||||
Query::new(
|
||||
&tree_sitter_json::LANGUAGE.into(),
|
||||
&KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS
|
||||
.iter()
|
||||
.map(|pattern| pattern.0)
|
||||
.collect::<String>(),
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
const ACTION_ARRAY_PATTERN: &str = r#"(document
|
||||
(array
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @name)
|
||||
value: (
|
||||
(object
|
||||
(pair
|
||||
key: (string)
|
||||
value: ((array
|
||||
. (string (string_content) @action_name)
|
||||
. (string (string_content) @argument)
|
||||
.)) @array
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(#eq? @name "bindings")
|
||||
)"#;
|
||||
|
||||
fn replace_array_with_single_string(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let array_ix = query.capture_index_for_name("array").unwrap();
|
||||
let action_name_ix = query.capture_index_for_name("action_name").unwrap();
|
||||
let argument_ix = query.capture_index_for_name("argument").unwrap();
|
||||
|
||||
let action_name = contents.get(
|
||||
mat.nodes_for_capture_index(action_name_ix)
|
||||
.next()?
|
||||
.byte_range(),
|
||||
)?;
|
||||
let argument = contents.get(
|
||||
mat.nodes_for_capture_index(argument_ix)
|
||||
.next()?
|
||||
.byte_range(),
|
||||
)?;
|
||||
|
||||
let replacement = TRANSFORM_ARRAY.get(&(action_name, argument))?;
|
||||
let replacement_as_string = format!("\"{replacement}\"");
|
||||
let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
|
||||
|
||||
Some((range_to_replace, replacement_as_string))
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
static TRANSFORM_ARRAY: LazyLock<HashMap<(&str, &str), &str>> = LazyLock::new(|| {
|
||||
HashMap::from_iter([
|
||||
// activate
|
||||
(("workspace::ActivatePaneInDirection", "Up"), "workspace::ActivatePaneUp"),
|
||||
(("workspace::ActivatePaneInDirection", "Down"), "workspace::ActivatePaneDown"),
|
||||
(("workspace::ActivatePaneInDirection", "Left"), "workspace::ActivatePaneLeft"),
|
||||
(("workspace::ActivatePaneInDirection", "Right"), "workspace::ActivatePaneRight"),
|
||||
// swap
|
||||
(("workspace::SwapPaneInDirection", "Up"), "workspace::SwapPaneUp"),
|
||||
(("workspace::SwapPaneInDirection", "Down"), "workspace::SwapPaneDown"),
|
||||
(("workspace::SwapPaneInDirection", "Left"), "workspace::SwapPaneLeft"),
|
||||
(("workspace::SwapPaneInDirection", "Right"), "workspace::SwapPaneRight"),
|
||||
// menu
|
||||
(("app_menu::NavigateApplicationMenuInDirection", "Left"), "app_menu::ActivateMenuLeft"),
|
||||
(("app_menu::NavigateApplicationMenuInDirection", "Right"), "app_menu::ActivateMenuRight"),
|
||||
// vim push
|
||||
(("vim::PushOperator", "Change"), "vim::PushChange"),
|
||||
(("vim::PushOperator", "Delete"), "vim::PushDelete"),
|
||||
(("vim::PushOperator", "Yank"), "vim::PushYank"),
|
||||
(("vim::PushOperator", "Replace"), "vim::PushReplace"),
|
||||
(("vim::PushOperator", "DeleteSurrounds"), "vim::PushDeleteSurrounds"),
|
||||
(("vim::PushOperator", "Mark"), "vim::PushMark"),
|
||||
(("vim::PushOperator", "Indent"), "vim::PushIndent"),
|
||||
(("vim::PushOperator", "Outdent"), "vim::PushOutdent"),
|
||||
(("vim::PushOperator", "AutoIndent"), "vim::PushAutoIndent"),
|
||||
(("vim::PushOperator", "Rewrap"), "vim::PushRewrap"),
|
||||
(("vim::PushOperator", "ShellCommand"), "vim::PushShellCommand"),
|
||||
(("vim::PushOperator", "Lowercase"), "vim::PushLowercase"),
|
||||
(("vim::PushOperator", "Uppercase"), "vim::PushUppercase"),
|
||||
(("vim::PushOperator", "OppositeCase"), "vim::PushOppositeCase"),
|
||||
(("vim::PushOperator", "Register"), "vim::PushRegister"),
|
||||
(("vim::PushOperator", "RecordRegister"), "vim::PushRecordRegister"),
|
||||
(("vim::PushOperator", "ReplayRegister"), "vim::PushReplayRegister"),
|
||||
(("vim::PushOperator", "ReplaceWithRegister"), "vim::PushReplaceWithRegister"),
|
||||
(("vim::PushOperator", "ToggleComments"), "vim::PushToggleComments"),
|
||||
// vim switch
|
||||
(("vim::SwitchMode", "Normal"), "vim::SwitchToNormalMode"),
|
||||
(("vim::SwitchMode", "Insert"), "vim::SwitchToInsertMode"),
|
||||
(("vim::SwitchMode", "Replace"), "vim::SwitchToReplaceMode"),
|
||||
(("vim::SwitchMode", "Visual"), "vim::SwitchToVisualMode"),
|
||||
(("vim::SwitchMode", "VisualLine"), "vim::SwitchToVisualLineMode"),
|
||||
(("vim::SwitchMode", "VisualBlock"), "vim::SwitchToVisualBlockMode"),
|
||||
(("vim::SwitchMode", "HelixNormal"), "vim::SwitchToHelixNormalMode"),
|
||||
// vim resize
|
||||
(("vim::ResizePane", "Widen"), "vim::ResizePaneRight"),
|
||||
(("vim::ResizePane", "Narrow"), "vim::ResizePaneLeft"),
|
||||
(("vim::ResizePane", "Shorten"), "vim::ResizePaneDown"),
|
||||
(("vim::ResizePane", "Lengthen"), "vim::ResizePaneUp"),
|
||||
])
|
||||
});
|
||||
|
||||
const ACTION_ARGUMENT_OBJECT_PATTERN: &str = r#"(document
|
||||
(array
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @name)
|
||||
value: (
|
||||
(object
|
||||
(pair
|
||||
key: (string)
|
||||
value: ((array
|
||||
. (string (string_content) @action_name)
|
||||
. (object
|
||||
(pair
|
||||
key: (string (string_content) @action_key)
|
||||
value: (_) @argument))
|
||||
. ) @array
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(#eq? @name "bindings")
|
||||
)"#;
|
||||
|
||||
/// [ "editor::FoldAtLevel", { "level": 1 } ] -> [ "editor::FoldAtLevel", 1 ]
|
||||
fn replace_action_argument_object_with_single_value(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let array_ix = query.capture_index_for_name("array").unwrap();
|
||||
let action_name_ix = query.capture_index_for_name("action_name").unwrap();
|
||||
let action_key_ix = query.capture_index_for_name("action_key").unwrap();
|
||||
let argument_ix = query.capture_index_for_name("argument").unwrap();
|
||||
|
||||
let action_name = contents.get(
|
||||
mat.nodes_for_capture_index(action_name_ix)
|
||||
.next()?
|
||||
.byte_range(),
|
||||
)?;
|
||||
let action_key = contents.get(
|
||||
mat.nodes_for_capture_index(action_key_ix)
|
||||
.next()?
|
||||
.byte_range(),
|
||||
)?;
|
||||
let argument = contents.get(
|
||||
mat.nodes_for_capture_index(argument_ix)
|
||||
.next()?
|
||||
.byte_range(),
|
||||
)?;
|
||||
|
||||
let new_action_name = UNWRAP_OBJECTS.get(&action_name)?.get(&action_key)?;
|
||||
|
||||
let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
|
||||
let replacement = format!("[\"{}\", {}]", new_action_name, argument);
|
||||
Some((range_to_replace, replacement))
|
||||
}
|
||||
|
||||
// "ctrl-k ctrl-1": [ "editor::PushOperator", { "Object": {} } ] -> [ "editor::vim::PushObject", {} ]
|
||||
static UNWRAP_OBJECTS: LazyLock<HashMap<&str, HashMap<&str, &str>>> = LazyLock::new(|| {
|
||||
HashMap::from_iter([
|
||||
(
|
||||
"editor::FoldAtLevel",
|
||||
HashMap::from_iter([("level", "editor::FoldAtLevel")]),
|
||||
),
|
||||
(
|
||||
"vim::PushOperator",
|
||||
HashMap::from_iter([
|
||||
("Object", "vim::PushObject"),
|
||||
("FindForward", "vim::PushFindForward"),
|
||||
("FindBackward", "vim::PushFindBackward"),
|
||||
("Sneak", "vim::PushSneak"),
|
||||
("SneakBackward", "vim::PushSneakBackward"),
|
||||
("AddSurrounds", "vim::PushAddSurrounds"),
|
||||
("ChangeSurrounds", "vim::PushChangeSurrounds"),
|
||||
("Jump", "vim::PushJump"),
|
||||
("Digraph", "vim::PushDigraph"),
|
||||
("Literal", "vim::PushLiteral"),
|
||||
]),
|
||||
),
|
||||
])
|
||||
});
|
||||
|
||||
static KEYMAP_MIGRATION_REPLACEMENT_PATTERNS: MigrationPatterns = &[(
|
||||
ACTION_ARGUMENT_SNAKE_CASE_PATTERN,
|
||||
action_argument_snake_case,
|
||||
)];
|
||||
|
||||
static KEYMAP_MIGRATION_REPLACEMENT_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
||||
Query::new(
|
||||
&tree_sitter_json::LANGUAGE.into(),
|
||||
&KEYMAP_MIGRATION_REPLACEMENT_PATTERNS
|
||||
.iter()
|
||||
.map(|pattern| pattern.0)
|
||||
.collect::<String>(),
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
const ACTION_STRING_PATTERN: &str = r#"(document
|
||||
(array
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @name)
|
||||
value: (
|
||||
(object
|
||||
(pair
|
||||
key: (string)
|
||||
value: (string (string_content) @action_name)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(#eq? @name "bindings")
|
||||
)"#;
|
||||
|
||||
fn rename_string_action(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let action_name_ix = query.capture_index_for_name("action_name").unwrap();
|
||||
let action_name_range = mat
|
||||
.nodes_for_capture_index(action_name_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
let action_name = contents.get(action_name_range.clone())?;
|
||||
let new_action_name = STRING_REPLACE.get(&action_name)?;
|
||||
Some((action_name_range, new_action_name.to_string()))
|
||||
}
|
||||
|
||||
// "ctrl-k ctrl-1": "inline_completion::ToggleMenu" -> "edit_prediction::ToggleMenu"
|
||||
#[rustfmt::skip]
|
||||
static STRING_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
|
||||
HashMap::from_iter([
|
||||
("inline_completion::ToggleMenu", "edit_prediction::ToggleMenu"),
|
||||
("editor::NextInlineCompletion", "editor::NextEditPrediction"),
|
||||
("editor::PreviousInlineCompletion", "editor::PreviousEditPrediction"),
|
||||
("editor::AcceptPartialInlineCompletion", "editor::AcceptPartialEditPrediction"),
|
||||
("editor::ShowInlineCompletion", "editor::ShowEditPrediction"),
|
||||
("editor::AcceptInlineCompletion", "editor::AcceptEditPrediction"),
|
||||
("editor::ToggleInlineCompletions", "editor::ToggleEditPrediction"),
|
||||
])
|
||||
});
|
||||
|
||||
const CONTEXT_PREDICATE_PATTERN: &str = r#"
|
||||
(array
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @name)
|
||||
value: (string (string_content) @context_predicate)
|
||||
)
|
||||
)
|
||||
)
|
||||
(#eq? @name "context")
|
||||
"#;
|
||||
|
||||
fn rename_context_key(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let context_predicate_ix = query.capture_index_for_name("context_predicate").unwrap();
|
||||
let context_predicate_range = mat
|
||||
.nodes_for_capture_index(context_predicate_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
let old_predicate = contents.get(context_predicate_range.clone())?.to_string();
|
||||
let mut new_predicate = old_predicate.to_string();
|
||||
for (old_key, new_key) in CONTEXT_REPLACE.iter() {
|
||||
new_predicate = new_predicate.replace(old_key, new_key);
|
||||
}
|
||||
if new_predicate != old_predicate {
|
||||
Some((context_predicate_range, new_predicate.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
const ACTION_ARGUMENT_SNAKE_CASE_PATTERN: &str = r#"(document
|
||||
(array
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @name)
|
||||
value: (
|
||||
(object
|
||||
(pair
|
||||
key: (string)
|
||||
value: ((array
|
||||
. (string (string_content) @action_name)
|
||||
. (object
|
||||
(pair
|
||||
key: (string (string_content) @argument_key)
|
||||
value: (_) @argument_value))
|
||||
. ) @array
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(#eq? @name "bindings")
|
||||
)"#;
|
||||
|
||||
fn is_snake_case(text: &str) -> bool {
|
||||
text == text.to_case(Case::Snake)
|
||||
}
|
||||
|
||||
fn to_snake_case(text: &str) -> String {
|
||||
text.to_case(Case::Snake)
|
||||
}
|
||||
|
||||
/// [ "editor::FoldAtLevel", { "SomeKey": "Value" } ] -> [ "editor::FoldAtLevel", { "some_key" : "value" } ]
|
||||
fn action_argument_snake_case(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let array_ix = query.capture_index_for_name("array").unwrap();
|
||||
let action_name_ix = query.capture_index_for_name("action_name").unwrap();
|
||||
let argument_key_ix = query.capture_index_for_name("argument_key").unwrap();
|
||||
let argument_value_ix = query.capture_index_for_name("argument_value").unwrap();
|
||||
let action_name = contents.get(
|
||||
mat.nodes_for_capture_index(action_name_ix)
|
||||
.next()?
|
||||
.byte_range(),
|
||||
)?;
|
||||
|
||||
let argument_key = contents.get(
|
||||
mat.nodes_for_capture_index(argument_key_ix)
|
||||
.next()?
|
||||
.byte_range(),
|
||||
)?;
|
||||
|
||||
let argument_value_node = mat.nodes_for_capture_index(argument_value_ix).next()?;
|
||||
let argument_value = contents.get(argument_value_node.byte_range())?;
|
||||
|
||||
let mut needs_replacement = false;
|
||||
let mut new_key = argument_key.to_string();
|
||||
if !is_snake_case(argument_key) {
|
||||
new_key = to_snake_case(argument_key);
|
||||
needs_replacement = true;
|
||||
}
|
||||
|
||||
let mut new_value = argument_value.to_string();
|
||||
if argument_value_node.kind() == "string" {
|
||||
let inner_value = argument_value.trim_matches('"');
|
||||
if !is_snake_case(inner_value) {
|
||||
new_value = format!("\"{}\"", to_snake_case(inner_value));
|
||||
needs_replacement = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !needs_replacement {
|
||||
return None;
|
||||
}
|
||||
|
||||
let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
|
||||
let replacement = format!(
|
||||
"[\"{}\", {{ \"{}\": {} }}]",
|
||||
action_name, new_key, new_value
|
||||
);
|
||||
|
||||
Some((range_to_replace, replacement))
|
||||
}
|
||||
|
||||
// "context": "Editor && inline_completion && !showing_completions" -> "Editor && edit_prediction && !showing_completions"
|
||||
pub static CONTEXT_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
|
||||
HashMap::from_iter([
|
||||
("inline_completion", "edit_prediction"),
|
||||
(
|
||||
"inline_completion_requires_modifier",
|
||||
"edit_prediction_requires_modifier",
|
||||
),
|
||||
])
|
||||
});
|
||||
|
||||
static SETTINGS_MIGRATION_PATTERNS: MigrationPatterns = &[
|
||||
(SETTINGS_STRING_REPLACE_QUERY, replace_setting_name),
|
||||
(SETTINGS_REPLACE_NESTED_KEY, replace_setting_nested_key),
|
||||
(
|
||||
SETTINGS_REPLACE_IN_LANGUAGES_QUERY,
|
||||
replace_setting_in_languages,
|
||||
),
|
||||
];
|
||||
|
||||
static SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
||||
Query::new(
|
||||
&tree_sitter_json::LANGUAGE.into(),
|
||||
&SETTINGS_MIGRATION_PATTERNS
|
||||
.iter()
|
||||
.map(|pattern| pattern.0)
|
||||
.collect::<String>(),
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
static SETTINGS_STRING_REPLACE_QUERY: &str = r#"(document
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @name)
|
||||
value: (_)
|
||||
)
|
||||
)
|
||||
)"#;
|
||||
|
||||
fn replace_setting_name(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let setting_capture_ix = query.capture_index_for_name("name").unwrap();
|
||||
let setting_name_range = mat
|
||||
.nodes_for_capture_index(setting_capture_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
let setting_name = contents.get(setting_name_range.clone())?;
|
||||
let new_setting_name = SETTINGS_STRING_REPLACE.get(&setting_name)?;
|
||||
Some((setting_name_range, new_setting_name.to_string()))
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub static SETTINGS_STRING_REPLACE: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
|
||||
HashMap::from_iter([
|
||||
("show_inline_completions_in_menu", "show_edit_predictions_in_menu"),
|
||||
("show_inline_completions", "show_edit_predictions"),
|
||||
("inline_completions_disabled_in", "edit_predictions_disabled_in"),
|
||||
("inline_completions", "edit_predictions")
|
||||
])
|
||||
});
|
||||
|
||||
static SETTINGS_REPLACE_NESTED_KEY: &str = r#"
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @parent_key)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string (string_content) @setting_name)
|
||||
value: (_) @value
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
fn replace_setting_nested_key(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let parent_object_capture_ix = query.capture_index_for_name("parent_key").unwrap();
|
||||
let parent_object_range = mat
|
||||
.nodes_for_capture_index(parent_object_capture_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
let parent_object_name = contents.get(parent_object_range.clone())?;
|
||||
|
||||
let setting_name_ix = query.capture_index_for_name("setting_name").unwrap();
|
||||
let setting_range = mat
|
||||
.nodes_for_capture_index(setting_name_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
let setting_name = contents.get(setting_range.clone())?;
|
||||
|
||||
let new_setting_name = SETTINGS_NESTED_STRING_REPLACE
|
||||
.get(&parent_object_name)?
|
||||
.get(setting_name)?;
|
||||
|
||||
Some((setting_range, new_setting_name.to_string()))
|
||||
}
|
||||
|
||||
// "features": {
|
||||
// "inline_completion_provider": "copilot"
|
||||
// },
|
||||
pub static SETTINGS_NESTED_STRING_REPLACE: LazyLock<
|
||||
HashMap<&'static str, HashMap<&'static str, &'static str>>,
|
||||
> = LazyLock::new(|| {
|
||||
HashMap::from_iter([(
|
||||
"features",
|
||||
HashMap::from_iter([("inline_completion_provider", "edit_prediction_provider")]),
|
||||
)])
|
||||
});
|
||||
|
||||
static SETTINGS_REPLACE_IN_LANGUAGES_QUERY: &str = r#"
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @languages)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string)
|
||||
value: (object
|
||||
(pair
|
||||
key: (string (string_content) @setting_name)
|
||||
value: (_) @value
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
(#eq? @languages "languages")
|
||||
"#;
|
||||
|
||||
fn replace_setting_in_languages(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let setting_capture_ix = query.capture_index_for_name("setting_name").unwrap();
|
||||
let setting_name_range = mat
|
||||
.nodes_for_capture_index(setting_capture_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
let setting_name = contents.get(setting_name_range.clone())?;
|
||||
let new_setting_name = LANGUAGE_SETTINGS_REPLACE.get(&setting_name)?;
|
||||
|
||||
Some((setting_name_range, new_setting_name.to_string()))
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
static LANGUAGE_SETTINGS_REPLACE: LazyLock<
|
||||
HashMap<&'static str, &'static str>,
|
||||
> = LazyLock::new(|| {
|
||||
HashMap::from_iter([
|
||||
("show_inline_completions", "show_edit_predictions"),
|
||||
("inline_completions_disabled_in", "edit_predictions_disabled_in"),
|
||||
])
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_migrate_keymap(input: &str, output: Option<&str>) {
|
||||
let migrated = migrate_keymap(&input);
|
||||
pretty_assertions::assert_eq!(migrated.as_deref(), output);
|
||||
}
|
||||
|
||||
fn assert_migrate_settings(input: &str, output: Option<&str>) {
|
||||
let migrated = migrate_settings(&input);
|
||||
pretty_assertions::assert_eq!(migrated.as_deref(), output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_array_with_single_string() {
|
||||
assert_migrate_keymap(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-1": ["workspace::ActivatePaneInDirection", "Up"]
|
||||
}
|
||||
}
|
||||
]
|
||||
"#,
|
||||
Some(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-1": "workspace::ActivatePaneUp"
|
||||
}
|
||||
}
|
||||
]
|
||||
"#,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_action_argument_object_with_single_value() {
|
||||
assert_migrate_keymap(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-1": ["editor::FoldAtLevel", { "level": 1 }]
|
||||
}
|
||||
}
|
||||
]
|
||||
"#,
|
||||
Some(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-1": ["editor::FoldAtLevel", 1]
|
||||
}
|
||||
}
|
||||
]
|
||||
"#,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_action_argument_object_with_single_value_2() {
|
||||
assert_migrate_keymap(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-1": ["vim::PushOperator", { "Object": { "some" : "value" } }]
|
||||
}
|
||||
}
|
||||
]
|
||||
"#,
|
||||
Some(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-1": ["vim::PushObject", { "some" : "value" }]
|
||||
}
|
||||
}
|
||||
]
|
||||
"#,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rename_string_action() {
|
||||
assert_migrate_keymap(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-1": "inline_completion::ToggleMenu"
|
||||
}
|
||||
}
|
||||
]
|
||||
"#,
|
||||
Some(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-1": "edit_prediction::ToggleMenu"
|
||||
}
|
||||
}
|
||||
]
|
||||
"#,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rename_context_key() {
|
||||
assert_migrate_keymap(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"context": "Editor && inline_completion && !showing_completions"
|
||||
}
|
||||
]
|
||||
"#,
|
||||
Some(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"context": "Editor && edit_prediction && !showing_completions"
|
||||
}
|
||||
]
|
||||
"#,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_action_argument_snake_case() {
|
||||
// First performs transformations, then replacements
|
||||
assert_migrate_keymap(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-1": ["vim::PushOperator", { "Object": { "SomeKey": "Value" } }],
|
||||
"cmd-2": ["vim::SomeOtherAction", { "OtherKey": "Value" }],
|
||||
"cmd-3": ["vim::SomeDifferentAction", { "OtherKey": true }],
|
||||
"cmd-4": ["vim::OneMore", { "OtherKey": 4 }]
|
||||
}
|
||||
}
|
||||
]
|
||||
"#,
|
||||
Some(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-1": ["vim::PushObject", { "some_key": "value" }],
|
||||
"cmd-2": ["vim::SomeOtherAction", { "other_key": "value" }],
|
||||
"cmd-3": ["vim::SomeDifferentAction", { "other_key": true }],
|
||||
"cmd-4": ["vim::OneMore", { "other_key": 4 }]
|
||||
}
|
||||
}
|
||||
]
|
||||
"#,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_setting_name() {
|
||||
assert_migrate_settings(
|
||||
r#"
|
||||
{
|
||||
"show_inline_completions_in_menu": true,
|
||||
"show_inline_completions": true,
|
||||
"inline_completions_disabled_in": ["string"],
|
||||
"inline_completions": { "some" : "value" }
|
||||
}
|
||||
"#,
|
||||
Some(
|
||||
r#"
|
||||
{
|
||||
"show_edit_predictions_in_menu": true,
|
||||
"show_edit_predictions": true,
|
||||
"edit_predictions_disabled_in": ["string"],
|
||||
"edit_predictions": { "some" : "value" }
|
||||
}
|
||||
"#,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested_string_replace_for_settings() {
|
||||
assert_migrate_settings(
|
||||
r#"
|
||||
{
|
||||
"features": {
|
||||
"inline_completion_provider": "zed"
|
||||
},
|
||||
}
|
||||
"#,
|
||||
Some(
|
||||
r#"
|
||||
{
|
||||
"features": {
|
||||
"edit_prediction_provider": "zed"
|
||||
},
|
||||
}
|
||||
"#,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_settings_in_languages() {
|
||||
assert_migrate_settings(
|
||||
r#"
|
||||
{
|
||||
"languages": {
|
||||
"Astro": {
|
||||
"show_inline_completions": true
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
Some(
|
||||
r#"
|
||||
{
|
||||
"languages": {
|
||||
"Astro": {
|
||||
"show_edit_predictions": true
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2376,17 +2376,24 @@ impl MultiBuffer {
|
||||
false
|
||||
}
|
||||
|
||||
fn expand_or_collapse_diff_hunks_internal(
|
||||
fn expand_or_collapse_diff_hunks(
|
||||
&mut self,
|
||||
ranges: impl Iterator<Item = (Range<Point>, ExcerptId)>,
|
||||
ranges: Vec<Range<Anchor>>,
|
||||
expand: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.sync(cx);
|
||||
let mut snapshot = self.snapshot.borrow_mut();
|
||||
let mut excerpt_edits = Vec::new();
|
||||
for (range, end_excerpt_id) in ranges {
|
||||
for diff_hunk in snapshot.diff_hunks_in_range(range) {
|
||||
for range in ranges.iter() {
|
||||
let end_excerpt_id = range.end.excerpt_id;
|
||||
let range = range.to_point(&snapshot);
|
||||
let mut peek_end = range.end;
|
||||
if range.end.row < snapshot.max_row().0 {
|
||||
peek_end = Point::new(range.end.row + 1, 0);
|
||||
};
|
||||
|
||||
for diff_hunk in snapshot.diff_hunks_in_range(range.start..peek_end) {
|
||||
if diff_hunk.excerpt_id.cmp(&end_excerpt_id, &snapshot).is_gt() {
|
||||
continue;
|
||||
}
|
||||
@@ -2421,44 +2428,6 @@ impl MultiBuffer {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn expand_or_collapse_diff_hunks_narrow(
|
||||
&mut self,
|
||||
ranges: Vec<Range<Anchor>>,
|
||||
expand: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let snapshot = self.snapshot.borrow().clone();
|
||||
self.expand_or_collapse_diff_hunks_internal(
|
||||
ranges
|
||||
.iter()
|
||||
.map(move |range| (range.to_point(&snapshot), range.end.excerpt_id)),
|
||||
expand,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn expand_or_collapse_diff_hunks(
|
||||
&mut self,
|
||||
ranges: Vec<Range<Anchor>>,
|
||||
expand: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let snapshot = self.snapshot.borrow().clone();
|
||||
self.expand_or_collapse_diff_hunks_internal(
|
||||
ranges.iter().map(move |range| {
|
||||
let end_excerpt_id = range.end.excerpt_id;
|
||||
let range = range.to_point(&snapshot);
|
||||
let mut peek_end = range.end;
|
||||
if range.end.row < snapshot.max_row().0 {
|
||||
peek_end = Point::new(range.end.row + 1, 0);
|
||||
};
|
||||
(range.start..peek_end, end_excerpt_id)
|
||||
}),
|
||||
expand,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn resize_excerpt(
|
||||
&mut self,
|
||||
id: ExcerptId,
|
||||
@@ -7320,7 +7289,6 @@ impl ToOffset for Point {
|
||||
}
|
||||
|
||||
impl ToOffset for usize {
|
||||
#[track_caller]
|
||||
fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
|
||||
assert!(*self <= snapshot.len(), "offset is out of range");
|
||||
*self
|
||||
|
||||
@@ -2,14 +2,10 @@ mod outline_panel_settings;
|
||||
|
||||
use std::{
|
||||
cmp,
|
||||
collections::BTreeMap,
|
||||
hash::Hash,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf, MAIN_SEPARATOR_STR},
|
||||
sync::{
|
||||
atomic::{self, AtomicBool},
|
||||
Arc, OnceLock,
|
||||
},
|
||||
sync::{atomic::AtomicBool, Arc, OnceLock},
|
||||
time::Duration,
|
||||
u32,
|
||||
};
|
||||
@@ -107,7 +103,6 @@ pub struct OutlinePanel {
|
||||
active_item: Option<ActiveItem>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
updating_fs_entries: bool,
|
||||
updating_cached_entries: bool,
|
||||
new_entries_for_fs_update: HashSet<ExcerptId>,
|
||||
fs_entries_update_task: Task<()>,
|
||||
cached_entries_update_task: Task<()>,
|
||||
@@ -782,10 +777,7 @@ impl OutlinePanel {
|
||||
excerpt.invalidate_outlines();
|
||||
}
|
||||
}
|
||||
let update_cached_items = outline_panel.update_non_fs_items(window, cx);
|
||||
if update_cached_items {
|
||||
outline_panel.update_cached_entries(Some(UPDATE_DEBOUNCE), window, cx);
|
||||
}
|
||||
outline_panel.update_non_fs_items(window, cx);
|
||||
} else if &outline_panel_settings != new_settings {
|
||||
outline_panel_settings = *new_settings;
|
||||
cx.notify();
|
||||
@@ -822,7 +814,6 @@ impl OutlinePanel {
|
||||
active_item: None,
|
||||
pending_serialization: Task::ready(None),
|
||||
updating_fs_entries: false,
|
||||
updating_cached_entries: false,
|
||||
new_entries_for_fs_update: HashSet::default(),
|
||||
preserve_selection_on_buffer_fold_toggles: HashSet::default(),
|
||||
fs_entries_update_task: Task::ready(()),
|
||||
@@ -931,7 +922,7 @@ impl OutlinePanel {
|
||||
cx.propagate()
|
||||
} else if let Some(selected_entry) = self.selected_entry().cloned() {
|
||||
self.toggle_expanded(&selected_entry, window, cx);
|
||||
self.scroll_editor_to_entry(&selected_entry, true, true, window, cx);
|
||||
self.scroll_editor_to_entry(&selected_entry, true, false, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -986,7 +977,7 @@ impl OutlinePanel {
|
||||
&mut self,
|
||||
entry: &PanelEntry,
|
||||
prefer_selection_change: bool,
|
||||
prefer_focus_change: bool,
|
||||
change_focus: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<OutlinePanel>,
|
||||
) {
|
||||
@@ -996,13 +987,9 @@ impl OutlinePanel {
|
||||
let active_multi_buffer = active_editor.read(cx).buffer().clone();
|
||||
let multi_buffer_snapshot = active_multi_buffer.read(cx).snapshot(cx);
|
||||
let mut change_selection = prefer_selection_change;
|
||||
let mut change_focus = prefer_focus_change;
|
||||
let mut scroll_to_buffer = None;
|
||||
let scroll_target = match entry {
|
||||
PanelEntry::FoldedDirs(..) | PanelEntry::Fs(FsEntry::Directory(..)) => {
|
||||
change_focus = false;
|
||||
None
|
||||
}
|
||||
PanelEntry::FoldedDirs(..) | PanelEntry::Fs(FsEntry::Directory(..)) => None,
|
||||
PanelEntry::Fs(FsEntry::ExternalFile(file)) => {
|
||||
change_selection = false;
|
||||
scroll_to_buffer = Some(file.buffer_id);
|
||||
@@ -1046,7 +1033,6 @@ impl OutlinePanel {
|
||||
}),
|
||||
PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => {
|
||||
change_selection = false;
|
||||
change_focus = false;
|
||||
multi_buffer_snapshot.anchor_in_excerpt(excerpt.id, excerpt.range.context.start)
|
||||
}
|
||||
PanelEntry::Search(search_entry) => Some(search_entry.match_range.start),
|
||||
@@ -2627,7 +2613,7 @@ impl OutlinePanel {
|
||||
.spawn(async move {
|
||||
let mut processed_external_buffers = HashSet::default();
|
||||
let mut new_worktree_entries =
|
||||
BTreeMap::<WorktreeId, HashMap<ProjectEntryId, GitEntry>>::default();
|
||||
HashMap::<WorktreeId, HashMap<ProjectEntryId, GitEntry>>::default();
|
||||
let mut worktree_excerpts = HashMap::<
|
||||
WorktreeId,
|
||||
HashMap<ProjectEntryId, (BufferId, Vec<ExcerptId>)>,
|
||||
@@ -2910,8 +2896,8 @@ impl OutlinePanel {
|
||||
outline_panel.fs_entries = new_fs_entries;
|
||||
outline_panel.fs_entries_depth = new_depth_map;
|
||||
outline_panel.fs_children_count = new_children_count;
|
||||
outline_panel.update_cached_entries(Some(UPDATE_DEBOUNCE), window, cx);
|
||||
outline_panel.update_non_fs_items(window, cx);
|
||||
outline_panel.update_cached_entries(debounce, window, cx);
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
@@ -2936,11 +2922,7 @@ impl OutlinePanel {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>| {
|
||||
if matches!(e, SearchEvent::MatchesInvalidated) {
|
||||
let update_cached_items = outline_panel.update_search_matches(window, cx);
|
||||
if update_cached_items {
|
||||
outline_panel.selected_entry.invalidate();
|
||||
outline_panel.update_cached_entries(Some(UPDATE_DEBOUNCE), window, cx);
|
||||
}
|
||||
outline_panel.update_search_matches(window, cx);
|
||||
};
|
||||
outline_panel.autoscroll(cx);
|
||||
},
|
||||
@@ -3206,12 +3188,10 @@ impl OutlinePanel {
|
||||
}
|
||||
|
||||
let syntax_theme = cx.theme().syntax().clone();
|
||||
let first_update = Arc::new(AtomicBool::new(true));
|
||||
for (buffer_id, (buffer_snapshot, excerpt_ranges)) in excerpt_fetch_ranges {
|
||||
for (excerpt_id, excerpt_range) in excerpt_ranges {
|
||||
let syntax_theme = syntax_theme.clone();
|
||||
let buffer_snapshot = buffer_snapshot.clone();
|
||||
let first_update = first_update.clone();
|
||||
self.outline_fetch_tasks.insert(
|
||||
(buffer_id, excerpt_id),
|
||||
cx.spawn_in(window, |outline_panel, mut cx| async move {
|
||||
@@ -3235,16 +3215,13 @@ impl OutlinePanel {
|
||||
.or_default()
|
||||
.get_mut(&excerpt_id)
|
||||
{
|
||||
let debounce = if first_update
|
||||
.fetch_and(false, atomic::Ordering::AcqRel)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(UPDATE_DEBOUNCE)
|
||||
};
|
||||
excerpt.outlines = ExcerptOutlines::Outlines(fetched_outlines);
|
||||
outline_panel.update_cached_entries(debounce, window, cx);
|
||||
}
|
||||
outline_panel.update_cached_entries(
|
||||
Some(UPDATE_DEBOUNCE),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}),
|
||||
@@ -3399,7 +3376,6 @@ impl OutlinePanel {
|
||||
|
||||
let is_singleton = self.is_singleton_active(cx);
|
||||
let query = self.query(cx);
|
||||
self.updating_cached_entries = true;
|
||||
self.cached_entries_update_task = cx.spawn_in(window, |outline_panel, mut cx| async move {
|
||||
if let Some(debounce) = debounce {
|
||||
cx.background_executor().timer(debounce).await;
|
||||
@@ -3434,7 +3410,6 @@ impl OutlinePanel {
|
||||
}
|
||||
|
||||
outline_panel.autoscroll(cx);
|
||||
outline_panel.updating_cached_entries = false;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
@@ -3493,8 +3468,7 @@ impl OutlinePanel {
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
while let Some(parent) = parent_dirs.last() {
|
||||
if !is_root && directory_entry.entry.path.starts_with(&parent.path)
|
||||
{
|
||||
if directory_entry.entry.path.starts_with(&parent.path) {
|
||||
break;
|
||||
}
|
||||
parent_dirs.pop();
|
||||
@@ -3941,27 +3915,19 @@ impl OutlinePanel {
|
||||
!self.collapsed_entries.contains(&entry_to_check)
|
||||
}
|
||||
|
||||
fn update_non_fs_items(&mut self, window: &mut Window, cx: &mut Context<OutlinePanel>) -> bool {
|
||||
fn update_non_fs_items(&mut self, window: &mut Window, cx: &mut Context<OutlinePanel>) {
|
||||
if !self.active {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
let mut update_cached_items = false;
|
||||
update_cached_items |= self.update_search_matches(window, cx);
|
||||
self.update_search_matches(window, cx);
|
||||
self.fetch_outdated_outlines(window, cx);
|
||||
if update_cached_items {
|
||||
self.selected_entry.invalidate();
|
||||
}
|
||||
update_cached_items
|
||||
self.autoscroll(cx);
|
||||
}
|
||||
|
||||
fn update_search_matches(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<OutlinePanel>,
|
||||
) -> bool {
|
||||
fn update_search_matches(&mut self, window: &mut Window, cx: &mut Context<OutlinePanel>) {
|
||||
if !self.active {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
let project_search = self
|
||||
@@ -4044,7 +4010,10 @@ impl OutlinePanel {
|
||||
cx,
|
||||
));
|
||||
}
|
||||
update_cached_entries
|
||||
if update_cached_entries {
|
||||
self.selected_entry.invalidate();
|
||||
self.update_cached_entries(Some(UPDATE_DEBOUNCE), window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -4457,42 +4426,41 @@ impl OutlinePanel {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Div {
|
||||
let contents = if self.cached_entries.is_empty() {
|
||||
let header = if self.updating_fs_entries || self.updating_cached_entries {
|
||||
None
|
||||
let header = if self.updating_fs_entries {
|
||||
"Loading outlines"
|
||||
} else if query.is_some() {
|
||||
Some("No matches for query")
|
||||
"No matches for query"
|
||||
} else {
|
||||
Some("No outlines available")
|
||||
"No outlines available"
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.flex_1()
|
||||
.justify_center()
|
||||
.size_full()
|
||||
.when_some(header, |panel, header| {
|
||||
panel
|
||||
.child(h_flex().justify_center().child(Label::new(header)))
|
||||
.when_some(query.clone(), |panel, query| {
|
||||
panel.child(h_flex().justify_center().child(Label::new(query)))
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.pt(DynamicSpacing::Base04.rems(cx))
|
||||
.justify_center()
|
||||
.child({
|
||||
let keystroke =
|
||||
match self.position(window, cx) {
|
||||
DockPosition::Left => window
|
||||
.keystroke_text_for(&workspace::ToggleLeftDock),
|
||||
DockPosition::Bottom => window
|
||||
.keystroke_text_for(&workspace::ToggleBottomDock),
|
||||
DockPosition::Right => window
|
||||
.keystroke_text_for(&workspace::ToggleRightDock),
|
||||
};
|
||||
Label::new(format!("Toggle this panel with {keystroke}"))
|
||||
}),
|
||||
)
|
||||
.child(h_flex().justify_center().child(Label::new(header)))
|
||||
.when_some(query.clone(), |panel, query| {
|
||||
panel.child(h_flex().justify_center().child(Label::new(query)))
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.pt(DynamicSpacing::Base04.rems(cx))
|
||||
.justify_center()
|
||||
.child({
|
||||
let keystroke = match self.position(window, cx) {
|
||||
DockPosition::Left => {
|
||||
window.keystroke_text_for(&workspace::ToggleLeftDock)
|
||||
}
|
||||
DockPosition::Bottom => {
|
||||
window.keystroke_text_for(&workspace::ToggleBottomDock)
|
||||
}
|
||||
DockPosition::Right => {
|
||||
window.keystroke_text_for(&workspace::ToggleRightDock)
|
||||
}
|
||||
};
|
||||
Label::new(format!("Toggle this panel with {keystroke}"))
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
let list_contents = {
|
||||
let items_len = self.cached_entries.len();
|
||||
@@ -5027,17 +4995,11 @@ fn subscribe_for_editor_events(
|
||||
}
|
||||
EditorEvent::ExcerptsExpanded { ids } => {
|
||||
outline_panel.invalidate_outlines(ids);
|
||||
let update_cached_items = outline_panel.update_non_fs_items(window, cx);
|
||||
if update_cached_items {
|
||||
outline_panel.update_cached_entries(Some(UPDATE_DEBOUNCE), window, cx);
|
||||
}
|
||||
outline_panel.update_non_fs_items(window, cx);
|
||||
}
|
||||
EditorEvent::ExcerptsEdited { ids } => {
|
||||
outline_panel.invalidate_outlines(ids);
|
||||
let update_cached_items = outline_panel.update_non_fs_items(window, cx);
|
||||
if update_cached_items {
|
||||
outline_panel.update_cached_entries(Some(UPDATE_DEBOUNCE), window, cx);
|
||||
}
|
||||
outline_panel.update_non_fs_items(window, cx);
|
||||
}
|
||||
EditorEvent::BufferFoldToggled { ids, .. } => {
|
||||
outline_panel.invalidate_outlines(ids);
|
||||
@@ -5111,10 +5073,7 @@ fn subscribe_for_editor_events(
|
||||
excerpt.invalidate_outlines();
|
||||
}
|
||||
}
|
||||
let update_cached_items = outline_panel.update_non_fs_items(window, cx);
|
||||
if update_cached_items {
|
||||
outline_panel.update_cached_entries(Some(UPDATE_DEBOUNCE), window, cx);
|
||||
}
|
||||
outline_panel.update_non_fs_items(window, cx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -5158,7 +5117,6 @@ mod tests {
|
||||
use project::FakeFs;
|
||||
use search::project_search::{self, perform_project_search};
|
||||
use serde_json::json;
|
||||
use workspace::OpenVisible;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -5215,7 +5173,7 @@ mod tests {
|
||||
});
|
||||
});
|
||||
|
||||
let all_matches = r#"/rust-analyzer/
|
||||
let all_matches = r#"/
|
||||
crates/
|
||||
ide/src/
|
||||
inlay_hints/
|
||||
@@ -5250,11 +5208,9 @@ mod tests {
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
select_first_in_all_matches(
|
||||
"search: match config.param_names_for_lifetime_elision_hints {"
|
||||
@@ -5266,11 +5222,9 @@ mod tests {
|
||||
outline_panel.select_parent(&SelectParent, window, cx);
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
select_first_in_all_matches("fn_lifetime_fn.rs")
|
||||
);
|
||||
@@ -5284,14 +5238,12 @@ mod tests {
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
format!(
|
||||
r#"/rust-analyzer/
|
||||
r#"/
|
||||
crates/
|
||||
ide/src/
|
||||
inlay_hints/
|
||||
@@ -5321,11 +5273,9 @@ mod tests {
|
||||
outline_panel.select_parent(&SelectParent, window, cx);
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
select_first_in_all_matches("inlay_hints/")
|
||||
);
|
||||
@@ -5335,11 +5285,9 @@ mod tests {
|
||||
outline_panel.select_parent(&SelectParent, window, cx);
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
select_first_in_all_matches("ide/src/")
|
||||
);
|
||||
@@ -5354,14 +5302,12 @@ mod tests {
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
format!(
|
||||
r#"/rust-analyzer/
|
||||
r#"/
|
||||
crates/
|
||||
ide/src/{SELECTED_MARKER}
|
||||
rust-analyzer/src/
|
||||
@@ -5382,11 +5328,9 @@ mod tests {
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
select_first_in_all_matches("ide/src/")
|
||||
);
|
||||
@@ -5443,7 +5387,7 @@ mod tests {
|
||||
);
|
||||
});
|
||||
});
|
||||
let all_matches = r#"/rust-analyzer/
|
||||
let all_matches = r#"/
|
||||
crates/
|
||||
ide/src/
|
||||
inlay_hints/
|
||||
@@ -5470,11 +5414,9 @@ mod tests {
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
None,
|
||||
cx,
|
||||
),
|
||||
all_matches,
|
||||
);
|
||||
@@ -5493,15 +5435,12 @@ mod tests {
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
None,
|
||||
cx,
|
||||
),
|
||||
all_matches
|
||||
.lines()
|
||||
.skip(1) // `/rust-analyzer/` is a root entry with path `` and it will be filtered out
|
||||
.filter(|item| item.contains(filter_text))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
@@ -5519,11 +5458,9 @@ mod tests {
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
None,
|
||||
cx,
|
||||
),
|
||||
all_matches,
|
||||
);
|
||||
@@ -5580,7 +5517,7 @@ mod tests {
|
||||
);
|
||||
});
|
||||
});
|
||||
let all_matches = r#"/rust-analyzer/
|
||||
let all_matches = r#"/
|
||||
crates/
|
||||
ide/src/
|
||||
inlay_hints/
|
||||
@@ -5622,11 +5559,9 @@ mod tests {
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
),
|
||||
select_first_in_all_matches(initial_outline_selection)
|
||||
);
|
||||
@@ -5645,11 +5580,9 @@ mod tests {
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
),
|
||||
select_first_in_all_matches(navigated_outline_selection)
|
||||
);
|
||||
@@ -5683,11 +5616,9 @@ mod tests {
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
),
|
||||
select_first_in_all_matches(next_navigated_outline_selection)
|
||||
);
|
||||
@@ -5720,11 +5651,9 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
),
|
||||
"fn_lifetime_fn.rs <==== selected"
|
||||
);
|
||||
@@ -5736,176 +5665,6 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multiple_workrees(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"one": {
|
||||
"a.txt": "aaa aaa"
|
||||
},
|
||||
"two": {
|
||||
"b.txt": "a aaa"
|
||||
}
|
||||
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [Path::new("/root/one")], cx).await;
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
outline_panel.set_active(true, window, cx)
|
||||
});
|
||||
|
||||
let items = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.open_paths(
|
||||
vec![PathBuf::from("/root/two")],
|
||||
OpenVisible::OnlyDirectories,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await;
|
||||
assert_eq!(items.len(), 1, "Were opening another worktree directory");
|
||||
assert!(
|
||||
items[0].is_none(),
|
||||
"Directory should be opened successfully"
|
||||
);
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(
|
||||
workspace,
|
||||
&workspace::DeploySearch::default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let search_view = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let query = "aaa";
|
||||
perform_project_search(&search_view, query, cx);
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
search_view
|
||||
.results_editor()
|
||||
.update(cx, |results_editor, cx| {
|
||||
assert_eq!(
|
||||
results_editor.display_text(cx).match_indices(query).count(),
|
||||
3
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
cx.executor()
|
||||
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
|
||||
cx.run_until_parked();
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
),
|
||||
r#"/root/one/
|
||||
a.txt
|
||||
search: aaa aaa <==== selected
|
||||
search: aaa aaa
|
||||
/root/two/
|
||||
b.txt
|
||||
search: a aaa"#
|
||||
);
|
||||
});
|
||||
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
outline_panel.select_prev(&SelectPrev, window, cx);
|
||||
outline_panel.open(&Open, window, cx);
|
||||
});
|
||||
cx.executor()
|
||||
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
|
||||
cx.run_until_parked();
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
),
|
||||
r#"/root/one/
|
||||
a.txt <==== selected
|
||||
/root/two/
|
||||
b.txt
|
||||
search: a aaa"#
|
||||
);
|
||||
});
|
||||
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
outline_panel.select_next(&SelectNext, window, cx);
|
||||
outline_panel.open(&Open, window, cx);
|
||||
});
|
||||
cx.executor()
|
||||
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
|
||||
cx.run_until_parked();
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
),
|
||||
r#"/root/one/
|
||||
a.txt
|
||||
/root/two/ <==== selected"#
|
||||
);
|
||||
});
|
||||
|
||||
outline_panel.update_in(cx, |outline_panel, window, cx| {
|
||||
outline_panel.open(&Open, window, cx);
|
||||
});
|
||||
cx.executor()
|
||||
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
|
||||
cx.run_until_parked();
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
),
|
||||
r#"/root/one/
|
||||
a.txt
|
||||
/root/two/ <==== selected
|
||||
b.txt
|
||||
search: a aaa"#
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_navigating_in_singleton(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
@@ -5971,11 +5730,9 @@ struct OutlineEntryExcerpt {
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
indoc!(
|
||||
"
|
||||
@@ -5998,11 +5755,9 @@ outline: struct OutlineEntryExcerpt
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
indoc!(
|
||||
"
|
||||
@@ -6025,11 +5780,9 @@ outline: struct OutlineEntryExcerpt <==== selected
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
indoc!(
|
||||
"
|
||||
@@ -6052,11 +5805,9 @@ outline: struct OutlineEntryExcerpt
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
indoc!(
|
||||
"
|
||||
@@ -6079,11 +5830,9 @@ outline: struct OutlineEntryExcerpt
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
indoc!(
|
||||
"
|
||||
@@ -6106,11 +5855,9 @@ outline: struct OutlineEntryExcerpt
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
indoc!(
|
||||
"
|
||||
@@ -6133,11 +5880,9 @@ outline: struct OutlineEntryExcerpt <==== selected
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
indoc!(
|
||||
"
|
||||
@@ -6160,11 +5905,9 @@ outline: struct OutlineEntryExcerpt
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
indoc!(
|
||||
"
|
||||
@@ -6187,11 +5930,9 @@ outline: struct OutlineEntryExcerpt
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
indoc!(
|
||||
"
|
||||
@@ -6214,11 +5955,9 @@ outline: struct OutlineEntryExcerpt
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
indoc!(
|
||||
"
|
||||
@@ -6241,11 +5980,9 @@ outline: struct OutlineEntryExcerpt <==== selected
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
indoc!(
|
||||
"
|
||||
@@ -6347,13 +6084,11 @@ outline: struct OutlineEntryExcerpt
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
r#"/frontend-project/
|
||||
r#"/
|
||||
public/lottie/
|
||||
syntax-tree.json
|
||||
search: { "something": "static" } <==== selected
|
||||
@@ -6384,13 +6119,11 @@ outline: struct OutlineEntryExcerpt
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
r#"/frontend-project/
|
||||
r#"/
|
||||
public/lottie/
|
||||
syntax-tree.json
|
||||
search: { "something": "static" }
|
||||
@@ -6412,13 +6145,11 @@ outline: struct OutlineEntryExcerpt
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
r#"/frontend-project/
|
||||
r#"/
|
||||
public/lottie/
|
||||
syntax-tree.json
|
||||
search: { "something": "static" }
|
||||
@@ -6444,13 +6175,11 @@ outline: struct OutlineEntryExcerpt
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
r#"/frontend-project/
|
||||
r#"/
|
||||
public/lottie/
|
||||
syntax-tree.json
|
||||
search: { "something": "static" }
|
||||
@@ -6475,13 +6204,11 @@ outline: struct OutlineEntryExcerpt
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&project,
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
cx,
|
||||
outline_panel.selected_entry()
|
||||
),
|
||||
r#"/frontend-project/
|
||||
r#"/
|
||||
public/lottie/
|
||||
syntax-tree.json
|
||||
search: { "something": "static" }
|
||||
@@ -6528,11 +6255,9 @@ outline: struct OutlineEntryExcerpt
|
||||
}
|
||||
|
||||
fn display_entries(
|
||||
project: &Entity<Project>,
|
||||
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||
cached_entries: &[CachedEntry],
|
||||
selected_entry: Option<&PanelEntry>,
|
||||
cx: &mut App,
|
||||
) -> String {
|
||||
let mut display_string = String::new();
|
||||
for entry in cached_entries {
|
||||
@@ -6547,33 +6272,15 @@ outline: struct OutlineEntryExcerpt
|
||||
FsEntry::ExternalFile(_) => {
|
||||
panic!("Did not cover external files with tests")
|
||||
}
|
||||
FsEntry::Directory(directory) => {
|
||||
match project
|
||||
.read(cx)
|
||||
.worktree_for_id(directory.worktree_id, cx)
|
||||
.and_then(|worktree| {
|
||||
if worktree.read(cx).root_entry() == Some(&directory.entry.entry) {
|
||||
Some(worktree.read(cx).abs_path())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
Some(root_path) => format!(
|
||||
"{}/{}",
|
||||
root_path.display(),
|
||||
directory.entry.path.display(),
|
||||
),
|
||||
None => format!(
|
||||
"{}/",
|
||||
directory
|
||||
.entry
|
||||
.path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
),
|
||||
}
|
||||
}
|
||||
FsEntry::Directory(directory) => format!(
|
||||
"{}/",
|
||||
directory
|
||||
.entry
|
||||
.path
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
.unwrap_or_default()
|
||||
),
|
||||
FsEntry::File(file) => file
|
||||
.entry
|
||||
.path
|
||||
|
||||
@@ -12,9 +12,6 @@ workspace = true
|
||||
path = "src/panel.rs"
|
||||
|
||||
[dependencies]
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
//! # panel
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{actions, Entity, TextStyle};
|
||||
use settings::Settings;
|
||||
use theme::ThemeSettings;
|
||||
use gpui::actions;
|
||||
use ui::{prelude::*, Tab};
|
||||
|
||||
actions!(panel, [NextPanelTab, PreviousPanelTab]);
|
||||
@@ -49,8 +46,7 @@ pub fn panel_button(label: impl Into<SharedString>) -> ui::Button {
|
||||
let id = ElementId::Name(label.clone().to_lowercase().replace(' ', "_").into());
|
||||
ui::Button::new(id, label)
|
||||
.label_size(ui::LabelSize::Small)
|
||||
// TODO: Change this once we use on_surface_bg in button_like
|
||||
.layer(ui::ElevationIndex::ModalSurface)
|
||||
.layer(ui::ElevationIndex::Surface)
|
||||
.size(ui::ButtonSize::Compact)
|
||||
}
|
||||
|
||||
@@ -61,65 +57,10 @@ pub fn panel_filled_button(label: impl Into<SharedString>) -> ui::Button {
|
||||
pub fn panel_icon_button(id: impl Into<SharedString>, icon: IconName) -> ui::IconButton {
|
||||
let id = ElementId::Name(id.into());
|
||||
ui::IconButton::new(id, icon)
|
||||
// TODO: Change this once we use on_surface_bg in button_like
|
||||
.layer(ui::ElevationIndex::ModalSurface)
|
||||
.layer(ui::ElevationIndex::Surface)
|
||||
.size(ui::ButtonSize::Compact)
|
||||
}
|
||||
|
||||
pub fn panel_filled_icon_button(id: impl Into<SharedString>, icon: IconName) -> ui::IconButton {
|
||||
panel_icon_button(id, icon).style(ui::ButtonStyle::Filled)
|
||||
}
|
||||
|
||||
pub fn panel_editor_container(_window: &mut Window, cx: &mut App) -> Div {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.gap(px(8.))
|
||||
.p_2()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
}
|
||||
|
||||
pub fn panel_editor_style(monospace: bool, window: &mut Window, cx: &mut App) -> EditorStyle {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
|
||||
let font_size = TextSize::Small.rems(cx).to_pixels(window.rem_size());
|
||||
|
||||
let (font_family, font_features, font_weight, line_height) = if monospace {
|
||||
(
|
||||
settings.buffer_font.family.clone(),
|
||||
settings.buffer_font.features.clone(),
|
||||
settings.buffer_font.weight,
|
||||
font_size * settings.buffer_line_height.value(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
settings.ui_font.family.clone(),
|
||||
settings.ui_font.features.clone(),
|
||||
settings.ui_font.weight,
|
||||
window.line_height(),
|
||||
)
|
||||
};
|
||||
|
||||
EditorStyle {
|
||||
background: cx.theme().colors().editor_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family,
|
||||
font_features,
|
||||
font_size: TextSize::Small.rems(cx).into(),
|
||||
font_weight,
|
||||
line_height: line_height.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn panel_editor_element(
|
||||
editor: &Entity<Editor>,
|
||||
monospace: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> EditorElement {
|
||||
EditorElement::new(editor, panel_editor_style(monospace, window, cx))
|
||||
}
|
||||
|
||||
@@ -145,24 +145,12 @@ pub fn settings_file() -> &'static PathBuf {
|
||||
SETTINGS_FILE.get_or_init(|| config_dir().join("settings.json"))
|
||||
}
|
||||
|
||||
/// Returns the path to the `settings_backup.json` file.
|
||||
pub fn settings_backup_file() -> &'static PathBuf {
|
||||
static SETTINGS_FILE: OnceLock<PathBuf> = OnceLock::new();
|
||||
SETTINGS_FILE.get_or_init(|| config_dir().join("settings_backup.json"))
|
||||
}
|
||||
|
||||
/// Returns the path to the `keymap.json` file.
|
||||
pub fn keymap_file() -> &'static PathBuf {
|
||||
static KEYMAP_FILE: OnceLock<PathBuf> = OnceLock::new();
|
||||
KEYMAP_FILE.get_or_init(|| config_dir().join("keymap.json"))
|
||||
}
|
||||
|
||||
/// Returns the path to the `keymap_backup.json` file.
|
||||
pub fn keymap_backup_file() -> &'static PathBuf {
|
||||
static KEYMAP_FILE: OnceLock<PathBuf> = OnceLock::new();
|
||||
KEYMAP_FILE.get_or_init(|| config_dir().join("keymap_backup.json"))
|
||||
}
|
||||
|
||||
/// Returns the path to the `tasks.json` file.
|
||||
pub fn tasks_file() -> &'static PathBuf {
|
||||
static TASKS_FILE: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
@@ -26,7 +26,6 @@ actions!(picker, [ConfirmCompletion]);
|
||||
/// ConfirmInput is an alternative editor action which - instead of selecting active picker entry - treats pickers editor input literally,
|
||||
/// performing some kind of action on it.
|
||||
#[derive(Clone, PartialEq, Deserialize, JsonSchema, Default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfirmInput {
|
||||
pub secondary: bool,
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ struct BufferDiffState {
|
||||
language: Option<Arc<Language>>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
diff_updated_futures: Vec<oneshot::Sender<()>>,
|
||||
buffer_subscription: Option<Subscription>,
|
||||
|
||||
head_text: Option<Arc<String>>,
|
||||
index_text: Option<Arc<String>>,
|
||||
@@ -1446,14 +1447,13 @@ impl BufferStore {
|
||||
this: WeakEntity<Self>,
|
||||
kind: DiffKind,
|
||||
texts: Result<DiffBasesChange>,
|
||||
buffer_entity: Entity<Buffer>,
|
||||
buffer: Entity<Buffer>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<Entity<BufferDiff>> {
|
||||
let diff_bases_change = match texts {
|
||||
Err(e) => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let buffer = buffer_entity.read(cx);
|
||||
let buffer_id = buffer.remote_id();
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
this.loading_diffs.remove(&(buffer_id, kind));
|
||||
})?;
|
||||
return Err(e);
|
||||
@@ -1462,23 +1462,26 @@ impl BufferStore {
|
||||
};
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let buffer = buffer_entity.read(cx);
|
||||
let buffer_id = buffer.remote_id();
|
||||
let language = buffer.language().cloned();
|
||||
let language_registry = buffer.language_registry();
|
||||
let text_snapshot = buffer.text_snapshot();
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
this.loading_diffs.remove(&(buffer_id, kind));
|
||||
|
||||
if let Some(OpenBuffer::Complete { diff_state, .. }) =
|
||||
this.opened_buffers.get_mut(&buffer_id)
|
||||
this.opened_buffers.get_mut(&buffer.read(cx).remote_id())
|
||||
{
|
||||
diff_state.update(cx, |diff_state, cx| {
|
||||
diff_state.language = language;
|
||||
diff_state.language_registry = language_registry;
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
diff_state.buffer_subscription.get_or_insert_with(|| {
|
||||
cx.subscribe(&buffer, |this, buffer, event, cx| match event {
|
||||
BufferEvent::LanguageChanged => {
|
||||
this.buffer_language_changed(buffer, cx)
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
});
|
||||
|
||||
let diff = cx.new(|_| BufferDiff {
|
||||
let diff = cx.new(|cx| BufferDiff {
|
||||
buffer_id,
|
||||
snapshot: BufferDiffSnapshot::new(&text_snapshot),
|
||||
snapshot: BufferDiffSnapshot::new(&buffer.read(cx).text_snapshot()),
|
||||
unstaged_diff: None,
|
||||
});
|
||||
match kind {
|
||||
@@ -1487,9 +1490,11 @@ impl BufferStore {
|
||||
let unstaged_diff = if let Some(diff) = diff_state.unstaged_diff() {
|
||||
diff
|
||||
} else {
|
||||
let unstaged_diff = cx.new(|_| BufferDiff {
|
||||
let unstaged_diff = cx.new(|cx| BufferDiff {
|
||||
buffer_id,
|
||||
snapshot: BufferDiffSnapshot::new(&text_snapshot),
|
||||
snapshot: BufferDiffSnapshot::new(
|
||||
&buffer.read(cx).text_snapshot(),
|
||||
),
|
||||
unstaged_diff: None,
|
||||
});
|
||||
diff_state.unstaged_diff = Some(unstaged_diff.downgrade());
|
||||
@@ -1503,7 +1508,8 @@ impl BufferStore {
|
||||
}
|
||||
};
|
||||
|
||||
let rx = diff_state.diff_bases_changed(text_snapshot, diff_bases_change, cx);
|
||||
let buffer = buffer.read(cx).text_snapshot();
|
||||
let rx = diff_state.diff_bases_changed(buffer, diff_bases_change, cx);
|
||||
|
||||
Ok(async move {
|
||||
rx.await.ok();
|
||||
@@ -1715,23 +1721,16 @@ impl BufferStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_buffer(&mut self, buffer_entity: Entity<Buffer>, cx: &mut Context<Self>) -> Result<()> {
|
||||
let buffer = buffer_entity.read(cx);
|
||||
let language = buffer.language().cloned();
|
||||
let language_registry = buffer.language_registry();
|
||||
let remote_id = buffer.remote_id();
|
||||
let is_remote = buffer.replica_id() != 0;
|
||||
fn add_buffer(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Result<()> {
|
||||
let remote_id = buffer.read(cx).remote_id();
|
||||
let is_remote = buffer.read(cx).replica_id() != 0;
|
||||
let open_buffer = OpenBuffer::Complete {
|
||||
buffer: buffer_entity.downgrade(),
|
||||
diff_state: cx.new(|_| BufferDiffState {
|
||||
language,
|
||||
language_registry,
|
||||
..Default::default()
|
||||
}),
|
||||
buffer: buffer.downgrade(),
|
||||
diff_state: cx.new(|_| BufferDiffState::default()),
|
||||
};
|
||||
|
||||
let handle = cx.entity().downgrade();
|
||||
buffer_entity.update(cx, move |_, cx| {
|
||||
buffer.update(cx, move |_, cx| {
|
||||
cx.on_release(move |buffer, cx| {
|
||||
handle
|
||||
.update(cx, |_, cx| {
|
||||
@@ -1748,7 +1747,7 @@ impl BufferStore {
|
||||
}
|
||||
hash_map::Entry::Occupied(mut entry) => {
|
||||
if let OpenBuffer::Operations(operations) = entry.get_mut() {
|
||||
buffer_entity.update(cx, |b, cx| b.apply_ops(operations.drain(..), cx));
|
||||
buffer.update(cx, |b, cx| b.apply_ops(operations.drain(..), cx));
|
||||
} else if entry.get().upgrade().is_some() {
|
||||
if is_remote {
|
||||
return Ok(());
|
||||
@@ -1761,8 +1760,8 @@ impl BufferStore {
|
||||
}
|
||||
}
|
||||
|
||||
cx.subscribe(&buffer_entity, Self::on_buffer_event).detach();
|
||||
cx.emit(BufferStoreEvent::BufferAdded(buffer_entity));
|
||||
cx.subscribe(&buffer, Self::on_buffer_event).detach();
|
||||
cx.emit(BufferStoreEvent::BufferAdded(buffer));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1983,16 +1982,6 @@ impl BufferStore {
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
BufferEvent::LanguageChanged => {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
if let Some(OpenBuffer::Complete { diff_state, .. }) =
|
||||
self.opened_buffers.get(&buffer_id)
|
||||
{
|
||||
diff_state.update(cx, |diff_state, cx| {
|
||||
diff_state.buffer_language_changed(buffer, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use gpui::{
|
||||
use language::{Buffer, LanguageRegistry};
|
||||
use rpc::{proto, AnyProtoClient};
|
||||
use settings::WorktreeId;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use text::BufferId;
|
||||
use util::{maybe, ResultExt};
|
||||
@@ -299,37 +299,31 @@ impl Repository {
|
||||
(self.worktree_id, self.repository_entry.work_directory_id())
|
||||
}
|
||||
|
||||
pub fn branch(&self) -> Option<Arc<str>> {
|
||||
self.repository_entry.branch()
|
||||
}
|
||||
|
||||
pub fn display_name(&self, project: &Project, cx: &App) -> SharedString {
|
||||
maybe!({
|
||||
let project_path = self.repo_path_to_project_path(&"".into())?;
|
||||
let worktree_name = project
|
||||
.worktree_for_id(project_path.worktree_id, cx)?
|
||||
.read(cx)
|
||||
.root_name();
|
||||
|
||||
let mut path = PathBuf::new();
|
||||
path = path.join(worktree_name);
|
||||
path = path.join(project_path.path);
|
||||
Some(path.to_string_lossy().to_string())
|
||||
let path = self.repo_path_to_project_path(&"".into())?;
|
||||
Some(
|
||||
project
|
||||
.absolute_path(&path, cx)?
|
||||
.file_name()?
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
.into(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| self.repository_entry.work_directory.display_name())
|
||||
.into()
|
||||
.unwrap_or("".into())
|
||||
}
|
||||
|
||||
pub fn activate(&self, cx: &mut Context<Self>) {
|
||||
pub fn activate(&self, cx: &mut App) {
|
||||
let Some(git_state) = self.git_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let entity = cx.entity();
|
||||
git_state.update(cx, |git_state, cx| {
|
||||
let Some(index) = git_state
|
||||
let Some((index, _)) = git_state
|
||||
.repositories
|
||||
.iter()
|
||||
.position(|handle| *handle == entity)
|
||||
.enumerate()
|
||||
.find(|(_, handle)| handle.read(cx).id() == self.id())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -2,15 +2,12 @@ use crate::{
|
||||
worktree_store::{WorktreeStore, WorktreeStoreEvent},
|
||||
Project, ProjectEntryId, ProjectItem, ProjectPath,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use futures::{channel::oneshot, StreamExt};
|
||||
use gpui::{
|
||||
hash, prelude::*, App, AsyncApp, Context, Entity, EventEmitter, Img, Subscription, Task,
|
||||
WeakEntity,
|
||||
hash, prelude::*, App, Context, Entity, EventEmitter, Img, Subscription, Task, WeakEntity,
|
||||
};
|
||||
pub use image::ImageFormat;
|
||||
use image::{ExtendedColorType, GenericImageView, ImageReader};
|
||||
use language::{DiskState, File};
|
||||
use rpc::{AnyProtoClient, ErrorExt as _};
|
||||
use std::ffi::OsStr;
|
||||
@@ -35,12 +32,10 @@ impl From<NonZeroU64> for ImageId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ImageItemEvent {
|
||||
ReloadNeeded,
|
||||
Reloaded,
|
||||
FileHandleChanged,
|
||||
MetadataUpdated,
|
||||
}
|
||||
|
||||
impl EventEmitter<ImageItemEvent> for ImageItem {}
|
||||
@@ -51,106 +46,14 @@ pub enum ImageStoreEvent {
|
||||
|
||||
impl EventEmitter<ImageStoreEvent> for ImageStore {}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ImageMetadata {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub file_size: u64,
|
||||
pub colors: Option<ImageColorInfo>,
|
||||
pub format: ImageFormat,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ImageColorInfo {
|
||||
pub channels: u8,
|
||||
pub bits_per_channel: u8,
|
||||
}
|
||||
|
||||
impl ImageColorInfo {
|
||||
pub fn from_color_type(color_type: impl Into<ExtendedColorType>) -> Option<Self> {
|
||||
let (channels, bits_per_channel) = match color_type.into() {
|
||||
ExtendedColorType::L8 => (1, 8),
|
||||
ExtendedColorType::L16 => (1, 16),
|
||||
ExtendedColorType::La8 => (2, 8),
|
||||
ExtendedColorType::La16 => (2, 16),
|
||||
ExtendedColorType::Rgb8 => (3, 8),
|
||||
ExtendedColorType::Rgb16 => (3, 16),
|
||||
ExtendedColorType::Rgba8 => (4, 8),
|
||||
ExtendedColorType::Rgba16 => (4, 16),
|
||||
ExtendedColorType::A8 => (1, 8),
|
||||
ExtendedColorType::Bgr8 => (3, 8),
|
||||
ExtendedColorType::Bgra8 => (4, 8),
|
||||
ExtendedColorType::Cmyk8 => (4, 8),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(Self {
|
||||
channels,
|
||||
bits_per_channel,
|
||||
})
|
||||
}
|
||||
|
||||
pub const fn bits_per_pixel(&self) -> u8 {
|
||||
self.channels * self.bits_per_channel
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImageItem {
|
||||
pub id: ImageId,
|
||||
pub file: Arc<dyn File>,
|
||||
pub image: Arc<gpui::Image>,
|
||||
reload_task: Option<Task<()>>,
|
||||
pub image_metadata: Option<ImageMetadata>,
|
||||
}
|
||||
|
||||
impl ImageItem {
|
||||
pub async fn load_image_metadata(
|
||||
image: Entity<ImageItem>,
|
||||
project: Entity<Project>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<ImageMetadata> {
|
||||
let (fs, image_path) = cx.update(|cx| {
|
||||
let project_path = image.read(cx).project_path(cx);
|
||||
|
||||
let worktree = project
|
||||
.read(cx)
|
||||
.worktree_for_id(project_path.worktree_id, cx)
|
||||
.ok_or_else(|| anyhow!("worktree not found"))?;
|
||||
let worktree_root = worktree.read(cx).abs_path();
|
||||
let image_path = image.read(cx).path();
|
||||
let image_path = if image_path.is_absolute() {
|
||||
image_path.to_path_buf()
|
||||
} else {
|
||||
worktree_root.join(image_path)
|
||||
};
|
||||
|
||||
let fs = project.read(cx).fs().clone();
|
||||
|
||||
anyhow::Ok((fs, image_path))
|
||||
})??;
|
||||
|
||||
let image_bytes = fs.load_bytes(&image_path).await?;
|
||||
let image_format = image::guess_format(&image_bytes)?;
|
||||
|
||||
let mut image_reader = ImageReader::new(std::io::Cursor::new(image_bytes));
|
||||
image_reader.set_format(image_format);
|
||||
let image = image_reader.decode()?;
|
||||
|
||||
let (width, height) = image.dimensions();
|
||||
let file_metadata = fs
|
||||
.metadata(image_path.as_path())
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("failed to load image metadata"))?;
|
||||
|
||||
Ok(ImageMetadata {
|
||||
width,
|
||||
height,
|
||||
file_size: file_metadata.len,
|
||||
format: image_format,
|
||||
colors: ImageColorInfo::from_color_type(image.color()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn project_path(&self, cx: &App) -> ProjectPath {
|
||||
ProjectPath {
|
||||
worktree_id: self.file.worktree_id(cx),
|
||||
@@ -488,7 +391,6 @@ impl ImageStoreImpl for Entity<LocalImageStore> {
|
||||
id: cx.entity_id().as_non_zero_u64().into(),
|
||||
file: file.clone(),
|
||||
image,
|
||||
image_metadata: None,
|
||||
reload_task: None,
|
||||
})?;
|
||||
|
||||
|
||||
@@ -1977,12 +1977,7 @@ impl LocalLspStore {
|
||||
Some(local) => local.abs_path(cx),
|
||||
None => return,
|
||||
};
|
||||
let file_url = lsp::Url::from_file_path(old_path.as_path()).unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"`{}` is not parseable as an URI",
|
||||
old_path.to_string_lossy()
|
||||
)
|
||||
});
|
||||
let file_url = lsp::Url::from_file_path(old_path).unwrap();
|
||||
self.unregister_buffer_from_language_servers(buffer, file_url, cx);
|
||||
}
|
||||
|
||||
|
||||
@@ -1535,10 +1535,6 @@ impl Project {
|
||||
})
|
||||
}
|
||||
|
||||
/// Renames the project entry with given `entry_id`.
|
||||
///
|
||||
/// `new_path` is a relative path to worktree root.
|
||||
/// If root entry is renamed then its new root name is used instead.
|
||||
pub fn rename_entry(
|
||||
&mut self,
|
||||
entry_id: ProjectEntryId,
|
||||
@@ -1555,18 +1551,12 @@ impl Project {
|
||||
};
|
||||
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
let is_root_entry = self.entry_is_worktree_root(entry_id, cx);
|
||||
|
||||
let lsp_store = self.lsp_store().downgrade();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let (old_abs_path, new_abs_path) = {
|
||||
let root_path = worktree.update(&mut cx, |this, _| this.abs_path())?;
|
||||
let new_abs_path = if is_root_entry {
|
||||
root_path.parent().unwrap().join(&new_path)
|
||||
} else {
|
||||
root_path.join(&new_path)
|
||||
};
|
||||
(root_path.join(&old_path), new_abs_path)
|
||||
(root_path.join(&old_path), root_path.join(&new_path))
|
||||
};
|
||||
LspStore::will_rename_entry(
|
||||
lsp_store.clone(),
|
||||
@@ -2085,25 +2075,8 @@ impl Project {
|
||||
return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
|
||||
}
|
||||
|
||||
let open_image_task = self.image_store.update(cx, |image_store, cx| {
|
||||
self.image_store.update(cx, |image_store, cx| {
|
||||
image_store.open_image(path.into(), cx)
|
||||
});
|
||||
|
||||
let weak_project = cx.entity().downgrade();
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
let image_item = open_image_task.await?;
|
||||
let project = weak_project
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("Project dropped"))?;
|
||||
|
||||
let metadata =
|
||||
ImageItem::load_image_metadata(image_item.clone(), project, &mut cx).await?;
|
||||
image_item.update(&mut cx, |image_item, cx| {
|
||||
image_item.image_metadata = Some(metadata);
|
||||
cx.emit(ImageItemEvent::MetadataUpdated);
|
||||
})?;
|
||||
|
||||
Ok(image_item)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -5776,9 +5776,6 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
|
||||
);
|
||||
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
let language = rust_lang();
|
||||
language_registry.add(language.clone());
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
@@ -5793,23 +5790,13 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
uncommitted_diff.read_with(cx, |diff, _| {
|
||||
assert_eq!(
|
||||
diff.snapshot
|
||||
.base_text
|
||||
.as_ref()
|
||||
.and_then(|base| base.language().cloned()),
|
||||
Some(language)
|
||||
)
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
uncommitted_diff.update(cx, |uncommitted_diff, cx| {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
assert_hunks(
|
||||
uncommitted_diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
|
||||
&snapshot,
|
||||
&uncommitted_diff.base_text_string().unwrap(),
|
||||
&uncommitted_diff.snapshot.base_text.as_ref().unwrap().text(),
|
||||
&[
|
||||
(0..1, "", "// print goodbye\n"),
|
||||
(
|
||||
|
||||
@@ -162,14 +162,12 @@ struct EntryDetails {
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Delete {
|
||||
#[serde(default)]
|
||||
pub skip_prompt: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Trash {
|
||||
#[serde(default)]
|
||||
pub skip_prompt: bool,
|
||||
@@ -733,9 +731,7 @@ impl ProjectPanel {
|
||||
.action("Copy Path", Box::new(CopyPath))
|
||||
.action("Copy Relative Path", Box::new(CopyRelativePath))
|
||||
.separator()
|
||||
.when(!is_root || !cfg!(target_os = "windows"), |menu| {
|
||||
menu.action("Rename", Box::new(Rename))
|
||||
})
|
||||
.action("Rename", Box::new(Rename))
|
||||
.when(!is_root & !is_remote, |menu| {
|
||||
menu.action("Trash", Box::new(Trash { skip_prompt: false }))
|
||||
})
|
||||
@@ -1350,10 +1346,6 @@ impl ProjectPanel {
|
||||
if let Some(worktree) = self.project.read(cx).worktree_for_id(worktree_id, cx) {
|
||||
let sub_entry_id = self.unflatten_entry_id(entry_id);
|
||||
if let Some(entry) = worktree.read(cx).entry_for_id(sub_entry_id) {
|
||||
#[cfg(target_os = "windows")]
|
||||
if Some(entry) == worktree.read(cx).root_entry() {
|
||||
return;
|
||||
}
|
||||
self.edit_state = Some(EditState {
|
||||
worktree_id,
|
||||
entry_id: sub_entry_id,
|
||||
@@ -1874,7 +1866,7 @@ impl ProjectPanel {
|
||||
) {
|
||||
let selection = self.find_entry(
|
||||
self.selection.as_ref(),
|
||||
false,
|
||||
true,
|
||||
|entry, worktree_id| {
|
||||
(self.selection.is_none()
|
||||
|| self.selection.is_some_and(|selection| {
|
||||
@@ -6734,286 +6726,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_select_git_entry(cx: &mut gpui::TestAppContext) {
|
||||
use git::status::{FileStatus, StatusCode, TrackedStatus};
|
||||
use std::path::Path;
|
||||
|
||||
init_test_with_editor(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"tree1": {
|
||||
".git": {},
|
||||
"dir1": {
|
||||
"modified1.txt": "",
|
||||
"unmodified1.txt": "",
|
||||
"modified2.txt": "",
|
||||
},
|
||||
"dir2": {
|
||||
"modified3.txt": "",
|
||||
"unmodified2.txt": "",
|
||||
},
|
||||
"modified4.txt": "",
|
||||
"unmodified3.txt": "",
|
||||
},
|
||||
"tree2": {
|
||||
".git": {},
|
||||
"dir3": {
|
||||
"modified5.txt": "",
|
||||
"unmodified4.txt": "",
|
||||
},
|
||||
"modified6.txt": "",
|
||||
"unmodified5.txt": "",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Mark files as git modified
|
||||
let tree1_modified_files = [
|
||||
"dir1/modified1.txt",
|
||||
"dir1/modified2.txt",
|
||||
"modified4.txt",
|
||||
"dir2/modified3.txt",
|
||||
];
|
||||
|
||||
let tree2_modified_files = ["dir3/modified5.txt", "modified6.txt"];
|
||||
|
||||
let root1_dot_git = Path::new("/root/tree1/.git");
|
||||
let root2_dot_git = Path::new("/root/tree2/.git");
|
||||
let set_value = FileStatus::Tracked(TrackedStatus {
|
||||
index_status: StatusCode::Modified,
|
||||
worktree_status: StatusCode::Modified,
|
||||
});
|
||||
|
||||
fs.with_git_state(&root1_dot_git, true, |git_repo_state| {
|
||||
for file_path in tree1_modified_files {
|
||||
git_repo_state.statuses.insert(file_path.into(), set_value);
|
||||
}
|
||||
});
|
||||
|
||||
fs.with_git_state(&root2_dot_git, true, |git_repo_state| {
|
||||
for file_path in tree2_modified_files {
|
||||
git_repo_state.statuses.insert(file_path.into(), set_value);
|
||||
}
|
||||
});
|
||||
|
||||
let project = Project::test(
|
||||
fs.clone(),
|
||||
["/root/tree1".as_ref(), "/root/tree2".as_ref()],
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
|
||||
// Check initial state
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..15, cx),
|
||||
&[
|
||||
"v tree1",
|
||||
" > .git",
|
||||
" > dir1",
|
||||
" > dir2",
|
||||
" modified4.txt",
|
||||
" unmodified3.txt",
|
||||
"v tree2",
|
||||
" > .git",
|
||||
" > dir3",
|
||||
" modified6.txt",
|
||||
" unmodified5.txt"
|
||||
],
|
||||
);
|
||||
|
||||
// Test selecting next modified entry
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_next_git_entry(&SelectNextGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..6, cx),
|
||||
&[
|
||||
"v tree1",
|
||||
" > .git",
|
||||
" v dir1",
|
||||
" modified1.txt <== selected",
|
||||
" modified2.txt",
|
||||
" unmodified1.txt",
|
||||
],
|
||||
);
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_next_git_entry(&SelectNextGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..6, cx),
|
||||
&[
|
||||
"v tree1",
|
||||
" > .git",
|
||||
" v dir1",
|
||||
" modified1.txt",
|
||||
" modified2.txt <== selected",
|
||||
" unmodified1.txt",
|
||||
],
|
||||
);
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_next_git_entry(&SelectNextGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 6..9, cx),
|
||||
&[
|
||||
" v dir2",
|
||||
" modified3.txt <== selected",
|
||||
" unmodified2.txt",
|
||||
],
|
||||
);
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_next_git_entry(&SelectNextGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 9..11, cx),
|
||||
&[" modified4.txt <== selected", " unmodified3.txt",],
|
||||
);
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_next_git_entry(&SelectNextGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 13..16, cx),
|
||||
&[
|
||||
" v dir3",
|
||||
" modified5.txt <== selected",
|
||||
" unmodified4.txt",
|
||||
],
|
||||
);
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_next_git_entry(&SelectNextGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 16..18, cx),
|
||||
&[" modified6.txt <== selected", " unmodified5.txt",],
|
||||
);
|
||||
|
||||
// Wraps around to first modified file
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_next_git_entry(&SelectNextGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..18, cx),
|
||||
&[
|
||||
"v tree1",
|
||||
" > .git",
|
||||
" v dir1",
|
||||
" modified1.txt <== selected",
|
||||
" modified2.txt",
|
||||
" unmodified1.txt",
|
||||
" v dir2",
|
||||
" modified3.txt",
|
||||
" unmodified2.txt",
|
||||
" modified4.txt",
|
||||
" unmodified3.txt",
|
||||
"v tree2",
|
||||
" > .git",
|
||||
" v dir3",
|
||||
" modified5.txt",
|
||||
" unmodified4.txt",
|
||||
" modified6.txt",
|
||||
" unmodified5.txt",
|
||||
],
|
||||
);
|
||||
|
||||
// Wraps around again to last modified file
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_prev_git_entry(&SelectPrevGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 16..18, cx),
|
||||
&[" modified6.txt <== selected", " unmodified5.txt",],
|
||||
);
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_prev_git_entry(&SelectPrevGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 13..16, cx),
|
||||
&[
|
||||
" v dir3",
|
||||
" modified5.txt <== selected",
|
||||
" unmodified4.txt",
|
||||
],
|
||||
);
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_prev_git_entry(&SelectPrevGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 9..11, cx),
|
||||
&[" modified4.txt <== selected", " unmodified3.txt",],
|
||||
);
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_prev_git_entry(&SelectPrevGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 6..9, cx),
|
||||
&[
|
||||
" v dir2",
|
||||
" modified3.txt <== selected",
|
||||
" unmodified2.txt",
|
||||
],
|
||||
);
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_prev_git_entry(&SelectPrevGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..6, cx),
|
||||
&[
|
||||
"v tree1",
|
||||
" > .git",
|
||||
" v dir1",
|
||||
" modified1.txt",
|
||||
" modified2.txt <== selected",
|
||||
" unmodified1.txt",
|
||||
],
|
||||
);
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_prev_git_entry(&SelectPrevGitEntry, window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..6, cx),
|
||||
&[
|
||||
"v tree1",
|
||||
" > .git",
|
||||
" v dir1",
|
||||
" modified1.txt <== selected",
|
||||
" modified2.txt",
|
||||
" unmodified1.txt",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_select_directory(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
@@ -7286,84 +6998,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
async fn test_rename_root_of_worktree(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root1",
|
||||
json!({
|
||||
"dir1": {
|
||||
"file1.txt": "content 1",
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
|
||||
toggle_expand_dir(&panel, "root1/dir1", cx);
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&["v root1", " v dir1 <== selected", " file1.txt",],
|
||||
"Initial state with worktrees"
|
||||
);
|
||||
|
||||
select_path(&panel, "root1", cx);
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&["v root1 <== selected", " v dir1", " file1.txt",],
|
||||
);
|
||||
|
||||
// Rename root1 to new_root1
|
||||
panel.update_in(cx, |panel, window, cx| panel.rename(&Rename, window, cx));
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&[
|
||||
"v [EDITOR: 'root1'] <== selected",
|
||||
" v dir1",
|
||||
" file1.txt",
|
||||
],
|
||||
);
|
||||
|
||||
let confirm = panel.update_in(cx, |panel, window, cx| {
|
||||
panel
|
||||
.filename_editor
|
||||
.update(cx, |editor, cx| editor.set_text("new_root1", window, cx));
|
||||
panel.confirm_edit(window, cx).unwrap()
|
||||
});
|
||||
confirm.await.unwrap();
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&[
|
||||
"v new_root1 <== selected",
|
||||
" v dir1",
|
||||
" file1.txt",
|
||||
],
|
||||
"Should update worktree name"
|
||||
);
|
||||
|
||||
// Ensure internal paths have been updated
|
||||
select_path(&panel, "new_root1/dir1/file1.txt", cx);
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&[
|
||||
"v new_root1",
|
||||
" v dir1",
|
||||
" file1.txt <== selected",
|
||||
],
|
||||
"Files in renamed worktree are selectable"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multiple_marked_entries(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
|
||||
@@ -368,7 +368,6 @@ enum ErrorCode {
|
||||
DevServerProjectPathDoesNotExist = 16;
|
||||
RemoteUpgradeRequired = 17;
|
||||
RateLimitExceeded = 18;
|
||||
CommitFailed = 19;
|
||||
reserved 6;
|
||||
reserved 14 to 15;
|
||||
}
|
||||
|
||||
@@ -79,10 +79,7 @@ fn main() {
|
||||
println!("{}", env!("ZED_PKG_VERSION"))
|
||||
}
|
||||
ReleaseChannel::Nightly | ReleaseChannel::Dev => {
|
||||
println!(
|
||||
"{}",
|
||||
option_env!("ZED_COMMIT_SHA").unwrap_or(release_channel.dev_name())
|
||||
)
|
||||
println!("{}", env!("ZED_COMMIT_SHA"))
|
||||
}
|
||||
};
|
||||
std::process::exit(0);
|
||||
|
||||
@@ -2,7 +2,6 @@ use crate::kernels::KernelSpecification;
|
||||
use crate::repl_store::ReplStore;
|
||||
use crate::KERNEL_DOCS_URL;
|
||||
|
||||
use gpui::AnyView;
|
||||
use gpui::DismissEvent;
|
||||
|
||||
use gpui::FontWeight;
|
||||
@@ -20,15 +19,10 @@ use ui::{prelude::*, ListItem, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
|
||||
type OnSelect = Box<dyn Fn(KernelSpecification, &mut Window, &mut App)>;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct KernelSelector<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
pub struct KernelSelector<T: PopoverTrigger> {
|
||||
handle: Option<PopoverMenuHandle<Picker<KernelPickerDelegate>>>,
|
||||
on_select: OnSelect,
|
||||
trigger: T,
|
||||
tooltip: TT,
|
||||
info_text: Option<SharedString>,
|
||||
worktree_id: WorktreeId,
|
||||
}
|
||||
@@ -50,17 +44,12 @@ fn truncate_path(path: &SharedString, max_length: usize) -> SharedString {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, TT> KernelSelector<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
pub fn new(on_select: OnSelect, worktree_id: WorktreeId, trigger: T, tooltip: TT) -> Self {
|
||||
impl<T: PopoverTrigger> KernelSelector<T> {
|
||||
pub fn new(on_select: OnSelect, worktree_id: WorktreeId, trigger: T) -> Self {
|
||||
KernelSelector {
|
||||
on_select,
|
||||
handle: None,
|
||||
trigger,
|
||||
tooltip,
|
||||
info_text: None,
|
||||
worktree_id,
|
||||
}
|
||||
@@ -246,11 +235,7 @@ impl PickerDelegate for KernelPickerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, TT> RenderOnce for KernelSelector<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
impl<T: PopoverTrigger> RenderOnce for KernelSelector<T> {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let store = ReplStore::global(cx).read(cx);
|
||||
|
||||
@@ -277,7 +262,7 @@ where
|
||||
|
||||
PopoverMenu::new("kernel-switcher")
|
||||
.menu(move |_window, _cx| Some(picker_view.clone()))
|
||||
.trigger_with_tooltip(self.trigger, self.tooltip)
|
||||
.trigger(self.trigger)
|
||||
.attach(gpui::Corner::BottomLeft)
|
||||
.when_some(self.handle, |menu, handle| menu.with_handle(handle))
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults};
|
||||
const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Deploy {
|
||||
#[serde(default = "util::serde::default_true")]
|
||||
pub focus: bool,
|
||||
|
||||
@@ -2197,7 +2197,7 @@ pub mod tests {
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
"/dir",
|
||||
json!({
|
||||
"one.rs": "const ONE: usize = 1;",
|
||||
"two.rs": "const TWO: usize = one::ONE + one::ONE;",
|
||||
@@ -2206,7 +2206,7 @@ pub mod tests {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
|
||||
@@ -2564,7 +2564,7 @@ pub mod tests {
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
"/dir",
|
||||
json!({
|
||||
"one.rs": "const ONE: usize = 1;",
|
||||
"two.rs": "const TWO: usize = one::ONE + one::ONE;",
|
||||
@@ -2573,7 +2573,7 @@ pub mod tests {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let workspace = window;
|
||||
let search_bar = window.build_entity(cx, |_, _| ProjectSearchBar::new());
|
||||
@@ -2859,7 +2859,7 @@ pub mod tests {
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
"/dir",
|
||||
json!({
|
||||
"a": {
|
||||
"one.rs": "const ONE: usize = 1;",
|
||||
@@ -2984,7 +2984,7 @@ pub mod tests {
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
"/dir",
|
||||
json!({
|
||||
"one.rs": "const ONE: usize = 1;",
|
||||
"two.rs": "const TWO: usize = one::ONE + one::ONE;",
|
||||
@@ -2993,7 +2993,7 @@ pub mod tests {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let search_bar = window.build_entity(cx, |_, _| ProjectSearchBar::new());
|
||||
@@ -3693,7 +3693,7 @@ pub mod tests {
|
||||
// We need many lines in the search results to be able to scroll the window
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
"/dir",
|
||||
json!({
|
||||
"1.txt": "\n\n\n\n\n A \n\n\n\n\n",
|
||||
"2.txt": "\n\n\n\n\n A \n\n\n\n\n",
|
||||
@@ -3718,7 +3718,7 @@ pub mod tests {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let search = cx.new(|cx| ProjectSearch::new(project, cx));
|
||||
|
||||
@@ -35,7 +35,6 @@ smallvec.workspace = true
|
||||
tree-sitter-json.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
util.workspace = true
|
||||
migrator.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user