Compare commits
175 Commits
missing-gi
...
update-edi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d11fc5bc4 | ||
|
|
d57f5937d4 | ||
|
|
fc7bf7bcb9 | ||
|
|
5d634245a2 | ||
|
|
21a1541a70 | ||
|
|
71867096c8 | ||
|
|
3d68dba696 | ||
|
|
f0cd71e43c | ||
|
|
a13c2baa7f | ||
|
|
7ba1492f0a | ||
|
|
0a681225b6 | ||
|
|
277fb54632 | ||
|
|
0e42a69490 | ||
|
|
b014afa938 | ||
|
|
df8adc8b11 | ||
|
|
522b8d662c | ||
|
|
5dc3c237eb | ||
|
|
c771ca49e1 | ||
|
|
ab4a6f1c79 | ||
|
|
316b97d6e3 | ||
|
|
eea6b526dc | ||
|
|
ea8da43c6b | ||
|
|
cc97f4131b | ||
|
|
ba7d2ba8c7 | ||
|
|
553cc2cca5 | ||
|
|
3f95d79fc5 | ||
|
|
51092c4e31 | ||
|
|
2b7d3726b4 | ||
|
|
1ce6e8d0e3 | ||
|
|
eb389a5132 | ||
|
|
e148815e04 | ||
|
|
fc86e7cd51 | ||
|
|
754560876b | ||
|
|
65934ae181 | ||
|
|
498bb518ff | ||
|
|
2e7a89c5e3 | ||
|
|
f5fd3d98ad | ||
|
|
9a9fdce253 | ||
|
|
cc931a8fcc | ||
|
|
148547ecd1 | ||
|
|
5293f5724c | ||
|
|
73b32a20e2 | ||
|
|
7da913c801 | ||
|
|
a2592a3a37 | ||
|
|
636253d2dc | ||
|
|
e851abd2ec | ||
|
|
b3814ce4e3 | ||
|
|
2d71733490 | ||
|
|
aab3e0495d | ||
|
|
12163c9b45 | ||
|
|
477cec0ef1 | ||
|
|
0a146793ea | ||
|
|
7378ab9ba5 | ||
|
|
759ea0ec48 | ||
|
|
7c00eec08b | ||
|
|
eaab7da2d8 | ||
|
|
14d9788ba3 | ||
|
|
b395beaf93 | ||
|
|
6e7416eb00 | ||
|
|
bbea3a2184 | ||
|
|
7950368bc2 | ||
|
|
7fe6943d89 | ||
|
|
8fa85c41a1 | ||
|
|
7b45901d96 | ||
|
|
22e2b8e832 | ||
|
|
5778e1e6f0 | ||
|
|
c61f2dff47 | ||
|
|
04d65cb3cd | ||
|
|
6a40a400bd | ||
|
|
c8c4ec21f3 | ||
|
|
64ae5093af | ||
|
|
7f4957cf20 | ||
|
|
8c349057e5 | ||
|
|
a1d4bd94c9 | ||
|
|
9e178f128d | ||
|
|
236f51cddd | ||
|
|
37785a54d5 | ||
|
|
2e7bb11b7d | ||
|
|
cf9661a56b | ||
|
|
c5fe5f1139 | ||
|
|
8f75fe25e5 | ||
|
|
a9de9e3cb4 | ||
|
|
4c881b6a12 | ||
|
|
c89ad65782 | ||
|
|
3d9f70946c | ||
|
|
7fe331f83d | ||
|
|
c1f162abc6 | ||
|
|
929c5e76b4 | ||
|
|
dab9c41799 | ||
|
|
1f288f7327 | ||
|
|
89e051d650 | ||
|
|
62bb3398ed | ||
|
|
0af048a7cf | ||
|
|
973cb916f3 | ||
|
|
63c0150cc2 | ||
|
|
78a5cf0257 | ||
|
|
43afa68dab | ||
|
|
0fd2203665 | ||
|
|
72e1947025 | ||
|
|
d9909c691d | ||
|
|
4f7200527c | ||
|
|
de8d4d00ce | ||
|
|
69d415c8d0 | ||
|
|
d292b7c96d | ||
|
|
ca4378cbaa | ||
|
|
d42322ab06 | ||
|
|
3f0288e52a | ||
|
|
d15a61a1aa | ||
|
|
e72f7b4e22 | ||
|
|
d0c4c664b0 | ||
|
|
994bea0003 | ||
|
|
6f7f0f30e2 | ||
|
|
1a133ab9d8 | ||
|
|
cf74d653bd | ||
|
|
8f1ff189cc | ||
|
|
56cfc60875 | ||
|
|
6ee447ee58 | ||
|
|
f42177a912 | ||
|
|
072d2b061a | ||
|
|
065fdcb86b | ||
|
|
e84d77e879 | ||
|
|
f1693e6129 | ||
|
|
4207b194e3 | ||
|
|
fe6d180a1a | ||
|
|
0294b19694 | ||
|
|
b1055878c7 | ||
|
|
3582fc4636 | ||
|
|
ca4e8043d4 | ||
|
|
d9183c7669 | ||
|
|
7bddb390ca | ||
|
|
146b9c232c | ||
|
|
be26acccca | ||
|
|
4be89ea60f | ||
|
|
e17e838c07 | ||
|
|
ed5656813c | ||
|
|
c4bcff1e87 | ||
|
|
07f1b612cf | ||
|
|
9e5bc81f1c | ||
|
|
7148092e12 | ||
|
|
ead5a836a1 | ||
|
|
3be8066415 | ||
|
|
f6e396837c | ||
|
|
fd7fa87939 | ||
|
|
a7a14e59bf | ||
|
|
8ff8dbdb2b | ||
|
|
44c6a54f95 | ||
|
|
f0565b4e2e | ||
|
|
a6e15dda4b | ||
|
|
144487bf1a | ||
|
|
2d57e43e34 | ||
|
|
c484374b2f | ||
|
|
8114d17cba | ||
|
|
c7cd5b019b | ||
|
|
07929229ae | ||
|
|
00c2a30059 | ||
|
|
a1544f47ad | ||
|
|
4f65cfa93d | ||
|
|
b6b06cf6d8 | ||
|
|
f700268029 | ||
|
|
92c21a2814 | ||
|
|
1f9d02607b | ||
|
|
6534e0bafd | ||
|
|
5ffacb9ca5 | ||
|
|
864c1ff00c | ||
|
|
35ef269233 | ||
|
|
d97adfc540 | ||
|
|
a42e040660 | ||
|
|
8646d37c0c | ||
|
|
d83c316e6d | ||
|
|
c5913899d9 | ||
|
|
e689c8c01b | ||
|
|
888a2df3f0 | ||
|
|
d6d0d7d3e4 | ||
|
|
a1ed1a00b3 | ||
|
|
a190f42ccc |
8
.github/workflows/deploy_cloudflare.yml
vendored
8
.github/workflows/deploy_cloudflare.yml
vendored
@@ -37,28 +37,28 @@ jobs:
|
||||
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
||||
|
||||
- name: Deploy Docs
|
||||
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
|
||||
uses: cloudflare/wrangler-action@392082e81ffbcb9ebdde27400634aa004b35ea37 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: pages deploy target/deploy --project-name=docs
|
||||
|
||||
- name: Deploy Install
|
||||
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
|
||||
uses: cloudflare/wrangler-action@392082e81ffbcb9ebdde27400634aa004b35ea37 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
|
||||
|
||||
- name: Deploy Docs Workers
|
||||
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
|
||||
uses: cloudflare/wrangler-action@392082e81ffbcb9ebdde27400634aa004b35ea37 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: deploy .cloudflare/docs-proxy/src/worker.js
|
||||
|
||||
- name: Deploy Install Workers
|
||||
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
|
||||
uses: cloudflare/wrangler-action@392082e81ffbcb9ebdde27400634aa004b35ea37 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
|
||||
6
.github/workflows/docs.yml
vendored
6
.github/workflows/docs.yml
vendored
@@ -24,11 +24,13 @@ jobs:
|
||||
- name: Prettier Check on /docs
|
||||
working-directory: ./docs
|
||||
run: |
|
||||
pnpm dlx prettier . --check || {
|
||||
pnpm dlx prettier@${PRETTIER_VERSION} . --check || {
|
||||
echo "To fix, run from the root of the zed repo:"
|
||||
echo " cd docs && pnpm dlx prettier . --write && cd .."
|
||||
echo " cd docs && pnpm dlx prettier@${PRETTIER_VERSION} . --write && cd .."
|
||||
false
|
||||
}
|
||||
env:
|
||||
PRETTIER_VERSION: 3.5.0
|
||||
|
||||
- name: Check for Typos with Typos-CLI
|
||||
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6
|
||||
|
||||
47
.gitignore
vendored
47
.gitignore
vendored
@@ -1,36 +1,35 @@
|
||||
/.direnv
|
||||
.envrc
|
||||
.idea
|
||||
**/target
|
||||
**/*.db
|
||||
**/cargo-target
|
||||
/zed.xcworkspace
|
||||
.DS_Store
|
||||
/plugins/bin
|
||||
/script/node_modules
|
||||
/crates/theme/schemas/theme.json
|
||||
/crates/collab/seed.json
|
||||
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
|
||||
/dev.zed.Zed*.json
|
||||
/assets/*licenses.*
|
||||
**/target
|
||||
**/venv
|
||||
.build
|
||||
*.wasm
|
||||
Packages
|
||||
*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.DS_Store
|
||||
.blob_store
|
||||
.build
|
||||
.envrc
|
||||
.flatpak-builder
|
||||
.idea
|
||||
.netrc
|
||||
.pytest_cache
|
||||
.swiftpm
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
.swiftpm
|
||||
**/*.db
|
||||
.pytest_cache
|
||||
.venv
|
||||
.blob_store
|
||||
.vscode
|
||||
.wrangler
|
||||
.flatpak-builder
|
||||
.envrc
|
||||
/.direnv
|
||||
/assets/*licenses.*
|
||||
/crates/collab/seed.json
|
||||
/crates/theme/schemas/theme.json
|
||||
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
|
||||
/dev.zed.Zed*.json
|
||||
/plugins/bin
|
||||
/script/node_modules
|
||||
/zed.xcworkspace
|
||||
DerivedData/
|
||||
Packages
|
||||
xcuserdata/
|
||||
|
||||
# Don't commit any secrets to the repo.
|
||||
.env.secret.toml
|
||||
|
||||
420
Cargo.lock
generated
420
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
31
Cargo.toml
31
Cargo.toml
@@ -26,13 +26,15 @@ members = [
|
||||
"crates/collections",
|
||||
"crates/command_palette",
|
||||
"crates/command_palette_hooks",
|
||||
"crates/component",
|
||||
"crates/component_preview",
|
||||
"crates/context_server",
|
||||
"crates/context_server_settings",
|
||||
"crates/copilot",
|
||||
"crates/db",
|
||||
"crates/deepseek",
|
||||
"crates/diagnostics",
|
||||
"crates/diff",
|
||||
"crates/buffer_diff",
|
||||
"crates/docs_preprocessor",
|
||||
"crates/editor",
|
||||
"crates/evals",
|
||||
@@ -81,6 +83,7 @@ members = [
|
||||
"crates/markdown_preview",
|
||||
"crates/media",
|
||||
"crates/menu",
|
||||
"crates/migrator",
|
||||
"crates/multi_buffer",
|
||||
"crates/node_runtime",
|
||||
"crates/notifications",
|
||||
@@ -146,7 +149,6 @@ members = [
|
||||
"crates/ui_macros",
|
||||
"crates/util",
|
||||
"crates/util_macros",
|
||||
"crates/vcs_menu",
|
||||
"crates/vim",
|
||||
"crates/vim_mode_setting",
|
||||
"crates/welcome",
|
||||
@@ -169,7 +171,6 @@ members = [
|
||||
"extensions/haskell",
|
||||
"extensions/html",
|
||||
"extensions/lua",
|
||||
"extensions/php",
|
||||
"extensions/perplexity",
|
||||
"extensions/proto",
|
||||
"extensions/purescript",
|
||||
@@ -226,13 +227,15 @@ 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" }
|
||||
db = { path = "crates/db" }
|
||||
deepseek = { path = "crates/deepseek" }
|
||||
diagnostics = { path = "crates/diagnostics" }
|
||||
diff = { path = "crates/diff" }
|
||||
buffer_diff = { path = "crates/buffer_diff" }
|
||||
editor = { path = "crates/editor" }
|
||||
extension = { path = "crates/extension" }
|
||||
extension_host = { path = "crates/extension_host" }
|
||||
@@ -279,6 +282,7 @@ 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" }
|
||||
@@ -344,7 +348,6 @@ 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" }
|
||||
@@ -419,6 +422,7 @@ ignore = "0.4.22"
|
||||
image = "0.25.1"
|
||||
indexmap = { version = "2.7.0", features = ["serde"] }
|
||||
indoc = "2"
|
||||
inventory = "0.3.19"
|
||||
itertools = "0.14.0"
|
||||
jsonwebtoken = "9.3"
|
||||
jupyter-protocol = { version = "0.6.0" }
|
||||
@@ -426,6 +430,7 @@ 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",
|
||||
@@ -477,7 +482,7 @@ rustc-demangle = "0.1.23"
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
rustc-hash = "2.1.0"
|
||||
rustls = { version = "0.23.22" }
|
||||
rustls-native-certs = "0.8.0"
|
||||
rustls-platform-verifier = "0.5.0"
|
||||
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
|
||||
semver = "1.0"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
@@ -497,6 +502,7 @@ simplelog = "0.12.2"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = "2.0"
|
||||
sqlformat = "0.2"
|
||||
streaming-iterator = "0.1"
|
||||
strsim = "0.11"
|
||||
strum = { version = "0.26.0", features = ["derive"] }
|
||||
subtle = "2.5.0"
|
||||
@@ -518,7 +524,7 @@ tiny_http = "0.8"
|
||||
toml = "0.8"
|
||||
tokio = { version = "1" }
|
||||
tower-http = "0.4.4"
|
||||
tree-sitter = { version = "0.23", features = ["wasm"] }
|
||||
tree-sitter = { version = "0.24", features = ["wasm"] }
|
||||
tree-sitter-bash = "0.23"
|
||||
tree-sitter-c = "0.23"
|
||||
tree-sitter-cpp = "0.23"
|
||||
@@ -547,19 +553,19 @@ unicode-segmentation = "1.10"
|
||||
unicode-script = "0.5.7"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
|
||||
wasmparser = "0.215"
|
||||
wasm-encoder = "0.215"
|
||||
wasmtime = { version = "24", default-features = false, features = [
|
||||
wasmparser = "0.217"
|
||||
wasm-encoder = "0.217"
|
||||
wasmtime = { version = "25", default-features = false, features = [
|
||||
"async",
|
||||
"demangle",
|
||||
"runtime",
|
||||
"cranelift",
|
||||
"component-model",
|
||||
] }
|
||||
wasmtime-wasi = "24"
|
||||
wasmtime-wasi = "25"
|
||||
which = "6.0.0"
|
||||
wit-component = "0.201"
|
||||
zed_llm_client = "0.2"
|
||||
zed_llm_client = "0.4"
|
||||
zstd = "0.11"
|
||||
metal = "0.31"
|
||||
|
||||
@@ -674,7 +680,6 @@ 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]
|
||||
|
||||
@@ -18,18 +18,23 @@
|
||||
"bash_logout": "terminal",
|
||||
"bash_profile": "terminal",
|
||||
"bashrc": "terminal",
|
||||
"bicep": "bicep",
|
||||
"bmp": "image",
|
||||
"c": "c",
|
||||
"c++": "cpp",
|
||||
"cc": "cpp",
|
||||
"cjs": "javascript",
|
||||
"cjsx": "react",
|
||||
"coffee": "coffeescript",
|
||||
"conf": "settings",
|
||||
"cpp": "cpp",
|
||||
"cs": "csharp",
|
||||
"css": "css",
|
||||
"csv": "storage",
|
||||
"cxx": "cpp",
|
||||
"cts": "typescript",
|
||||
"ctsx": "react",
|
||||
"cue": "cue",
|
||||
"dart": "dart",
|
||||
"dat": "storage",
|
||||
"db": "storage",
|
||||
@@ -64,6 +69,7 @@
|
||||
"gitattributes": "vcs",
|
||||
"gitignore": "vcs",
|
||||
"gitkeep": "vcs",
|
||||
"gitlab-ci.yml": "gitlab",
|
||||
"gitmodules": "vcs",
|
||||
"TAG_EDITMSG": "vcs",
|
||||
"MERGE_MSG": "vcs",
|
||||
@@ -101,7 +107,7 @@
|
||||
"jpeg": "image",
|
||||
"jpg": "image",
|
||||
"js": "javascript",
|
||||
"json": "storage",
|
||||
"json": "json",
|
||||
"jsonc": "storage",
|
||||
"jsx": "react",
|
||||
"jxl": "image",
|
||||
@@ -111,16 +117,18 @@
|
||||
"lockb": "bun",
|
||||
"log": "log",
|
||||
"lua": "lua",
|
||||
"luau": "luau",
|
||||
"m4a": "audio",
|
||||
"m4v": "video",
|
||||
"markdown": "document",
|
||||
"md": "document",
|
||||
"markdown": "markdown",
|
||||
"md": "markdown",
|
||||
"mdb": "storage",
|
||||
"mdf": "storage",
|
||||
"mdx": "document",
|
||||
"metadata": "code",
|
||||
"metal": "metal",
|
||||
"mjs": "javascript",
|
||||
"mjsx": "react",
|
||||
"mka": "audio",
|
||||
"mkv": "video",
|
||||
"ml": "ocaml",
|
||||
@@ -130,6 +138,7 @@
|
||||
"mp3": "audio",
|
||||
"mp4": "video",
|
||||
"mts": "typescript",
|
||||
"mtsx": "react",
|
||||
"myd": "storage",
|
||||
"myi": "storage",
|
||||
"nim": "nim",
|
||||
@@ -150,8 +159,19 @@
|
||||
"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",
|
||||
@@ -173,9 +193,21 @@
|
||||
"scss": "sass",
|
||||
"sdf": "storage",
|
||||
"sh": "terminal",
|
||||
"sol": "solidity",
|
||||
"sql": "storage",
|
||||
"sqlite": "storage",
|
||||
"svelte": "template",
|
||||
"stylelint.config.cjs": "stylelint",
|
||||
"stylelint.config.js": "stylelint",
|
||||
"stylelint.config.mjs": "stylelint",
|
||||
"stylelintignore": "stylelint",
|
||||
"stylelintrc": "stylelint",
|
||||
"stylelintrc.cjs": "stylelint",
|
||||
"stylelintrc.js": "stylelint",
|
||||
"stylelintrc.json": "stylelint",
|
||||
"stylelintrc.mjs": "stylelint",
|
||||
"stylelintrc.yaml": "stylelint",
|
||||
"stylelintrc.yml": "stylelint",
|
||||
"svelte": "svelte",
|
||||
"svg": "image",
|
||||
"swift": "swift",
|
||||
"tcl": "tcl",
|
||||
|
||||
6
assets/icons/lock_outlined.svg
Normal file
6
assets/icons/lock_outlined.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="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>
|
||||
|
After Width: | Height: | Size: 452 B |
@@ -1,6 +1,6 @@
|
||||
<svg width="440" height="128" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="550" height="128" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="tilePattern" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<pattern id="tilePattern" width="23" height="23" patternUnits="userSpaceOnUse">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 5L14 8L12 11" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M10 6.5L11 8L10 9.5" stroke="black" stroke-width="1.5"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 971 B After Width: | Height: | Size: 971 B |
5
assets/icons/zed_predict_down.svg
Normal file
5
assets/icons/zed_predict_down.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 9.33333L14.5 9.66667L12.5 11L10.5 9.66667L10 9.33333" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M12.5 11V4.5" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 375 B |
5
assets/icons/zed_predict_up.svg
Normal file
5
assets/icons/zed_predict_up.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 6.66667L10.5 6.33333L12.5 5L14.5 6.33333L15 6.66667" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M12.5 11V5" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 372 B |
@@ -31,8 +31,8 @@
|
||||
"ctrl-,": "zed::OpenSettings",
|
||||
"ctrl-q": "zed::Quit",
|
||||
"f11": "zed::ToggleFullScreen",
|
||||
"ctrl-alt-z": "zeta::RateCompletions",
|
||||
"ctrl-shift-i": "inline_completion::ToggleMenu"
|
||||
"ctrl-alt-z": "edit_prediction::RateCompletions",
|
||||
"ctrl-shift-i": "edit_prediction::ToggleMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -122,7 +122,8 @@
|
||||
"ctrl-i": "editor::ShowSignatureHelp",
|
||||
"alt-g b": "editor::ToggleGitBlame",
|
||||
"menu": "editor::OpenContextMenu",
|
||||
"shift-f10": "editor::OpenContextMenu"
|
||||
"shift-f10": "editor::OpenContextMenu",
|
||||
"ctrl-shift-e": "editor::ToggleEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -145,17 +146,17 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full && inline_completion",
|
||||
"context": "Editor && mode == full && edit_prediction",
|
||||
"bindings": {
|
||||
"alt-]": "editor::NextInlineCompletion",
|
||||
"alt-[": "editor::PreviousInlineCompletion",
|
||||
"alt-right": "editor::AcceptPartialInlineCompletion"
|
||||
"alt-]": "editor::NextEditPrediction",
|
||||
"alt-[": "editor::PreviousEditPrediction",
|
||||
"alt-right": "editor::AcceptPartialEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && !inline_completion",
|
||||
"context": "Editor && !edit_prediction",
|
||||
"bindings": {
|
||||
"alt-\\": "editor::ShowInlineCompletion"
|
||||
"alt-\\": "editor::ShowEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -274,8 +275,8 @@
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"ctrl-f4": "pane::CloseActiveItem",
|
||||
"ctrl-w": "pane::CloseActiveItem",
|
||||
"ctrl-f4": ["pane::CloseActiveItem", { "close_pinned": false }],
|
||||
"ctrl-w": ["pane::CloseActiveItem", { "close_pinned": false }],
|
||||
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
|
||||
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
|
||||
@@ -348,15 +349,15 @@
|
||||
"ctrl-k ctrl-l": "editor::ToggleFold",
|
||||
"ctrl-k ctrl-[": "editor::FoldRecursive",
|
||||
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
|
||||
"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-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-0": "editor::FoldAll",
|
||||
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
@@ -432,14 +433,14 @@
|
||||
"ctrl-alt-s": "workspace::SaveAll",
|
||||
"ctrl-k m": "language_selector::Toggle",
|
||||
"escape": "workspace::Unfollow",
|
||||
"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-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-shift-x": "zed::Extensions",
|
||||
"ctrl-shift-r": "task::Rerun",
|
||||
"ctrl-alt-r": "task::Rerun",
|
||||
@@ -453,8 +454,8 @@
|
||||
{
|
||||
"context": "ApplicationMenu",
|
||||
"bindings": {
|
||||
"left": ["app_menu::NavigateApplicationMenuInDirection", "Left"],
|
||||
"right": ["app_menu::NavigateApplicationMenuInDirection", "Right"]
|
||||
"left": "app_menu::ActivateMenuLeft",
|
||||
"right": "app_menu::ActivateMenuRight"
|
||||
}
|
||||
},
|
||||
// Bindings from Sublime Text
|
||||
@@ -496,24 +497,27 @@
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_completions",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCompletion",
|
||||
"tab": "editor::ComposeCompletion"
|
||||
}
|
||||
},
|
||||
// Bindings for accepting edit predictions
|
||||
//
|
||||
// alt-l is provided as an alternative to tab/alt-tab. and will be displayed in the UI. This is
|
||||
// because alt-tab may not be available, as it is often used for window switching.
|
||||
{
|
||||
"context": "Editor && inline_completion",
|
||||
"context": "Editor && edit_prediction",
|
||||
"bindings": {
|
||||
// Changing the modifier currently breaks accepting while you also an LSP completions menu open
|
||||
"alt-enter": "editor::AcceptInlineCompletion"
|
||||
"alt-tab": "editor::AcceptEditPrediction",
|
||||
"alt-l": "editor::AcceptEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && inline_completion && !inline_completion_requires_modifier",
|
||||
"use_key_equivalents": true,
|
||||
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
|
||||
"bindings": {
|
||||
"tab": "editor::AcceptInlineCompletion"
|
||||
"tab": "editor::AcceptEditPrediction",
|
||||
"alt-l": "editor::AcceptEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -537,8 +541,7 @@
|
||||
{
|
||||
"bindings": {
|
||||
"ctrl-alt-shift-f": "workspace::FollowNextCollaborator",
|
||||
"ctrl-alt-i": "zed::DebugElements",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
"ctrl-alt-i": "zed::DebugElements"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -556,7 +559,8 @@
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-f8": "editor::GoToHunk",
|
||||
"ctrl-shift-f8": "editor::GoToPrevHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist"
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -602,14 +606,12 @@
|
||||
},
|
||||
{
|
||||
"context": "MessageEditor > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "assistant2::Chat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ContextStrip",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "assistant2::FocusUp",
|
||||
"right": "assistant2::FocusRight",
|
||||
@@ -702,30 +704,32 @@
|
||||
},
|
||||
{
|
||||
"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"
|
||||
"ctrl-shift-space": "git::UnstageAll",
|
||||
"tab": "git_panel::FocusEditor",
|
||||
"shift-tab": "git_panel::FocusEditor",
|
||||
"escape": "git_panel::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && CommitEditor > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"context": "GitPanel > Editor",
|
||||
"bindings": {
|
||||
"escape": "git_panel::FocusChanges",
|
||||
"ctrl-enter": "git::CommitChanges",
|
||||
"ctrl-shift-enter": "git::CommitAllChanges"
|
||||
"ctrl-enter": "git::Commit",
|
||||
"tab": "git_panel::FocusChanges",
|
||||
"shift-tab": "git_panel::FocusChanges",
|
||||
"alt-up": "git_panel::FocusChanges"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -833,7 +837,6 @@
|
||||
},
|
||||
{
|
||||
"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-shift-z": "zeta::RateCompletions",
|
||||
"ctrl-shift-i": "inline_completion::ToggleMenu"
|
||||
"ctrl-cmd-z": "edit_prediction::RateCompletions",
|
||||
"ctrl-cmd-i": "edit_prediction::ToggleMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -132,7 +132,8 @@
|
||||
"cmd-alt-g b": "editor::ToggleGitBlame",
|
||||
"cmd-i": "editor::ShowSignatureHelp",
|
||||
"ctrl-f12": "editor::GoToDeclaration",
|
||||
"alt-ctrl-f12": "editor::GoToDeclarationSplit"
|
||||
"alt-ctrl-f12": "editor::GoToDeclarationSplit",
|
||||
"ctrl-cmd-e": "editor::ToggleEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -155,19 +156,19 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full && inline_completion",
|
||||
"context": "Editor && mode == full && edit_prediction",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-tab": "editor::NextInlineCompletion",
|
||||
"alt-shift-tab": "editor::PreviousInlineCompletion",
|
||||
"ctrl-cmd-right": "editor::AcceptPartialInlineCompletion"
|
||||
"alt-tab": "editor::NextEditPrediction",
|
||||
"alt-shift-tab": "editor::PreviousEditPrediction",
|
||||
"ctrl-cmd-right": "editor::AcceptPartialEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && !inline_completion",
|
||||
"context": "Editor && !edit_prediction",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-tab": "editor::ShowInlineCompletion"
|
||||
"alt-tab": "editor::ShowEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -349,7 +350,7 @@
|
||||
"cmd-}": "pane::ActivateNextItem",
|
||||
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"cmd-w": "pane::CloseActiveItem",
|
||||
"cmd-w": ["pane::CloseActiveItem", { "close_pinned": false }],
|
||||
"alt-cmd-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
|
||||
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
|
||||
"cmd-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
|
||||
@@ -413,15 +414,15 @@
|
||||
"cmd-k cmd-l": "editor::ToggleFold",
|
||||
"cmd-k cmd-[": "editor::FoldRecursive",
|
||||
"cmd-k cmd-]": "editor::UnfoldRecursive",
|
||||
"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-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-0": "editor::FoldAll",
|
||||
"cmd-k cmd-j": "editor::UnfoldAll",
|
||||
// Using `ctrl-space` in Zed requires disabling the macOS global shortcut.
|
||||
@@ -509,14 +510,14 @@
|
||||
"cmd-alt-s": "workspace::SaveAll",
|
||||
"cmd-k m": "language_selector::Toggle",
|
||||
"escape": "workspace::Unfollow",
|
||||
"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-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-shift-x": "zed::Extensions"
|
||||
}
|
||||
},
|
||||
@@ -580,17 +581,16 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && inline_completion",
|
||||
"context": "Editor && edit_prediction",
|
||||
"bindings": {
|
||||
// Changing the modifier currently breaks accepting while you also an LSP completions menu open
|
||||
"alt-tab": "editor::AcceptInlineCompletion"
|
||||
"alt-tab": "editor::AcceptEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && inline_completion && !inline_completion_requires_modifier",
|
||||
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"tab": "editor::AcceptInlineCompletion"
|
||||
"tab": "editor::AcceptEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -619,8 +619,7 @@
|
||||
"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",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
"cmd-alt-i": "zed::DebugElements"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -633,7 +632,8 @@
|
||||
"cmd-shift-e": "pane::RevealInProjectPanel",
|
||||
"cmd-f8": "editor::GoToHunk",
|
||||
"cmd-shift-f8": "editor::GoToPrevHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist"
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -715,13 +715,6 @@
|
||||
"space": "project_panel::Open"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && !CommitEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "git_panel::Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && ChangesList",
|
||||
"use_key_equivalents": true,
|
||||
@@ -734,17 +727,21 @@
|
||||
"space": "git::ToggleStaged",
|
||||
"cmd-shift-space": "git::StageAll",
|
||||
"ctrl-shift-space": "git::UnstageAll",
|
||||
"alt-down": "git_panel::FocusEditor"
|
||||
"alt-down": "git_panel::FocusEditor",
|
||||
"tab": "git_panel::FocusEditor",
|
||||
"shift-tab": "git_panel::FocusEditor",
|
||||
"escape": "git_panel::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && CommitEditor > Editor",
|
||||
"context": "GitPanel > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-up": "git_panel::FocusChanges",
|
||||
"escape": "git_panel::FocusChanges",
|
||||
"cmd-enter": "git::CommitChanges",
|
||||
"cmd-alt-enter": "git::CommitAllChanges"
|
||||
"enter": "editor::Newline",
|
||||
"cmd-enter": "git::Commit",
|
||||
"tab": "git_panel::FocusChanges",
|
||||
"shift-tab": "git_panel::FocusChanges",
|
||||
"alt-up": "git_panel::FocusChanges"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
{
|
||||
"context": "VimControl && !menu",
|
||||
"bindings": {
|
||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||
"i": ["vim::PushObject", { "around": false }],
|
||||
"a": ["vim::PushObject", { "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", { "ignorePunctuation": true }],
|
||||
"shift-e": ["vim::NextWordEnd", { "ignorePunctuation": true }],
|
||||
"shift-b": ["vim::PreviousWordStart", { "ignorePunctuation": true }],
|
||||
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
|
||||
"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 }],
|
||||
"/": "vim::Search",
|
||||
"g /": "pane::DeploySearch",
|
||||
"?": ["vim::Search", { "backwards": true }],
|
||||
@@ -70,20 +70,20 @@
|
||||
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
|
||||
"] )": ["vim::UnmatchedForward", { "char": ")" }],
|
||||
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
|
||||
"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 } }],
|
||||
"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 }],
|
||||
";": "vim::RepeatFind",
|
||||
",": "vim::RepeatFindReversed",
|
||||
"ctrl-o": "pane::GoBack",
|
||||
"ctrl-i": "pane::GoForward",
|
||||
"ctrl-]": "editor::GoToDefinition",
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||
"escape": "vim::SwitchToNormalMode",
|
||||
"ctrl-[": "vim::SwitchToNormalMode",
|
||||
"v": "vim::ToggleVisual",
|
||||
"shift-v": "vim::ToggleVisualLine",
|
||||
"ctrl-g": "vim::ShowLocation",
|
||||
@@ -102,6 +102,7 @@
|
||||
"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",
|
||||
@@ -124,17 +125,17 @@
|
||||
"g .": "editor::ToggleCodeActions", // zed specific
|
||||
"g shift-a": "editor::FindAllReferences", // zed specific
|
||||
"g space": "editor::OpenExcerpts", // zed specific
|
||||
"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 *": ["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 v": "vim::RestoreVisualSelection",
|
||||
"g ]": "editor::GoToDiagnostic",
|
||||
"g [": "editor::GoToPrevDiagnostic",
|
||||
@@ -146,7 +147,7 @@
|
||||
"shift-l": "vim::WindowBottom",
|
||||
"q": "vim::ToggleRecord",
|
||||
"shift-q": "vim::ReplayLastRecording",
|
||||
"@": ["vim::PushOperator", "ReplayRegister"],
|
||||
"@": "vim::PushReplayRegister",
|
||||
// z commands
|
||||
"z enter": ["workspace::SendKeystrokes", "z t ^"],
|
||||
"z -": ["workspace::SendKeystrokes", "z b ^"],
|
||||
@@ -165,8 +166,8 @@
|
||||
"z f": "editor::FoldSelectedRanges",
|
||||
"z shift-m": "editor::FoldAll",
|
||||
"z shift-r": "editor::UnfoldAll",
|
||||
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
|
||||
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
|
||||
"shift-z shift-q": ["pane::CloseActiveItem", { "save_intent": "skip" }],
|
||||
"shift-z shift-z": ["pane::CloseActiveItem", { "save_intent": "save_all" }],
|
||||
// Count support
|
||||
"1": ["vim::Number", 1],
|
||||
"2": ["vim::Number", 2],
|
||||
@@ -193,13 +194,13 @@
|
||||
"escape": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
".": "vim::Repeat",
|
||||
"c": ["vim::PushOperator", "Change"],
|
||||
"c": "vim::PushChange",
|
||||
"shift-c": "vim::ChangeToEndOfLine",
|
||||
"d": ["vim::PushOperator", "Delete"],
|
||||
"d": "vim::PushDelete",
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"g shift-j": "vim::JoinLinesNoWhitespace",
|
||||
"y": ["vim::PushOperator", "Yank"],
|
||||
"y": "vim::PushYank",
|
||||
"shift-y": "vim::YankLine",
|
||||
"i": "vim::InsertBefore",
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
@@ -216,19 +217,19 @@
|
||||
"shift-p": ["vim::Paste", { "before": true }],
|
||||
"u": "vim::Undo",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"r": "vim::PushReplace",
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
">": ["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"],
|
||||
">": "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",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"insert": "vim::InsertBefore",
|
||||
@@ -239,7 +240,7 @@
|
||||
"[ d": "editor::GoToPrevDiagnostic",
|
||||
"] c": "editor::GoToHunk",
|
||||
"[ c": "editor::GoToPrevHunk",
|
||||
"g c": ["vim::PushOperator", "ToggleComments"]
|
||||
"g c": "vim::PushToggleComments"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -264,14 +265,14 @@
|
||||
"y": "vim::VisualYank",
|
||||
"shift-y": "vim::VisualYankLine",
|
||||
"p": "vim::Paste",
|
||||
"shift-p": ["vim::Paste", { "preserveClipboard": true }],
|
||||
"shift-p": ["vim::Paste", { "preserve_clipboard": true }],
|
||||
"c": "vim::Substitute",
|
||||
"s": "vim::Substitute",
|
||||
"shift-r": "vim::SubstituteLine",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
"~": "vim::ChangeCase",
|
||||
"*": ["vim::MoveToNext", { "partialWord": true }],
|
||||
"#": ["vim::MoveToPrev", { "partialWord": true }],
|
||||
"*": ["vim::MoveToNext", { "partial_word": true }],
|
||||
"#": ["vim::MoveToPrev", { "partial_word": true }],
|
||||
"ctrl-a": "vim::Increment",
|
||||
"ctrl-x": "vim::Decrement",
|
||||
"g ctrl-a": ["vim::Increment", { "step": true }],
|
||||
@@ -282,19 +283,19 @@
|
||||
"g shift-a": "vim::VisualInsertEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"g shift-j": "vim::JoinLinesNoWhitespace",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
"r": "vim::PushReplace",
|
||||
"ctrl-c": "vim::SwitchToNormalMode",
|
||||
"ctrl-[": "vim::SwitchToNormalMode",
|
||||
"escape": "vim::SwitchToNormalMode",
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"=": "vim::AutoIndent",
|
||||
"!": "vim::ShellCommand",
|
||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||
"i": ["vim::PushObject", { "around": false }],
|
||||
"a": ["vim::PushObject", { "around": true }],
|
||||
"g c": "vim::ToggleComments",
|
||||
"g q": "vim::Rewrap",
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
"\"": "vim::PushRegister",
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode"
|
||||
@@ -309,19 +310,19 @@
|
||||
"ctrl-x": null,
|
||||
"ctrl-x ctrl-o": "editor::ShowCompletions",
|
||||
"ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
|
||||
"ctrl-x ctrl-c": "editor::ShowInlineCompletion", // zed specific
|
||||
"ctrl-x ctrl-c": "editor::ShowEditPrediction", // 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::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-k": ["vim::PushDigraph", {}],
|
||||
"ctrl-v": ["vim::PushLiteral", {}],
|
||||
"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::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-r": ["vim::PushOperator", "Register"],
|
||||
"ctrl-q": ["vim::PushLiteral", {}],
|
||||
"ctrl-shift-q": ["vim::PushLiteral", {}],
|
||||
"ctrl-r": "vim::PushRegister",
|
||||
"insert": "vim::ToggleReplace",
|
||||
"ctrl-o": "vim::TemporaryNormal"
|
||||
}
|
||||
@@ -356,11 +357,11 @@
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
"ctrl-[": "vim::NormalBefore",
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-k": ["vim::PushDigraph", {}],
|
||||
"ctrl-v": ["vim::PushLiteral", {}],
|
||||
"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::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-q": ["vim::PushLiteral", {}],
|
||||
"ctrl-shift-q": ["vim::PushLiteral", {}],
|
||||
"backspace": "vim::UndoReplace",
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
@@ -375,9 +376,15 @@
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
"ctrl-[": "vim::ClearOperators",
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-q": ["vim::PushOperator", { "Literal": {} }]
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -393,10 +400,10 @@
|
||||
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
|
||||
"bindings": {
|
||||
"w": "vim::Word",
|
||||
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
|
||||
"shift-w": ["vim::Word", { "ignore_punctuation": true }],
|
||||
// Subword TextObject
|
||||
// "w": "vim::Subword",
|
||||
// "shift-w": ["vim::Subword", { "ignorePunctuation": true }],
|
||||
// "shift-w": ["vim::Subword", { "ignore_punctuation": true }],
|
||||
"t": "vim::Tag",
|
||||
"s": "vim::Sentence",
|
||||
"p": "vim::Paragraph",
|
||||
@@ -419,7 +426,7 @@
|
||||
">": "vim::AngleBrackets",
|
||||
"a": "vim::Argument",
|
||||
"i": "vim::IndentObj",
|
||||
"shift-i": ["vim::IndentObj", { "includeBelow": true }],
|
||||
"shift-i": ["vim::IndentObj", { "include_below": true }],
|
||||
"f": "vim::Method",
|
||||
"c": "vim::Class",
|
||||
"e": "vim::EntireFile"
|
||||
@@ -430,14 +437,14 @@
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine",
|
||||
"d": "editor::Rename", // zed specific
|
||||
"s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
|
||||
"s": ["vim::PushChangeSurrounds", {}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == d",
|
||||
"bindings": {
|
||||
"d": "vim::CurrentLine",
|
||||
"s": ["vim::PushOperator", "DeleteSurrounds"],
|
||||
"s": "vim::PushDeleteSurrounds",
|
||||
"o": "editor::ToggleSelectedDiffHunks", // "d o"
|
||||
"p": "editor::RevertSelectedHunks" // "d p"
|
||||
}
|
||||
@@ -476,7 +483,7 @@
|
||||
"context": "vim_operator == y",
|
||||
"bindings": {
|
||||
"y": "vim::CurrentLine",
|
||||
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
|
||||
"s": ["vim::PushAddSurrounds", {}]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -566,34 +573,34 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||
"context": "GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||
"bindings": {
|
||||
// window related commands (ctrl-w X)
|
||||
"ctrl-w": null,
|
||||
"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 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 _": "vim::MaximizePane",
|
||||
"ctrl-w =": "vim::ResetPaneSizes",
|
||||
"ctrl-w g t": "pane::ActivateNextItem",
|
||||
@@ -624,7 +631,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
|
||||
"context": "ChangesList || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
"g /": "pane::DeploySearch"
|
||||
@@ -687,5 +694,22 @@
|
||||
"shift-x": "git::StageAll",
|
||||
"shift-u": "git::UnstageAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
|
||||
"bindings": {
|
||||
// This is identical to the binding in the base keymap, but the vim bindings above to
|
||||
// "vim::Tab" shadow it, so it needs to be bound again.
|
||||
"tab": "editor::AcceptEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "os != macos && Editor && edit_prediction",
|
||||
"bindings": {
|
||||
// alt-l is provided as an alternative to tab/alt-tab. and will be displayed in the UI. This
|
||||
// is because alt-tab may not be available, as it is often used for window switching on Linux
|
||||
// and Windows.
|
||||
"alt-l": "editor::AcceptEditPrediction"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
// Features that can be globally enabled or disabled
|
||||
"features": {
|
||||
// Which edit prediction provider to use.
|
||||
"inline_completion_provider": "copilot"
|
||||
"edit_prediction_provider": "copilot"
|
||||
},
|
||||
// The name of a font to use for rendering text in the editor
|
||||
"buffer_font_family": "Zed Plex Mono",
|
||||
@@ -93,6 +93,13 @@
|
||||
// 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.
|
||||
@@ -161,9 +168,6 @@
|
||||
/// Whether to show the signature help after completion or a bracket pair inserted.
|
||||
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
||||
"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_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
|
||||
@@ -197,11 +201,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::ShowInlineCompletion` (false).
|
||||
"show_inline_completions": true,
|
||||
// or manually by triggering `editor::ShowEditPrediction` (false).
|
||||
"show_edit_predictions": true,
|
||||
// Controls whether edit predictions are shown in a given language scope.
|
||||
// Example: ["string", "comment"]
|
||||
"inline_completions_disabled_in": [],
|
||||
"edit_predictions_disabled_in": [],
|
||||
// Whether to show tabs and spaces in the editor.
|
||||
// This setting can take four values:
|
||||
//
|
||||
@@ -648,15 +652,15 @@
|
||||
// There are 5 possible width values:
|
||||
//
|
||||
// 1. Small: This value is essentially a fixed width.
|
||||
// "modal_width": "small"
|
||||
// "modal_max_width": "small"
|
||||
// 2. Medium:
|
||||
// "modal_width": "medium"
|
||||
// "modal_max_width": "medium"
|
||||
// 3. Large:
|
||||
// "modal_width": "large"
|
||||
// "modal_max_width": "large"
|
||||
// 4. Extra Large:
|
||||
// "modal_width": "xlarge"
|
||||
// "modal_max_width": "xlarge"
|
||||
// 5. Fullscreen: This value removes any horizontal padding, as it consumes the whole viewport width.
|
||||
// "modal_width": "full"
|
||||
// "modal_max_width": "full"
|
||||
//
|
||||
// Default: small
|
||||
"modal_max_width": "small"
|
||||
@@ -774,8 +778,10 @@
|
||||
// 2. Load direnv configuration through the shell hook, works for POSIX shells and fish.
|
||||
// "load_direnv": "shell_hook"
|
||||
"load_direnv": "direct",
|
||||
"inline_completions": {
|
||||
"edit_predictions": {
|
||||
// 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",
|
||||
@@ -787,10 +793,10 @@
|
||||
// When to show edit predictions previews in buffer.
|
||||
// This setting takes two possible values:
|
||||
// 1. Display inline when there are no language server completions available.
|
||||
// "inline_preview": "auto"
|
||||
// "mode": "eager_preview"
|
||||
// 2. Display inline when holding modifier key (alt by default).
|
||||
// "inline_preview": "when_holding_modifier"
|
||||
"inline_preview": "auto"
|
||||
// "mode": "auto"
|
||||
"mode": "eager_preview"
|
||||
},
|
||||
// Settings specific to journaling
|
||||
"journal": {
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
"terminal.ansi.bright_green": "#4d6140ff",
|
||||
"terminal.ansi.dim_green": "#d1e0bfff",
|
||||
"terminal.ansi.yellow": "#dec184ff",
|
||||
"terminal.ansi.bright_yellow": "#786441ff",
|
||||
"terminal.ansi.bright_yellow": "#e5c07bff",
|
||||
"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": "#f1dfc1ff",
|
||||
"terminal.ansi.bright_yellow": "#826221ff",
|
||||
"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(
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("menu", IconName::EllipsisVertical)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Toggle Assistant Menu")),
|
||||
.icon_size(IconSize::Small),
|
||||
Tooltip::text("Toggle Assistant Menu"),
|
||||
)
|
||||
.menu(move |window, cx| {
|
||||
let zoom_label = if _pane.read(cx).is_zoomed() {
|
||||
|
||||
@@ -1255,7 +1255,7 @@ impl InlineAssistant {
|
||||
editor.scroll_manager.set_forbid_vertical_scroll(true);
|
||||
editor.set_show_scrollbars(false, cx);
|
||||
editor.set_read_only(true);
|
||||
editor.set_show_inline_completions(Some(false), window, cx);
|
||||
editor.set_show_edit_predictions(Some(false), window, cx);
|
||||
editor.highlight_rows::<DeletedLines>(
|
||||
Anchor::min()..Anchor::max(),
|
||||
cx.theme().status().deleted_background,
|
||||
@@ -1595,22 +1595,22 @@ impl Render for PromptEditor {
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.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,
|
||||
)
|
||||
}),
|
||||
.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,
|
||||
)
|
||||
},
|
||||
))
|
||||
.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)
|
||||
.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,
|
||||
)
|
||||
}),
|
||||
.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,
|
||||
)
|
||||
},
|
||||
))
|
||||
.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(
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("new", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(Tooltip::text("New…")),
|
||||
.style(ButtonStyle::Subtle),
|
||||
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(
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(Tooltip::text("History…")),
|
||||
.style(ButtonStyle::Subtle),
|
||||
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(
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("add-context", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.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,
|
||||
)
|
||||
}
|
||||
}),
|
||||
.style(ui::ButtonStyle::Filled),
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -1345,7 +1345,7 @@ impl InlineAssistant {
|
||||
editor.scroll_manager.set_forbid_vertical_scroll(true);
|
||||
editor.set_show_scrollbars(false, cx);
|
||||
editor.set_read_only(true);
|
||||
editor.set_show_inline_completions(Some(false), window, cx);
|
||||
editor.set_show_edit_predictions(Some(false), window, cx);
|
||||
editor.highlight_rows::<DeletedLines>(
|
||||
Anchor::min()..Anchor::max(),
|
||||
cx.theme().status().deleted_background,
|
||||
|
||||
@@ -832,12 +832,13 @@ 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;
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
let window = &mut cx.window;
|
||||
this.update(cx.app, |this, cx| {
|
||||
this.render_patch_block(
|
||||
patch_range.clone(),
|
||||
max_width,
|
||||
@@ -2358,8 +2359,8 @@ impl ContextEditor {
|
||||
.icon(IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.tooltip(Tooltip::text("Type / to insert via keyboard")),
|
||||
.icon_position(IconPosition::Start),
|
||||
Tooltip::text("Type / to insert via keyboard"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3322,10 +3323,10 @@ impl Render for ContextEditorToolbarItem {
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Change Model", &ToggleModelSelector, window, cx)
|
||||
}),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action("Change Model", &ToggleModelSelector, window, cx)
|
||||
},
|
||||
)
|
||||
.with_handle(self.language_model_selector_menu_handle.clone()),
|
||||
)
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakEntity};
|
||||
use gpui::{AnyElement, AnyView, 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: PopoverTrigger> {
|
||||
pub(super) struct SlashCommandSelector<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
working_set: Arc<SlashCommandWorkingSet>,
|
||||
active_context_editor: WeakEntity<ContextEditor>,
|
||||
trigger: T,
|
||||
tooltip: TT,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -48,16 +53,22 @@ pub(crate) struct SlashCommandDelegate {
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
impl<T: PopoverTrigger> SlashCommandSelector<T> {
|
||||
impl<T, TT> SlashCommandSelector<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,7 +252,11 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
||||
impl<T, TT> RenderOnce for SlashCommandSelector<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let all_models = self
|
||||
.working_set
|
||||
@@ -322,7 +337,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
||||
.ok();
|
||||
PopoverMenu::new("model-switcher")
|
||||
.menu(move |_window, _cx| Some(picker_view.clone()))
|
||||
.trigger(self.trigger)
|
||||
.trigger_with_tooltip(self.trigger, self.tooltip)
|
||||
.attach(gpui::Corner::TopLeft)
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "diff"
|
||||
name = "buffer_diff"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
@@ -9,9 +9,13 @@ license = "GPL-3.0-or-later"
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/diff.rs"
|
||||
path = "src/buffer_diff.rs"
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
git2.workspace = true
|
||||
gpui.workspace = true
|
||||
@@ -23,10 +27,11 @@ text.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
unindent.workspace = true
|
||||
serde_json.workspace = true
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
text = {workspace = true, features = ["test-support"]}
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
rand.workspace = true
|
||||
serde_json.workspace = true
|
||||
text = { workspace = true, features = ["test-support"] }
|
||||
unindent.workspace = true
|
||||
1520
crates/buffer_diff/src/buffer_diff.rs
Normal file
1520
crates/buffer_diff/src/buffer_diff.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -33,8 +33,6 @@ postage.workspace = true
|
||||
rand.workspace = true
|
||||
release_channel.workspace = true
|
||||
rpc = { workspace = true, features = ["gpui"] }
|
||||
rustls-native-certs.workspace = true
|
||||
rustls.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -146,8 +146,6 @@ pub fn init_settings(cx: &mut App) {
|
||||
}
|
||||
|
||||
pub fn init(client: &Arc<Client>, cx: &mut App) {
|
||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||
|
||||
let client = Arc::downgrade(client);
|
||||
cx.on_action({
|
||||
let client = client.clone();
|
||||
@@ -1126,24 +1124,11 @@ impl Client {
|
||||
|
||||
match url_scheme {
|
||||
Https => {
|
||||
let client_config = {
|
||||
let mut root_store = rustls::RootCertStore::empty();
|
||||
|
||||
let root_certs = rustls_native_certs::load_native_certs();
|
||||
for error in root_certs.errors {
|
||||
log::warn!("error loading native certs: {:?}", error);
|
||||
}
|
||||
root_store.add_parsable_certificates(root_certs.certs);
|
||||
rustls::ClientConfig::builder()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth()
|
||||
};
|
||||
|
||||
let (stream, _) =
|
||||
async_tungstenite::async_tls::client_async_tls_with_connector(
|
||||
request,
|
||||
stream,
|
||||
Some(client_config.into()),
|
||||
Some(http_client::tls_config().into()),
|
||||
)
|
||||
.await?;
|
||||
Ok(Connection::new(
|
||||
|
||||
@@ -33,7 +33,7 @@ clock.workspace = true
|
||||
collections.workspace = true
|
||||
dashmap.workspace = true
|
||||
derive_more.workspace = true
|
||||
diff.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
envy = "0.4.2"
|
||||
futures.workspace = true
|
||||
google_ai.workspace = true
|
||||
@@ -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"]
|
||||
|
||||
@@ -101,6 +101,7 @@ CREATE TABLE "worktree_repositories" (
|
||||
"scan_id" INTEGER NOT NULL,
|
||||
"is_deleted" BOOL NOT NULL,
|
||||
"current_merge_conflicts" VARCHAR,
|
||||
"branch_summary" VARCHAR,
|
||||
PRIMARY KEY(project_id, worktree_id, work_directory_id),
|
||||
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE worktree_repositories
|
||||
ADD COLUMN worktree_repositories VARCHAR NULL;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE worktree_repositories ADD COLUMN branch_summary TEXT NULL;
|
||||
@@ -326,16 +326,26 @@ impl Database {
|
||||
|
||||
if !update.updated_repositories.is_empty() {
|
||||
worktree_repository::Entity::insert_many(update.updated_repositories.iter().map(
|
||||
|repository| worktree_repository::ActiveModel {
|
||||
project_id: ActiveValue::set(project_id),
|
||||
worktree_id: ActiveValue::set(worktree_id),
|
||||
work_directory_id: ActiveValue::set(repository.work_directory_id as i64),
|
||||
scan_id: ActiveValue::set(update.scan_id as i64),
|
||||
branch: ActiveValue::set(repository.branch.clone()),
|
||||
is_deleted: ActiveValue::set(false),
|
||||
current_merge_conflicts: ActiveValue::Set(Some(
|
||||
serde_json::to_string(&repository.current_merge_conflicts).unwrap(),
|
||||
)),
|
||||
|repository| {
|
||||
worktree_repository::ActiveModel {
|
||||
project_id: ActiveValue::set(project_id),
|
||||
worktree_id: ActiveValue::set(worktree_id),
|
||||
work_directory_id: ActiveValue::set(
|
||||
repository.work_directory_id as i64,
|
||||
),
|
||||
scan_id: ActiveValue::set(update.scan_id as i64),
|
||||
branch: ActiveValue::set(repository.branch.clone()),
|
||||
is_deleted: ActiveValue::set(false),
|
||||
branch_summary: ActiveValue::Set(
|
||||
repository
|
||||
.branch_summary
|
||||
.as_ref()
|
||||
.map(|summary| serde_json::to_string(summary).unwrap()),
|
||||
),
|
||||
current_merge_conflicts: ActiveValue::Set(Some(
|
||||
serde_json::to_string(&repository.current_merge_conflicts).unwrap(),
|
||||
)),
|
||||
}
|
||||
},
|
||||
))
|
||||
.on_conflict(
|
||||
@@ -347,6 +357,8 @@ impl Database {
|
||||
.update_columns([
|
||||
worktree_repository::Column::ScanId,
|
||||
worktree_repository::Column::Branch,
|
||||
worktree_repository::Column::BranchSummary,
|
||||
worktree_repository::Column::CurrentMergeConflicts,
|
||||
])
|
||||
.to_owned(),
|
||||
)
|
||||
@@ -779,6 +791,13 @@ impl Database {
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
let branch_summary = db_repository_entry
|
||||
.branch_summary
|
||||
.as_ref()
|
||||
.map(|branch_summary| serde_json::from_str(&branch_summary))
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
worktree.repository_entries.insert(
|
||||
db_repository_entry.work_directory_id as u64,
|
||||
proto::RepositoryEntry {
|
||||
@@ -787,6 +806,7 @@ impl Database {
|
||||
updated_statuses,
|
||||
removed_statuses: Vec::new(),
|
||||
current_merge_conflicts,
|
||||
branch_summary,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -743,12 +743,20 @@ impl Database {
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
let branch_summary = db_repository
|
||||
.branch_summary
|
||||
.as_ref()
|
||||
.map(|branch_summary| serde_json::from_str(&branch_summary))
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
worktree.updated_repositories.push(proto::RepositoryEntry {
|
||||
work_directory_id: db_repository.work_directory_id as u64,
|
||||
branch: db_repository.branch,
|
||||
updated_statuses,
|
||||
removed_statuses,
|
||||
current_merge_conflicts,
|
||||
branch_summary,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,26 +133,23 @@ impl Database {
|
||||
initial_channel_id: Option<ChannelId>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<User> {
|
||||
if let Some(user_by_github_user_id) = user::Entity::find()
|
||||
.filter(user::Column::GithubUserId.eq(github_user_id))
|
||||
.one(tx)
|
||||
if let Some(existing_user) = self
|
||||
.get_user_by_github_user_id_or_github_login(github_user_id, github_login, tx)
|
||||
.await?
|
||||
{
|
||||
let mut user_by_github_user_id = user_by_github_user_id.into_active_model();
|
||||
user_by_github_user_id.github_login = ActiveValue::set(github_login.into());
|
||||
user_by_github_user_id.github_user_created_at =
|
||||
ActiveValue::set(Some(github_user_created_at));
|
||||
Ok(user_by_github_user_id.update(tx).await?)
|
||||
} else if let Some(user_by_github_login) = user::Entity::find()
|
||||
.filter(user::Column::GithubLogin.eq(github_login))
|
||||
.one(tx)
|
||||
.await?
|
||||
{
|
||||
let mut user_by_github_login = user_by_github_login.into_active_model();
|
||||
user_by_github_login.github_user_id = ActiveValue::set(github_user_id);
|
||||
user_by_github_login.github_user_created_at =
|
||||
ActiveValue::set(Some(github_user_created_at));
|
||||
Ok(user_by_github_login.update(tx).await?)
|
||||
let mut existing_user = existing_user.into_active_model();
|
||||
existing_user.github_login = ActiveValue::set(github_login.into());
|
||||
existing_user.github_user_created_at = ActiveValue::set(Some(github_user_created_at));
|
||||
|
||||
if let Some(github_email) = github_email {
|
||||
existing_user.email_address = ActiveValue::set(Some(github_email.into()));
|
||||
}
|
||||
|
||||
if let Some(github_name) = github_name {
|
||||
existing_user.name = ActiveValue::set(Some(github_name.into()));
|
||||
}
|
||||
|
||||
Ok(existing_user.update(tx).await?)
|
||||
} else {
|
||||
let user = user::Entity::insert(user::ActiveModel {
|
||||
email_address: ActiveValue::set(github_email.map(|email| email.into())),
|
||||
@@ -183,6 +180,34 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to retrieve a user, first by their GitHub user ID, and then by their GitHub login.
|
||||
///
|
||||
/// Returns `None` if a user is not found with this GitHub user ID or GitHub login.
|
||||
pub async fn get_user_by_github_user_id_or_github_login(
|
||||
&self,
|
||||
github_user_id: i32,
|
||||
github_login: &str,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Option<User>> {
|
||||
if let Some(user_by_github_user_id) = user::Entity::find()
|
||||
.filter(user::Column::GithubUserId.eq(github_user_id))
|
||||
.one(tx)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(user_by_github_user_id));
|
||||
}
|
||||
|
||||
if let Some(user_by_github_login) = user::Entity::find()
|
||||
.filter(user::Column::GithubLogin.eq(github_login))
|
||||
.one(tx)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(user_by_github_login));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// get_all_users returns the next page of users. To get more call again with
|
||||
/// the same limit and the page incremented by 1.
|
||||
pub async fn get_all_users(&self, page: u32, limit: u32) -> Result<Vec<User>> {
|
||||
|
||||
@@ -15,6 +15,8 @@ pub struct Model {
|
||||
pub is_deleted: bool,
|
||||
// JSON array typed string
|
||||
pub current_merge_conflicts: Option<String>,
|
||||
// A JSON object representing the current Branch values
|
||||
pub branch_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
||||
@@ -395,6 +395,9 @@ impl Server {
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Stage>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Unstage>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Commit>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GitShow>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GitReset>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::SetIndexText>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>)
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
|
||||
.add_message_handler(update_context)
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::{
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_context_editor::ContextStore;
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use buffer_diff::{assert_hunks, DiffHunkSecondaryStatus, DiffHunkStatus};
|
||||
use call::{room, ActiveCall, ParticipantLocation, Room};
|
||||
use client::{User, RECEIVE_TIMEOUT};
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -2613,11 +2614,11 @@ async fn test_git_diff_base_change(
|
||||
diff.base_text_string().as_deref(),
|
||||
Some(staged_text.as_str())
|
||||
);
|
||||
diff::assert_hunks(
|
||||
diff.snapshot.hunks_in_row_range(0..4, buffer),
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&[(1..2, "", "two\n")],
|
||||
&[(1..2, "", "two\n", DiffHunkStatus::added())],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2641,11 +2642,11 @@ async fn test_git_diff_base_change(
|
||||
diff.base_text_string().as_deref(),
|
||||
Some(staged_text.as_str())
|
||||
);
|
||||
diff::assert_hunks(
|
||||
diff.snapshot.hunks_in_row_range(0..4, buffer),
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&[(1..2, "", "two\n")],
|
||||
&[(1..2, "", "two\n", DiffHunkStatus::added())],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2663,11 +2664,16 @@ async fn test_git_diff_base_change(
|
||||
diff.base_text_string().as_deref(),
|
||||
Some(committed_text.as_str())
|
||||
);
|
||||
diff::assert_hunks(
|
||||
diff.snapshot.hunks_in_row_range(0..4, buffer),
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&[(1..2, "TWO\n", "two\n")],
|
||||
&[(
|
||||
1..2,
|
||||
"TWO\n",
|
||||
"two\n",
|
||||
DiffHunkStatus::Modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
|
||||
)],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2689,11 +2695,11 @@ async fn test_git_diff_base_change(
|
||||
diff.base_text_string().as_deref(),
|
||||
Some(new_staged_text.as_str())
|
||||
);
|
||||
diff::assert_hunks(
|
||||
diff.snapshot.hunks_in_row_range(0..4, buffer),
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&[(2..3, "", "three\n")],
|
||||
&[(2..3, "", "three\n", DiffHunkStatus::added())],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2703,11 +2709,11 @@ async fn test_git_diff_base_change(
|
||||
diff.base_text_string().as_deref(),
|
||||
Some(new_staged_text.as_str())
|
||||
);
|
||||
diff::assert_hunks(
|
||||
diff.snapshot.hunks_in_row_range(0..4, buffer),
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&[(2..3, "", "three\n")],
|
||||
&[(2..3, "", "three\n", DiffHunkStatus::added())],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2717,11 +2723,16 @@ async fn test_git_diff_base_change(
|
||||
diff.base_text_string().as_deref(),
|
||||
Some(new_committed_text.as_str())
|
||||
);
|
||||
diff::assert_hunks(
|
||||
diff.snapshot.hunks_in_row_range(0..4, buffer),
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&[(1..2, "TWO_HUNDRED\n", "two\n")],
|
||||
&[(
|
||||
1..2,
|
||||
"TWO_HUNDRED\n",
|
||||
"two\n",
|
||||
DiffHunkStatus::Modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
|
||||
)],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2763,11 +2774,11 @@ async fn test_git_diff_base_change(
|
||||
diff.base_text_string().as_deref(),
|
||||
Some(staged_text.as_str())
|
||||
);
|
||||
diff::assert_hunks(
|
||||
diff.snapshot.hunks_in_row_range(0..4, buffer),
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&[(1..2, "", "two\n")],
|
||||
&[(1..2, "", "two\n", DiffHunkStatus::added())],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2790,11 +2801,11 @@ async fn test_git_diff_base_change(
|
||||
diff.base_text_string().as_deref(),
|
||||
Some(staged_text.as_str())
|
||||
);
|
||||
diff::assert_hunks(
|
||||
diff.snapshot.hunks_in_row_range(0..4, buffer),
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
buffer,
|
||||
&staged_text,
|
||||
&[(1..2, "", "two\n")],
|
||||
&[(1..2, "", "two\n", DiffHunkStatus::added())],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2812,11 +2823,11 @@ async fn test_git_diff_base_change(
|
||||
diff.base_text_string().as_deref(),
|
||||
Some(new_staged_text.as_str())
|
||||
);
|
||||
diff::assert_hunks(
|
||||
diff.snapshot.hunks_in_row_range(0..4, buffer),
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
buffer,
|
||||
&new_staged_text,
|
||||
&[(2..3, "", "three\n")],
|
||||
&[(2..3, "", "three\n", DiffHunkStatus::added())],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2826,11 +2837,11 @@ async fn test_git_diff_base_change(
|
||||
diff.base_text_string().as_deref(),
|
||||
Some(new_staged_text.as_str())
|
||||
);
|
||||
diff::assert_hunks(
|
||||
diff.snapshot.hunks_in_row_range(0..4, buffer),
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
buffer,
|
||||
&new_staged_text,
|
||||
&[(2..3, "", "three\n")],
|
||||
&[(2..3, "", "three\n", DiffHunkStatus::added())],
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -2884,7 +2895,10 @@ async fn test_git_branch_name(
|
||||
assert_eq!(worktrees.len(), 1);
|
||||
let worktree = worktrees[0].clone();
|
||||
let root_entry = worktree.read(cx).snapshot().root_git_entry().unwrap();
|
||||
assert_eq!(root_entry.branch(), branch_name.map(Into::into));
|
||||
assert_eq!(
|
||||
root_entry.branch().map(|branch| branch.name.to_string()),
|
||||
branch_name
|
||||
);
|
||||
}
|
||||
|
||||
// Smoke test branch reading
|
||||
@@ -6772,7 +6786,7 @@ async fn test_remote_git_branches(
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(host_branch.as_ref(), branches[2]);
|
||||
assert_eq!(host_branch.name, branches[2]);
|
||||
|
||||
// Also try creating a new branch
|
||||
cx_b.update(|cx| {
|
||||
@@ -6793,5 +6807,5 @@ async fn test_remote_git_branches(
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(host_branch.as_ref(), "totally-new-branch");
|
||||
assert_eq!(host_branch.name, "totally-new-branch");
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ async fn test_ssh_collaboration_git_branches(
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(server_branch.as_ref(), branches[2]);
|
||||
assert_eq!(server_branch.name, branches[2]);
|
||||
|
||||
// Also try creating a new branch
|
||||
cx_b.update(|cx| {
|
||||
@@ -337,7 +337,7 @@ async fn test_ssh_collaboration_git_branches(
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(server_branch.as_ref(), "totally-new-branch");
|
||||
assert_eq!(server_branch.name, "totally-new-branch");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -461,7 +461,7 @@ impl Item for ChannelView {
|
||||
.child(
|
||||
Label::new(channel_name)
|
||||
.color(params.text_color())
|
||||
.italic(params.preview),
|
||||
.when(params.preview, |this| this.italic()),
|
||||
)
|
||||
.when_some(status, |element, status| {
|
||||
element.child(
|
||||
|
||||
@@ -198,26 +198,29 @@ impl CommandPaletteDelegate {
|
||||
) {
|
||||
self.updating_matches.take();
|
||||
|
||||
let mut intercept_result = CommandPaletteInterceptor::try_global(cx)
|
||||
.and_then(|interceptor| interceptor.intercept(&query, cx));
|
||||
let mut intercept_results = CommandPaletteInterceptor::try_global(cx)
|
||||
.map(|interceptor| interceptor.intercept(&query, cx))
|
||||
.unwrap_or_default();
|
||||
|
||||
if parse_zed_link(&query, cx).is_some() {
|
||||
intercept_result = Some(CommandInterceptResult {
|
||||
intercept_results = vec![CommandInterceptResult {
|
||||
action: OpenZedUrl { url: query.clone() }.boxed_clone(),
|
||||
string: query.clone(),
|
||||
positions: vec![],
|
||||
})
|
||||
}]
|
||||
}
|
||||
|
||||
if let Some(CommandInterceptResult {
|
||||
let mut new_matches = Vec::new();
|
||||
|
||||
for CommandInterceptResult {
|
||||
action,
|
||||
string,
|
||||
positions,
|
||||
}) = intercept_result
|
||||
} in intercept_results
|
||||
{
|
||||
if let Some(idx) = matches
|
||||
.iter()
|
||||
.position(|m| commands[m.candidate_id].action.type_id() == action.type_id())
|
||||
.position(|m| commands[m.candidate_id].action.partial_eq(&*action))
|
||||
{
|
||||
matches.remove(idx);
|
||||
}
|
||||
@@ -225,18 +228,16 @@ impl CommandPaletteDelegate {
|
||||
name: string.clone(),
|
||||
action,
|
||||
});
|
||||
matches.insert(
|
||||
0,
|
||||
StringMatch {
|
||||
candidate_id: commands.len() - 1,
|
||||
string,
|
||||
positions,
|
||||
score: 0.0,
|
||||
},
|
||||
)
|
||||
new_matches.push(StringMatch {
|
||||
candidate_id: commands.len() - 1,
|
||||
string,
|
||||
positions,
|
||||
score: 0.0,
|
||||
})
|
||||
}
|
||||
new_matches.append(&mut matches);
|
||||
self.commands = commands;
|
||||
self.matches = matches;
|
||||
self.matches = new_matches;
|
||||
if self.matches.is_empty() {
|
||||
self.selected_ix = 0;
|
||||
} else {
|
||||
|
||||
@@ -108,7 +108,7 @@ pub struct CommandInterceptResult {
|
||||
/// An interceptor for the command palette.
|
||||
#[derive(Default)]
|
||||
pub struct CommandPaletteInterceptor(
|
||||
Option<Box<dyn Fn(&str, &App) -> Option<CommandInterceptResult>>>,
|
||||
Option<Box<dyn Fn(&str, &App) -> Vec<CommandInterceptResult>>>,
|
||||
);
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -132,10 +132,12 @@ impl CommandPaletteInterceptor {
|
||||
}
|
||||
|
||||
/// Intercepts the given query from the command palette.
|
||||
pub fn intercept(&self, query: &str, cx: &App) -> Option<CommandInterceptResult> {
|
||||
let handler = self.0.as_ref()?;
|
||||
|
||||
(handler)(query, cx)
|
||||
pub fn intercept(&self, query: &str, cx: &App) -> Vec<CommandInterceptResult> {
|
||||
if let Some(handler) = self.0.as_ref() {
|
||||
(handler)(query, cx)
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the global interceptor.
|
||||
@@ -146,7 +148,7 @@ impl CommandPaletteInterceptor {
|
||||
/// Sets the global interceptor.
|
||||
///
|
||||
/// This will override the previous interceptor, if it exists.
|
||||
pub fn set(&mut self, handler: Box<dyn Fn(&str, &App) -> Option<CommandInterceptResult>>) {
|
||||
pub fn set(&mut self, handler: Box<dyn Fn(&str, &App) -> Vec<CommandInterceptResult>>) {
|
||||
self.0 = Some(handler);
|
||||
}
|
||||
}
|
||||
|
||||
23
crates/component/Cargo.toml
Normal file
23
crates/component/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[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 = []
|
||||
327
crates/component/src/component.rs
Normal file
327
crates/component/src/component.rs
Normal file
@@ -0,0 +1,327 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{div, prelude::*, px, 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()
|
||||
.p_2()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().colors().text)
|
||||
.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().pb_5().child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(div().h_px().w_4().bg(cx.theme().colors().border_variant))
|
||||
.child(
|
||||
div()
|
||||
.flex_none()
|
||||
.text_size(px(10.))
|
||||
.child(title.to_uppercase()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.h_px()
|
||||
.w_full()
|
||||
.flex_1()
|
||||
.bg(cx.theme().colors().border),
|
||||
),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_start()
|
||||
.w_full()
|
||||
.gap_8()
|
||||
.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,5 +1,5 @@
|
||||
[package]
|
||||
name = "vcs_menu"
|
||||
name = "component_preview"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
@@ -8,14 +8,14 @@ license = "GPL-3.0-or-later"
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/component_preview.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
fuzzy.workspace = true
|
||||
git.workspace = true
|
||||
component.workspace = true
|
||||
gpui.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
1
crates/component_preview/LICENSE-GPL
Symbolic link
1
crates/component_preview/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
190
crates/component_preview/src/component_preview.rs
Normal file
190
crates/component_preview/src/component_preview.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
//! # 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()
|
||||
.max_w_48()
|
||||
.gap_px()
|
||||
.p_1()
|
||||
.children(
|
||||
sorted_components
|
||||
.into_iter()
|
||||
.map(|component| self.render_sidebar_entry(&component, _cx)),
|
||||
)
|
||||
.child(
|
||||
Label::new("These will be clickable once the layout is moved to a gpui::List.")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall)
|
||||
.italic(),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_sidebar_entry(
|
||||
&self,
|
||||
component: &ComponentMetadata,
|
||||
_cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
h_flex()
|
||||
.w_40()
|
||||
.px_1p5()
|
||||
.py_0p5()
|
||||
.text_sm()
|
||||
.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_flex()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.w_full()
|
||||
.gap_3()
|
||||
.py_6()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.text_2xl()
|
||||
.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_4()
|
||||
.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, InlineCompletionProvider},
|
||||
language_settings::{all_language_settings, language_settings, EditPredictionProvider},
|
||||
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).inline_completions.provider
|
||||
== InlineCompletionProvider::Copilot
|
||||
if all_language_settings(None, cx).edit_predictions.provider
|
||||
== EditPredictionProvider::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, InlineCompletion, InlineCompletionProvider};
|
||||
use inline_completion::{Direction, EditPredictionProvider, InlineCompletion};
|
||||
use language::{language_settings::AllLanguageSettings, Buffer, OffsetRangeExt, ToOffset};
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
@@ -48,7 +48,7 @@ impl CopilotCompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
impl EditPredictionProvider for CopilotCompletionProvider {
|
||||
fn name() -> &'static str {
|
||||
"copilot"
|
||||
}
|
||||
@@ -61,10 +61,6 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
false
|
||||
}
|
||||
|
||||
fn show_completions_in_normal_mode() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_refreshing(&self) -> bool {
|
||||
self.pending_refresh.is_some()
|
||||
}
|
||||
@@ -246,6 +242,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
} else {
|
||||
let position = cursor_position.bias_right(buffer);
|
||||
Some(InlineCompletion {
|
||||
id: None,
|
||||
edits: vec![(position..position, completion_text.into())],
|
||||
edit_preview: None,
|
||||
})
|
||||
@@ -305,7 +302,7 @@ mod tests {
|
||||
.await;
|
||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
|
||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||
});
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
@@ -440,8 +437,8 @@ mod tests {
|
||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
|
||||
|
||||
// AcceptInlineCompletion when there is an active suggestion inserts it.
|
||||
editor.accept_inline_completion(&Default::default(), window, cx);
|
||||
// AcceptEditPrediction when there is an active suggestion inserts it.
|
||||
editor.accept_edit_prediction(&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");
|
||||
@@ -486,7 +483,7 @@ mod tests {
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.next_inline_completion(&Default::default(), window, cx)
|
||||
editor.next_edit_prediction(&Default::default(), window, cx)
|
||||
});
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
@@ -500,8 +497,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 AcceptInlineCompletion again accepts the suggestion.
|
||||
editor.accept_inline_completion(&Default::default(), window, cx);
|
||||
// Using AcceptEditPrediction again accepts the suggestion.
|
||||
editor.accept_edit_prediction(&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}");
|
||||
@@ -530,7 +527,7 @@ mod tests {
|
||||
.await;
|
||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
|
||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||
});
|
||||
|
||||
// Setup the editor with a completion request.
|
||||
@@ -654,7 +651,7 @@ mod tests {
|
||||
.await;
|
||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
|
||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||
});
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
@@ -673,7 +670,7 @@ mod tests {
|
||||
vec![],
|
||||
);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.next_inline_completion(&Default::default(), window, cx)
|
||||
editor.next_edit_prediction(&Default::default(), window, cx)
|
||||
});
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
@@ -744,7 +741,7 @@ mod tests {
|
||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
|
||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -762,7 +759,7 @@ mod tests {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
|
||||
});
|
||||
editor.next_inline_completion(&Default::default(), window, cx);
|
||||
editor.next_edit_prediction(&Default::default(), window, cx);
|
||||
});
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
_ = editor.update(cx, |editor, _, cx| {
|
||||
@@ -838,7 +835,7 @@ mod tests {
|
||||
.await;
|
||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
|
||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||
});
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
@@ -866,7 +863,7 @@ mod tests {
|
||||
vec![],
|
||||
);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.next_inline_completion(&Default::default(), window, cx)
|
||||
editor.next_edit_prediction(&Default::default(), window, cx)
|
||||
});
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
@@ -934,7 +931,7 @@ mod tests {
|
||||
async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||
init_test(cx, |settings| {
|
||||
settings
|
||||
.inline_completions
|
||||
.edit_predictions
|
||||
.get_or_insert(Default::default())
|
||||
.disabled_globs = Some(vec![".env*".to_string()]);
|
||||
});
|
||||
@@ -996,7 +993,7 @@ mod tests {
|
||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
|
||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use unindent::Unindent as _;
|
||||
use util::{post_inc, RandomCharIter};
|
||||
use util::{path, 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(
|
||||
"/test",
|
||||
path!("/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(), ["/test".as_ref()], cx).await;
|
||||
let project = Project::test(fs.clone(), [path!("/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("/test/main.rs"),
|
||||
PathBuf::from(path!("/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("/test/consts.rs"),
|
||||
PathBuf::from(path!("/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("/test/consts.rs"),
|
||||
PathBuf::from(path!("/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(
|
||||
"/test",
|
||||
path!("/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(), ["/test".as_ref()], cx).await;
|
||||
let project = Project::test(fs.clone(), [path!("/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("/test/main.js"),
|
||||
PathBuf::from(path!("/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("/test/main.js"),
|
||||
PathBuf::from(path!("/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("/test/main.js"),
|
||||
PathBuf::from(path!("/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("/test/main.rs"),
|
||||
PathBuf::from(path!("/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("/test/main.js"),
|
||||
PathBuf::from(path!("/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("/test", json!({})).await;
|
||||
fs.insert_tree(path!("/test"), json!({})).await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
let project = Project::test(fs.clone(), [path!("/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!("/test/{}.rs", post_inc(&mut next_filename)).into();
|
||||
format!(path!("/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("/test").unwrap()
|
||||
info.path == path.strip_prefix(path!("/test")).unwrap()
|
||||
&& info.language_server == language_server_id
|
||||
&& row_range.contains(&diagnostic.range.start.0.row)
|
||||
});
|
||||
|
||||
@@ -1,897 +0,0 @@
|
||||
use futures::{channel::oneshot, future::OptionFuture};
|
||||
use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
|
||||
use gpui::{App, Context, Entity, EventEmitter};
|
||||
use language::{Language, LanguageRegistry};
|
||||
use rope::Rope;
|
||||
use std::{cmp, future::Future, iter, ops::Range, sync::Arc};
|
||||
use sum_tree::SumTree;
|
||||
use text::{Anchor, BufferId, OffsetRangeExt, Point};
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DiffHunkStatus {
|
||||
Added,
|
||||
Modified,
|
||||
Removed,
|
||||
}
|
||||
|
||||
/// A diff hunk resolved to rows in the buffer.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DiffHunk {
|
||||
/// The buffer range, expressed in terms of rows.
|
||||
pub row_range: Range<u32>,
|
||||
/// The range in the buffer to which this hunk corresponds.
|
||||
pub buffer_range: Range<Anchor>,
|
||||
/// The range in the buffer's diff base text to which this hunk corresponds.
|
||||
pub diff_base_byte_range: Range<usize>,
|
||||
}
|
||||
|
||||
/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct InternalDiffHunk {
|
||||
buffer_range: Range<Anchor>,
|
||||
diff_base_byte_range: Range<usize>,
|
||||
}
|
||||
|
||||
impl sum_tree::Item for InternalDiffHunk {
|
||||
type Summary = DiffHunkSummary;
|
||||
|
||||
fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
|
||||
DiffHunkSummary {
|
||||
buffer_range: self.buffer_range.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DiffHunkSummary {
|
||||
buffer_range: Range<Anchor>,
|
||||
}
|
||||
|
||||
impl sum_tree::Summary for DiffHunkSummary {
|
||||
type Context = text::BufferSnapshot;
|
||||
|
||||
fn zero(_cx: &Self::Context) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
|
||||
self.buffer_range.start = self
|
||||
.buffer_range
|
||||
.start
|
||||
.min(&other.buffer_range.start, buffer);
|
||||
self.buffer_range.end = self.buffer_range.end.max(&other.buffer_range.end, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BufferDiffSnapshot {
|
||||
hunks: SumTree<InternalDiffHunk>,
|
||||
pub base_text: Option<language::BufferSnapshot>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BufferDiffSnapshot {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("BufferDiffSnapshot")
|
||||
.field("hunks", &self.hunks)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferDiffSnapshot {
|
||||
pub fn new(buffer: &text::BufferSnapshot) -> BufferDiffSnapshot {
|
||||
BufferDiffSnapshot {
|
||||
hunks: SumTree::new(buffer),
|
||||
base_text: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_single_insertion(cx: &mut App) -> Self {
|
||||
let base_text = language::Buffer::build_empty_snapshot(cx);
|
||||
Self {
|
||||
hunks: SumTree::from_item(
|
||||
InternalDiffHunk {
|
||||
buffer_range: Anchor::MIN..Anchor::MAX,
|
||||
diff_base_byte_range: 0..0,
|
||||
},
|
||||
&base_text,
|
||||
),
|
||||
base_text: Some(base_text),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn build_sync(
|
||||
buffer: text::BufferSnapshot,
|
||||
diff_base: String,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) -> Self {
|
||||
let snapshot =
|
||||
cx.update(|cx| Self::build(buffer, Some(Arc::new(diff_base)), None, None, cx));
|
||||
cx.executor().block(snapshot)
|
||||
}
|
||||
|
||||
pub fn build(
|
||||
buffer: text::BufferSnapshot,
|
||||
diff_base: Option<Arc<String>>,
|
||||
language: Option<Arc<Language>>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &mut App,
|
||||
) -> impl Future<Output = Self> {
|
||||
let base_text_snapshot = diff_base.as_ref().map(|base_text| {
|
||||
language::Buffer::build_snapshot(
|
||||
Rope::from(base_text.as_str()),
|
||||
language.clone(),
|
||||
language_registry.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let base_text_snapshot = cx
|
||||
.background_executor()
|
||||
.spawn(OptionFuture::from(base_text_snapshot));
|
||||
|
||||
let hunks = cx.background_executor().spawn({
|
||||
let buffer = buffer.clone();
|
||||
async move { Self::recalculate_hunks(diff_base, buffer) }
|
||||
});
|
||||
|
||||
async move {
|
||||
let (base_text, hunks) = futures::join!(base_text_snapshot, hunks);
|
||||
Self { base_text, hunks }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_with_base_buffer(
|
||||
buffer: text::BufferSnapshot,
|
||||
diff_base: Option<Arc<String>>,
|
||||
diff_base_buffer: Option<language::BufferSnapshot>,
|
||||
cx: &App,
|
||||
) -> impl Future<Output = Self> {
|
||||
cx.background_executor().spawn({
|
||||
let buffer = buffer.clone();
|
||||
async move {
|
||||
let hunks = Self::recalculate_hunks(diff_base, buffer);
|
||||
Self {
|
||||
hunks,
|
||||
base_text: diff_base_buffer,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn recalculate_hunks(
|
||||
diff_base: Option<Arc<String>>,
|
||||
buffer: text::BufferSnapshot,
|
||||
) -> SumTree<InternalDiffHunk> {
|
||||
let mut tree = SumTree::new(&buffer);
|
||||
|
||||
if let Some(diff_base) = diff_base {
|
||||
let buffer_text = buffer.as_rope().to_string();
|
||||
let patch = Self::diff(&diff_base, &buffer_text);
|
||||
|
||||
// A common case in Zed is that the empty buffer is represented as just a newline,
|
||||
// but if we just compute a naive diff you get a "preserved" line in the middle,
|
||||
// which is a bit odd.
|
||||
if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
|
||||
tree.push(
|
||||
InternalDiffHunk {
|
||||
buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
|
||||
diff_base_byte_range: 0..diff_base.len() - 1,
|
||||
},
|
||||
&buffer,
|
||||
);
|
||||
return tree;
|
||||
}
|
||||
|
||||
if let Some(patch) = patch {
|
||||
let mut divergence = 0;
|
||||
for hunk_index in 0..patch.num_hunks() {
|
||||
let hunk =
|
||||
Self::process_patch_hunk(&patch, hunk_index, &buffer, &mut divergence);
|
||||
tree.push(hunk, &buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tree
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.hunks.is_empty()
|
||||
}
|
||||
|
||||
pub fn hunks_in_row_range<'a>(
|
||||
&'a self,
|
||||
range: Range<u32>,
|
||||
buffer: &'a text::BufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk> {
|
||||
let start = buffer.anchor_before(Point::new(range.start, 0));
|
||||
let end = buffer.anchor_after(Point::new(range.end, 0));
|
||||
|
||||
self.hunks_intersecting_range(start..end, buffer)
|
||||
}
|
||||
|
||||
pub fn hunks_intersecting_range<'a>(
|
||||
&'a self,
|
||||
range: Range<Anchor>,
|
||||
buffer: &'a text::BufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk> {
|
||||
let range = range.to_offset(buffer);
|
||||
|
||||
let mut cursor = self
|
||||
.hunks
|
||||
.filter::<_, DiffHunkSummary>(buffer, move |summary| {
|
||||
let summary_range = summary.buffer_range.to_offset(buffer);
|
||||
let before_start = summary_range.end < range.start;
|
||||
let after_end = summary_range.start > range.end;
|
||||
!before_start && !after_end
|
||||
});
|
||||
|
||||
let anchor_iter = iter::from_fn(move || {
|
||||
cursor.next(buffer);
|
||||
cursor.item()
|
||||
})
|
||||
.flat_map(move |hunk| {
|
||||
[
|
||||
(
|
||||
&hunk.buffer_range.start,
|
||||
(hunk.buffer_range.start, hunk.diff_base_byte_range.start),
|
||||
),
|
||||
(
|
||||
&hunk.buffer_range.end,
|
||||
(hunk.buffer_range.end, hunk.diff_base_byte_range.end),
|
||||
),
|
||||
]
|
||||
});
|
||||
|
||||
let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
|
||||
iter::from_fn(move || loop {
|
||||
let (start_point, (start_anchor, start_base)) = summaries.next()?;
|
||||
let (mut end_point, (mut end_anchor, end_base)) = summaries.next()?;
|
||||
|
||||
if !start_anchor.is_valid(buffer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if end_point.column > 0 {
|
||||
end_point.row += 1;
|
||||
end_point.column = 0;
|
||||
end_anchor = buffer.anchor_before(end_point);
|
||||
}
|
||||
|
||||
return Some(DiffHunk {
|
||||
row_range: start_point.row..end_point.row,
|
||||
diff_base_byte_range: start_base..end_base,
|
||||
buffer_range: start_anchor..end_anchor,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hunks_intersecting_range_rev<'a>(
|
||||
&'a self,
|
||||
range: Range<Anchor>,
|
||||
buffer: &'a text::BufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk> {
|
||||
let mut cursor = self
|
||||
.hunks
|
||||
.filter::<_, DiffHunkSummary>(buffer, move |summary| {
|
||||
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
|
||||
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
|
||||
!before_start && !after_end
|
||||
});
|
||||
|
||||
iter::from_fn(move || {
|
||||
cursor.prev(buffer);
|
||||
|
||||
let hunk = cursor.item()?;
|
||||
let range = hunk.buffer_range.to_point(buffer);
|
||||
let end_row = if range.end.column > 0 {
|
||||
range.end.row + 1
|
||||
} else {
|
||||
range.end.row
|
||||
};
|
||||
|
||||
Some(DiffHunk {
|
||||
row_range: range.start.row..end_row,
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn compare(
|
||||
&self,
|
||||
old: &Self,
|
||||
new_snapshot: &text::BufferSnapshot,
|
||||
) -> Option<Range<Anchor>> {
|
||||
let mut new_cursor = self.hunks.cursor::<()>(new_snapshot);
|
||||
let mut old_cursor = old.hunks.cursor::<()>(new_snapshot);
|
||||
old_cursor.next(new_snapshot);
|
||||
new_cursor.next(new_snapshot);
|
||||
let mut start = None;
|
||||
let mut end = None;
|
||||
|
||||
loop {
|
||||
match (new_cursor.item(), old_cursor.item()) {
|
||||
(Some(new_hunk), Some(old_hunk)) => {
|
||||
match new_hunk
|
||||
.buffer_range
|
||||
.start
|
||||
.cmp(&old_hunk.buffer_range.start, new_snapshot)
|
||||
{
|
||||
cmp::Ordering::Less => {
|
||||
start.get_or_insert(new_hunk.buffer_range.start);
|
||||
end.replace(new_hunk.buffer_range.end);
|
||||
new_cursor.next(new_snapshot);
|
||||
}
|
||||
cmp::Ordering::Equal => {
|
||||
if new_hunk != old_hunk {
|
||||
start.get_or_insert(new_hunk.buffer_range.start);
|
||||
if old_hunk
|
||||
.buffer_range
|
||||
.end
|
||||
.cmp(&new_hunk.buffer_range.end, new_snapshot)
|
||||
.is_ge()
|
||||
{
|
||||
end.replace(old_hunk.buffer_range.end);
|
||||
} else {
|
||||
end.replace(new_hunk.buffer_range.end);
|
||||
}
|
||||
}
|
||||
|
||||
new_cursor.next(new_snapshot);
|
||||
old_cursor.next(new_snapshot);
|
||||
}
|
||||
cmp::Ordering::Greater => {
|
||||
start.get_or_insert(old_hunk.buffer_range.start);
|
||||
end.replace(old_hunk.buffer_range.end);
|
||||
old_cursor.next(new_snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
(Some(new_hunk), None) => {
|
||||
start.get_or_insert(new_hunk.buffer_range.start);
|
||||
end.replace(new_hunk.buffer_range.end);
|
||||
new_cursor.next(new_snapshot);
|
||||
}
|
||||
(None, Some(old_hunk)) => {
|
||||
start.get_or_insert(old_hunk.buffer_range.start);
|
||||
end.replace(old_hunk.buffer_range.end);
|
||||
old_cursor.next(new_snapshot);
|
||||
}
|
||||
(None, None) => break,
|
||||
}
|
||||
}
|
||||
|
||||
start.zip(end).map(|(start, end)| start..end)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn clear(&mut self, buffer: &text::BufferSnapshot) {
|
||||
self.hunks = SumTree::new(buffer);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn hunks<'a>(&'a self, text: &'a text::BufferSnapshot) -> impl 'a + Iterator<Item = DiffHunk> {
|
||||
let start = text.anchor_before(Point::new(0, 0));
|
||||
let end = text.anchor_after(Point::new(u32::MAX, u32::MAX));
|
||||
self.hunks_intersecting_range(start..end, text)
|
||||
}
|
||||
|
||||
fn diff<'a>(head: &'a str, current: &'a str) -> Option<GitPatch<'a>> {
|
||||
let mut options = GitOptions::default();
|
||||
options.context_lines(0);
|
||||
|
||||
let patch = GitPatch::from_buffers(
|
||||
head.as_bytes(),
|
||||
None,
|
||||
current.as_bytes(),
|
||||
None,
|
||||
Some(&mut options),
|
||||
);
|
||||
|
||||
match patch {
|
||||
Ok(patch) => Some(patch),
|
||||
|
||||
Err(err) => {
|
||||
log::error!("`GitPatch::from_buffers` failed: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_patch_hunk(
|
||||
patch: &GitPatch<'_>,
|
||||
hunk_index: usize,
|
||||
buffer: &text::BufferSnapshot,
|
||||
buffer_row_divergence: &mut i64,
|
||||
) -> InternalDiffHunk {
|
||||
let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
|
||||
assert!(line_item_count > 0);
|
||||
|
||||
let mut first_deletion_buffer_row: Option<u32> = None;
|
||||
let mut buffer_row_range: Option<Range<u32>> = None;
|
||||
let mut diff_base_byte_range: Option<Range<usize>> = None;
|
||||
|
||||
for line_index in 0..line_item_count {
|
||||
let line = patch.line_in_hunk(hunk_index, line_index).unwrap();
|
||||
let kind = line.origin_value();
|
||||
let content_offset = line.content_offset() as isize;
|
||||
let content_len = line.content().len() as isize;
|
||||
|
||||
if kind == GitDiffLineType::Addition {
|
||||
*buffer_row_divergence += 1;
|
||||
let row = line.new_lineno().unwrap().saturating_sub(1);
|
||||
|
||||
match &mut buffer_row_range {
|
||||
Some(buffer_row_range) => buffer_row_range.end = row + 1,
|
||||
None => buffer_row_range = Some(row..row + 1),
|
||||
}
|
||||
}
|
||||
|
||||
if kind == GitDiffLineType::Deletion {
|
||||
let end = content_offset + content_len;
|
||||
|
||||
match &mut diff_base_byte_range {
|
||||
Some(head_byte_range) => head_byte_range.end = end as usize,
|
||||
None => diff_base_byte_range = Some(content_offset as usize..end as usize),
|
||||
}
|
||||
|
||||
if first_deletion_buffer_row.is_none() {
|
||||
let old_row = line.old_lineno().unwrap().saturating_sub(1);
|
||||
let row = old_row as i64 + *buffer_row_divergence;
|
||||
first_deletion_buffer_row = Some(row as u32);
|
||||
}
|
||||
|
||||
*buffer_row_divergence -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
//unwrap_or deletion without addition
|
||||
let buffer_row_range = buffer_row_range.unwrap_or_else(|| {
|
||||
//we cannot have an addition-less hunk without deletion(s) or else there would be no hunk
|
||||
let row = first_deletion_buffer_row.unwrap();
|
||||
row..row
|
||||
});
|
||||
|
||||
//unwrap_or addition without deletion
|
||||
let diff_base_byte_range = diff_base_byte_range.unwrap_or(0..0);
|
||||
|
||||
let start = Point::new(buffer_row_range.start, 0);
|
||||
let end = Point::new(buffer_row_range.end, 0);
|
||||
let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
|
||||
InternalDiffHunk {
|
||||
buffer_range,
|
||||
diff_base_byte_range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BufferDiff {
|
||||
pub buffer_id: BufferId,
|
||||
pub snapshot: BufferDiffSnapshot,
|
||||
pub unstaged_diff: Option<Entity<BufferDiff>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BufferDiff {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("BufferChangeSet")
|
||||
.field("buffer_id", &self.buffer_id)
|
||||
.field("snapshot", &self.snapshot)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum BufferDiffEvent {
|
||||
DiffChanged { changed_range: Range<text::Anchor> },
|
||||
LanguageChanged,
|
||||
}
|
||||
|
||||
impl EventEmitter<BufferDiffEvent> for BufferDiff {}
|
||||
|
||||
impl BufferDiff {
|
||||
pub fn set_state(
|
||||
&mut self,
|
||||
snapshot: BufferDiffSnapshot,
|
||||
buffer: &text::BufferSnapshot,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(base_text) = snapshot.base_text.as_ref() {
|
||||
let changed_range = if Some(base_text.remote_id())
|
||||
!= self
|
||||
.snapshot
|
||||
.base_text
|
||||
.as_ref()
|
||||
.map(|buffer| buffer.remote_id())
|
||||
{
|
||||
Some(text::Anchor::MIN..text::Anchor::MAX)
|
||||
} else {
|
||||
snapshot.compare(&self.snapshot, buffer)
|
||||
};
|
||||
if let Some(changed_range) = changed_range {
|
||||
cx.emit(BufferDiffEvent::DiffChanged { changed_range });
|
||||
}
|
||||
}
|
||||
self.snapshot = snapshot;
|
||||
}
|
||||
|
||||
pub fn diff_hunks_intersecting_range<'a>(
|
||||
&'a self,
|
||||
range: Range<text::Anchor>,
|
||||
buffer_snapshot: &'a text::BufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk> {
|
||||
self.snapshot
|
||||
.hunks_intersecting_range(range, buffer_snapshot)
|
||||
}
|
||||
|
||||
pub fn diff_hunks_intersecting_range_rev<'a>(
|
||||
&'a self,
|
||||
range: Range<text::Anchor>,
|
||||
buffer_snapshot: &'a text::BufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk> {
|
||||
self.snapshot
|
||||
.hunks_intersecting_range_rev(range, buffer_snapshot)
|
||||
}
|
||||
|
||||
/// Used in cases where the change set isn't derived from git.
|
||||
pub fn set_base_text(
|
||||
&mut self,
|
||||
base_buffer: Entity<language::Buffer>,
|
||||
buffer: text::BufferSnapshot,
|
||||
cx: &mut Context<Self>,
|
||||
) -> oneshot::Receiver<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let this = cx.weak_entity();
|
||||
let base_buffer = base_buffer.read(cx);
|
||||
let language_registry = base_buffer.language_registry();
|
||||
let base_buffer = base_buffer.snapshot();
|
||||
let base_text = Arc::new(base_buffer.text());
|
||||
|
||||
let snapshot = BufferDiffSnapshot::build(
|
||||
buffer.clone(),
|
||||
Some(base_text),
|
||||
base_buffer.language().cloned(),
|
||||
language_registry,
|
||||
cx,
|
||||
);
|
||||
let complete_on_drop = util::defer(|| {
|
||||
tx.send(()).ok();
|
||||
});
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let snapshot = snapshot.await;
|
||||
let Some(this) = this.upgrade() else {
|
||||
return;
|
||||
};
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.set_state(snapshot, &buffer, cx);
|
||||
})
|
||||
.log_err();
|
||||
drop(complete_on_drop)
|
||||
})
|
||||
.detach();
|
||||
rx
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn base_text_string(&self) -> Option<String> {
|
||||
self.snapshot.base_text.as_ref().map(|buffer| buffer.text())
|
||||
}
|
||||
|
||||
pub fn new(buffer: &Entity<language::Buffer>, cx: &mut App) -> Self {
|
||||
BufferDiff {
|
||||
buffer_id: buffer.read(cx).remote_id(),
|
||||
snapshot: BufferDiffSnapshot::new(&buffer.read(cx)),
|
||||
unstaged_diff: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn new_with_base_text(
|
||||
base_text: &str,
|
||||
buffer: &Entity<language::Buffer>,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let mut base_text = base_text.to_owned();
|
||||
text::LineEnding::normalize(&mut base_text);
|
||||
let snapshot = BufferDiffSnapshot::build(
|
||||
buffer.read(cx).text_snapshot(),
|
||||
Some(base_text.into()),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
let snapshot = cx.background_executor().block(snapshot);
|
||||
BufferDiff {
|
||||
buffer_id: buffer.read(cx).remote_id(),
|
||||
snapshot,
|
||||
unstaged_diff: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context<Self>) {
|
||||
let base_text = self
|
||||
.snapshot
|
||||
.base_text
|
||||
.as_ref()
|
||||
.map(|base_text| base_text.text());
|
||||
let snapshot = BufferDiffSnapshot::build_with_base_buffer(
|
||||
buffer.clone(),
|
||||
base_text.clone().map(Arc::new),
|
||||
self.snapshot.base_text.clone(),
|
||||
cx,
|
||||
);
|
||||
let snapshot = cx.background_executor().block(snapshot);
|
||||
self.set_state(snapshot, &buffer, cx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Range (crossing new lines), old, new
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[track_caller]
|
||||
pub fn assert_hunks<Iter>(
|
||||
diff_hunks: Iter,
|
||||
buffer: &text::BufferSnapshot,
|
||||
diff_base: &str,
|
||||
expected_hunks: &[(Range<u32>, &str, &str)],
|
||||
) where
|
||||
Iter: Iterator<Item = DiffHunk>,
|
||||
{
|
||||
let actual_hunks = diff_hunks
|
||||
.map(|hunk| {
|
||||
(
|
||||
hunk.row_range.clone(),
|
||||
&diff_base[hunk.diff_base_byte_range],
|
||||
buffer
|
||||
.text_for_range(
|
||||
Point::new(hunk.row_range.start, 0)..Point::new(hunk.row_range.end, 0),
|
||||
)
|
||||
.collect::<String>(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let expected_hunks: Vec<_> = expected_hunks
|
||||
.iter()
|
||||
.map(|(r, s, h)| (r.clone(), *s, h.to_string()))
|
||||
.collect();
|
||||
|
||||
assert_eq!(actual_hunks, expected_hunks);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::assert_eq;
|
||||
|
||||
use super::*;
|
||||
use gpui::TestAppContext;
|
||||
use text::{Buffer, BufferId};
|
||||
use unindent::Unindent as _;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
|
||||
let diff_base = "
|
||||
one
|
||||
two
|
||||
three
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer_text = "
|
||||
one
|
||||
HELLO
|
||||
three
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
|
||||
let mut diff = BufferDiffSnapshot::build_sync(buffer.clone(), diff_base.clone(), cx);
|
||||
assert_hunks(
|
||||
diff.hunks(&buffer),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(1..2, "two\n", "HELLO\n")],
|
||||
);
|
||||
|
||||
buffer.edit([(0..0, "point five\n")]);
|
||||
diff = BufferDiffSnapshot::build_sync(buffer.clone(), diff_base.clone(), cx);
|
||||
assert_hunks(
|
||||
diff.hunks(&buffer),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(0..1, "", "point five\n"), (2..3, "two\n", "HELLO\n")],
|
||||
);
|
||||
|
||||
diff.clear(&buffer);
|
||||
assert_hunks(diff.hunks(&buffer), &buffer, &diff_base, &[]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_buffer_diff_range(cx: &mut TestAppContext) {
|
||||
let diff_base = Arc::new(
|
||||
"
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
seven
|
||||
eight
|
||||
nine
|
||||
ten
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
let buffer_text = "
|
||||
A
|
||||
one
|
||||
B
|
||||
two
|
||||
C
|
||||
three
|
||||
HELLO
|
||||
four
|
||||
five
|
||||
SIXTEEN
|
||||
seven
|
||||
eight
|
||||
WORLD
|
||||
nine
|
||||
|
||||
ten
|
||||
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
|
||||
let diff = cx
|
||||
.update(|cx| {
|
||||
BufferDiffSnapshot::build(
|
||||
buffer.snapshot(),
|
||||
Some(diff_base.clone()),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await;
|
||||
assert_eq!(diff.hunks(&buffer).count(), 8);
|
||||
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(7..12, &buffer),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[
|
||||
(6..7, "", "HELLO\n"),
|
||||
(9..10, "six\n", "SIXTEEN\n"),
|
||||
(12..13, "", "WORLD\n"),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
|
||||
let base_text = "
|
||||
zero
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
seven
|
||||
eight
|
||||
nine
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer_text_1 = "
|
||||
one
|
||||
three
|
||||
four
|
||||
five
|
||||
SIX
|
||||
seven
|
||||
eight
|
||||
NINE
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1);
|
||||
|
||||
let empty_diff = BufferDiffSnapshot::new(&buffer);
|
||||
let diff_1 = BufferDiffSnapshot::build_sync(buffer.clone(), base_text.clone(), cx);
|
||||
let range = diff_1.compare(&empty_diff, &buffer).unwrap();
|
||||
assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
|
||||
|
||||
// Edit does not affect the diff.
|
||||
buffer.edit_via_marked_text(
|
||||
&"
|
||||
one
|
||||
three
|
||||
four
|
||||
five
|
||||
«SIX.5»
|
||||
seven
|
||||
eight
|
||||
NINE
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
let diff_2 = BufferDiffSnapshot::build_sync(buffer.clone(), base_text.clone(), cx);
|
||||
assert_eq!(None, diff_2.compare(&diff_1, &buffer));
|
||||
|
||||
// Edit turns a deletion hunk into a modification.
|
||||
buffer.edit_via_marked_text(
|
||||
&"
|
||||
one
|
||||
«THREE»
|
||||
four
|
||||
five
|
||||
SIX.5
|
||||
seven
|
||||
eight
|
||||
NINE
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
let diff_3 = BufferDiffSnapshot::build_sync(buffer.clone(), base_text.clone(), cx);
|
||||
let range = diff_3.compare(&diff_2, &buffer).unwrap();
|
||||
assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
|
||||
|
||||
// Edit turns a modification hunk into a deletion.
|
||||
buffer.edit_via_marked_text(
|
||||
&"
|
||||
one
|
||||
THREE
|
||||
four
|
||||
five«»
|
||||
seven
|
||||
eight
|
||||
NINE
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
let diff_4 = BufferDiffSnapshot::build_sync(buffer.clone(), base_text.clone(), cx);
|
||||
let range = diff_4.compare(&diff_3, &buffer).unwrap();
|
||||
assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
|
||||
|
||||
// Edit introduces a new insertion hunk.
|
||||
buffer.edit_via_marked_text(
|
||||
&"
|
||||
one
|
||||
THREE
|
||||
four«
|
||||
FOUR.5
|
||||
»five
|
||||
seven
|
||||
eight
|
||||
NINE
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
let diff_5 = BufferDiffSnapshot::build_sync(buffer.snapshot(), base_text.clone(), cx);
|
||||
let range = diff_5.compare(&diff_4, &buffer).unwrap();
|
||||
assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
|
||||
|
||||
// Edit removes a hunk.
|
||||
buffer.edit_via_marked_text(
|
||||
&"
|
||||
one
|
||||
THREE
|
||||
four
|
||||
FOUR.5
|
||||
five
|
||||
seven
|
||||
eight
|
||||
«nine»
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
let diff_6 = BufferDiffSnapshot::build_sync(buffer.snapshot(), base_text, cx);
|
||||
let range = diff_6.compare(&diff_5, &buffer).unwrap();
|
||||
assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,14 @@ impl Template for KeybindingTemplate {
|
||||
|
||||
fn render(&self, context: &PreprocessorContext, args: &HashMap<String, String>) -> String {
|
||||
let action = args.get("action").map(String::as_str).unwrap_or("");
|
||||
let macos_binding = context.find_binding("macos", action).unwrap_or_default();
|
||||
let linux_binding = context.find_binding("linux", action).unwrap_or_default();
|
||||
let macos_binding = context
|
||||
.find_binding("macos", action)
|
||||
.unwrap_or_default()
|
||||
.replace("\\", "\");
|
||||
let linux_binding = context
|
||||
.find_binding("linux", action)
|
||||
.unwrap_or_default()
|
||||
.replace("\\", "\");
|
||||
format!("<kbd class=\"keybinding\">{macos_binding}|{linux_binding}</kbd>")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ clock.workspace = true
|
||||
collections.workspace = true
|
||||
convert_case.workspace = true
|
||||
db.workspace = true
|
||||
diff.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
emojis.workspace = true
|
||||
file_icons.workspace = true
|
||||
futures.workspace = true
|
||||
@@ -49,6 +49,7 @@ gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
indoc.workspace = true
|
||||
inline_completion.workspace = true
|
||||
inventory.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
linkify.workspace = true
|
||||
|
||||
@@ -3,56 +3,64 @@ 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)]
|
||||
@@ -61,24 +69,28 @@ 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,
|
||||
@@ -87,60 +99,70 @@ 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>,
|
||||
@@ -150,23 +172,24 @@ 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 level: u32,
|
||||
}
|
||||
pub struct FoldAtLevel(pub u32);
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SpawnNearestTask {
|
||||
#[serde(default)]
|
||||
pub reveal: task::RevealStrategy,
|
||||
@@ -216,9 +239,9 @@ impl_actions!(
|
||||
gpui::actions!(
|
||||
editor,
|
||||
[
|
||||
AcceptInlineCompletion,
|
||||
AcceptEditPrediction,
|
||||
AcceptPartialCopilotSuggestion,
|
||||
AcceptPartialInlineCompletion,
|
||||
AcceptPartialEditPrediction,
|
||||
AddSelectionAbove,
|
||||
AddSelectionBelow,
|
||||
ApplyAllDiffHunks,
|
||||
@@ -242,6 +265,8 @@ gpui::actions!(
|
||||
Copy,
|
||||
CopyFileLocation,
|
||||
CopyHighlightJson,
|
||||
CopyFileName,
|
||||
CopyFileNameWithoutExtension,
|
||||
CopyPath,
|
||||
CopyPermalinkToLine,
|
||||
CopyRelativePath,
|
||||
@@ -310,7 +335,7 @@ gpui::actions!(
|
||||
Newline,
|
||||
NewlineAbove,
|
||||
NewlineBelow,
|
||||
NextInlineCompletion,
|
||||
NextEditPrediction,
|
||||
NextScreen,
|
||||
OpenContextMenu,
|
||||
OpenExcerpts,
|
||||
@@ -325,7 +350,7 @@ gpui::actions!(
|
||||
PageDown,
|
||||
PageUp,
|
||||
Paste,
|
||||
PreviousInlineCompletion,
|
||||
PreviousEditPrediction,
|
||||
Redo,
|
||||
RedoSelection,
|
||||
Rename,
|
||||
@@ -361,7 +386,7 @@ gpui::actions!(
|
||||
SelectToStartOfParagraph,
|
||||
SelectUp,
|
||||
ShowCharacterPalette,
|
||||
ShowInlineCompletion,
|
||||
ShowEditPrediction,
|
||||
ShowSignatureHelp,
|
||||
ShuffleLines,
|
||||
SortLinesCaseInsensitive,
|
||||
@@ -375,8 +400,9 @@ gpui::actions!(
|
||||
ToggleGitBlameInline,
|
||||
ToggleIndentGuides,
|
||||
ToggleInlayHints,
|
||||
ToggleInlineCompletions,
|
||||
ToggleEditPrediction,
|
||||
ToggleLineNumbers,
|
||||
ToggleStagedSelectedDiffHunks,
|
||||
SwapSelectionEnds,
|
||||
SetMark,
|
||||
ToggleRelativeLineNumbers,
|
||||
|
||||
@@ -517,7 +517,6 @@ impl CompletionsMenu {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let color_swatch = completion
|
||||
.color()
|
||||
.map(|color| div().size_4().bg(color).rounded_sm());
|
||||
|
||||
@@ -1,28 +1,48 @@
|
||||
use futures::Future;
|
||||
use git::blame::BlameEntry;
|
||||
use git::Oid;
|
||||
use git::PullRequest;
|
||||
use gpui::{
|
||||
App, Asset, ClipboardItem, Element, ParentElement, Render, ScrollHandle,
|
||||
StatefulInteractiveElement, WeakEntity,
|
||||
};
|
||||
use language::ParsedMarkdown;
|
||||
use settings::Settings;
|
||||
use std::hash::Hash;
|
||||
use theme::ThemeSettings;
|
||||
use time::UtcOffset;
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use time_format::format_local_timestamp;
|
||||
use ui::{prelude::*, tooltip_container, Avatar, Divider, IconButtonShape};
|
||||
use url::Url;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::git::blame::{CommitDetails, GitRemote};
|
||||
use crate::git::blame::GitRemote;
|
||||
use crate::EditorStyle;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommitDetails {
|
||||
pub sha: SharedString,
|
||||
pub committer_name: SharedString,
|
||||
pub committer_email: SharedString,
|
||||
pub commit_time: OffsetDateTime,
|
||||
pub message: Option<ParsedCommitMessage>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ParsedCommitMessage {
|
||||
pub message: SharedString,
|
||||
pub parsed_message: ParsedMarkdown,
|
||||
pub permalink: Option<Url>,
|
||||
pub pull_request: Option<PullRequest>,
|
||||
pub remote: Option<GitRemote>,
|
||||
}
|
||||
|
||||
struct CommitAvatar<'a> {
|
||||
details: Option<&'a CommitDetails>,
|
||||
sha: Oid,
|
||||
commit: &'a CommitDetails,
|
||||
}
|
||||
|
||||
impl<'a> CommitAvatar<'a> {
|
||||
fn new(details: Option<&'a CommitDetails>, sha: Oid) -> Self {
|
||||
Self { details, sha }
|
||||
fn new(details: &'a CommitDetails) -> Self {
|
||||
Self { commit: details }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,14 +50,16 @@ impl<'a> CommitAvatar<'a> {
|
||||
fn render(
|
||||
&'a self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<BlameEntryTooltip>,
|
||||
cx: &mut Context<CommitTooltip>,
|
||||
) -> Option<impl IntoElement> {
|
||||
let remote = self
|
||||
.details
|
||||
.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.and_then(|details| details.remote.as_ref())
|
||||
.filter(|remote| remote.host_supports_avatars())?;
|
||||
|
||||
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.sha);
|
||||
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.commit.sha.clone());
|
||||
|
||||
let element = match window.use_asset::<CommitAvatarAsset>(&avatar_url, cx) {
|
||||
// Loading or no avatar found
|
||||
@@ -54,7 +76,7 @@ impl<'a> CommitAvatar<'a> {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct CommitAvatarAsset {
|
||||
sha: Oid,
|
||||
sha: SharedString,
|
||||
remote: GitRemote,
|
||||
}
|
||||
|
||||
@@ -66,7 +88,7 @@ impl Hash for CommitAvatarAsset {
|
||||
}
|
||||
|
||||
impl CommitAvatarAsset {
|
||||
fn new(remote: GitRemote, sha: Oid) -> Self {
|
||||
fn new(remote: GitRemote, sha: SharedString) -> Self {
|
||||
Self { remote, sha }
|
||||
}
|
||||
}
|
||||
@@ -91,50 +113,78 @@ impl Asset for CommitAvatarAsset {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BlameEntryTooltip {
|
||||
blame_entry: BlameEntry,
|
||||
details: Option<CommitDetails>,
|
||||
pub struct CommitTooltip {
|
||||
commit: CommitDetails,
|
||||
editor_style: EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
scroll_handle: ScrollHandle,
|
||||
}
|
||||
|
||||
impl BlameEntryTooltip {
|
||||
pub(crate) fn new(
|
||||
blame_entry: BlameEntry,
|
||||
details: Option<CommitDetails>,
|
||||
style: &EditorStyle,
|
||||
impl CommitTooltip {
|
||||
pub fn blame_entry(
|
||||
blame: BlameEntry,
|
||||
details: Option<ParsedCommitMessage>,
|
||||
style: EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
) -> Self {
|
||||
let commit_time = blame
|
||||
.committer_time
|
||||
.and_then(|t| OffsetDateTime::from_unix_timestamp(t).ok())
|
||||
.unwrap_or(OffsetDateTime::now_utc());
|
||||
Self::new(
|
||||
CommitDetails {
|
||||
sha: blame.sha.to_string().into(),
|
||||
commit_time,
|
||||
committer_name: blame
|
||||
.committer_name
|
||||
.unwrap_or("<no name>".to_string())
|
||||
.into(),
|
||||
committer_email: blame.committer_email.unwrap_or("".to_string()).into(),
|
||||
message: details,
|
||||
},
|
||||
style,
|
||||
workspace,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
commit: CommitDetails,
|
||||
editor_style: EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
editor_style: style.clone(),
|
||||
blame_entry,
|
||||
details,
|
||||
editor_style,
|
||||
commit,
|
||||
workspace,
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for BlameEntryTooltip {
|
||||
impl Render for CommitTooltip {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let avatar =
|
||||
CommitAvatar::new(self.details.as_ref(), self.blame_entry.sha).render(window, cx);
|
||||
let avatar = CommitAvatar::new(&self.commit).render(window, cx);
|
||||
|
||||
let author = self
|
||||
.blame_entry
|
||||
.author
|
||||
.clone()
|
||||
.unwrap_or("<no name>".to_string());
|
||||
let author = self.commit.committer_name.clone();
|
||||
|
||||
let author_email = self.blame_entry.author_mail.clone();
|
||||
let author_email = self.commit.committer_email.clone();
|
||||
|
||||
let short_commit_id = self.blame_entry.sha.display_short();
|
||||
let full_sha = self.blame_entry.sha.to_string().clone();
|
||||
let absolute_timestamp = blame_entry_absolute_timestamp(&self.blame_entry);
|
||||
let short_commit_id = self
|
||||
.commit
|
||||
.sha
|
||||
.get(0..8)
|
||||
.map(|sha| sha.to_string().into())
|
||||
.unwrap_or_else(|| self.commit.sha.clone());
|
||||
let full_sha = self.commit.sha.to_string().clone();
|
||||
let absolute_timestamp = format_local_timestamp(
|
||||
self.commit.commit_time,
|
||||
OffsetDateTime::now_utc(),
|
||||
time_format::TimestampFormat::MediumAbsolute,
|
||||
);
|
||||
|
||||
let message = self
|
||||
.details
|
||||
.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.map(|details| {
|
||||
crate::render_parsed_markdown(
|
||||
@@ -149,7 +199,8 @@ impl Render for BlameEntryTooltip {
|
||||
.unwrap_or("<no commit message>".into_any());
|
||||
|
||||
let pull_request = self
|
||||
.details
|
||||
.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.and_then(|details| details.pull_request.clone());
|
||||
|
||||
@@ -171,7 +222,7 @@ impl Render for BlameEntryTooltip {
|
||||
.flex_wrap()
|
||||
.children(avatar)
|
||||
.child(author)
|
||||
.when_some(author_email, |this, author_email| {
|
||||
.when(!author_email.is_empty(), |this| {
|
||||
this.child(
|
||||
div()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
@@ -231,12 +282,16 @@ impl Render for BlameEntryTooltip {
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.disabled(
|
||||
self.details.as_ref().map_or(true, |details| {
|
||||
details.permalink.is_none()
|
||||
}),
|
||||
self.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.map_or(true, |details| {
|
||||
details.permalink.is_none()
|
||||
}),
|
||||
)
|
||||
.when_some(
|
||||
self.details
|
||||
self.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.and_then(|details| details.permalink.clone()),
|
||||
|this, url| {
|
||||
@@ -284,7 +339,3 @@ fn blame_entry_timestamp(blame_entry: &BlameEntry, format: time_format::Timestam
|
||||
pub fn blame_entry_relative_timestamp(blame_entry: &BlameEntry) -> String {
|
||||
blame_entry_timestamp(blame_entry, time_format::TimestampFormat::Relative)
|
||||
}
|
||||
|
||||
fn blame_entry_absolute_timestamp(blame_entry: &BlameEntry) -> String {
|
||||
blame_entry_timestamp(blame_entry, time_format::TimestampFormat::MediumAbsolute)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,6 @@ pub struct EditorSettings {
|
||||
pub auto_signature_help: bool,
|
||||
pub show_signature_help_after_edits: bool,
|
||||
pub jupyter: Jupyter,
|
||||
pub show_inline_completions_in_menu: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
@@ -368,12 +367,6 @@ pub struct EditorSettingsContent {
|
||||
/// Default: false
|
||||
pub show_signature_help_after_edits: Option<bool>,
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Default: true
|
||||
pub show_inline_completions_in_menu: Option<bool>,
|
||||
|
||||
/// Jupyter REPL settings.
|
||||
pub jupyter: Option<JupyterContent>,
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
},
|
||||
JoinLines,
|
||||
};
|
||||
use diff::{BufferDiff, DiffHunkStatus};
|
||||
use buffer_diff::{BufferDiff, DiffHunkStatus};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
|
||||
@@ -1159,7 +1159,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
editor.fold_at_level(&FoldAtLevel { level: 2 }, window, cx);
|
||||
editor.fold_at_level(&FoldAtLevel(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 { level: 1 }, window, cx);
|
||||
editor.fold_at_level(&FoldAtLevel(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 { level: 0 }, window, cx);
|
||||
editor.fold_at_level(&FoldAtLevel(0), window, cx);
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
"
|
||||
@@ -5362,6 +5362,21 @@ async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
|
||||
cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_select_previous_empty_buffer(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_state("aˇ");
|
||||
|
||||
cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
|
||||
.unwrap();
|
||||
cx.assert_editor_state("«aˇ»");
|
||||
cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
|
||||
.unwrap();
|
||||
cx.assert_editor_state("«aˇ»");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -9711,7 +9726,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
|
||||
&r#"
|
||||
<!-- ˇ<script> -->
|
||||
// ˇvar x = new Y();
|
||||
// ˇ</script>
|
||||
<!-- ˇ</script> -->
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
@@ -10653,6 +10668,176 @@ 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, |_| {});
|
||||
@@ -11989,7 +12174,7 @@ async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
|
||||
struct Row9.2;
|
||||
struct Row9.3;
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
|
||||
vec![DiffHunkStatus::added(), DiffHunkStatus::added()],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row1.1;
|
||||
@@ -12027,7 +12212,7 @@ async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
|
||||
vec![DiffHunkStatus::added(), DiffHunkStatus::added()],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
@@ -12074,11 +12259,11 @@ async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
|
||||
«ˇ// something on bottom»
|
||||
struct Row10;"#},
|
||||
vec![
|
||||
DiffHunkStatus::Added,
|
||||
DiffHunkStatus::Added,
|
||||
DiffHunkStatus::Added,
|
||||
DiffHunkStatus::Added,
|
||||
DiffHunkStatus::Added,
|
||||
DiffHunkStatus::added(),
|
||||
DiffHunkStatus::added(),
|
||||
DiffHunkStatus::added(),
|
||||
DiffHunkStatus::added(),
|
||||
DiffHunkStatus::added(),
|
||||
],
|
||||
indoc! {r#"struct Row;
|
||||
ˇstruct Row1;
|
||||
@@ -12126,7 +12311,7 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
|
||||
struct Row99;
|
||||
struct Row9;
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
|
||||
vec![DiffHunkStatus::modified(), DiffHunkStatus::modified()],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row33;
|
||||
@@ -12153,7 +12338,7 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
|
||||
struct Row99;
|
||||
struct Row9;
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
|
||||
vec![DiffHunkStatus::modified(), DiffHunkStatus::modified()],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row33;
|
||||
@@ -12182,12 +12367,12 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
|
||||
struct Row9;
|
||||
struct Row1011;ˇ"#},
|
||||
vec![
|
||||
DiffHunkStatus::Modified,
|
||||
DiffHunkStatus::Modified,
|
||||
DiffHunkStatus::Modified,
|
||||
DiffHunkStatus::Modified,
|
||||
DiffHunkStatus::Modified,
|
||||
DiffHunkStatus::Modified,
|
||||
DiffHunkStatus::modified(),
|
||||
DiffHunkStatus::modified(),
|
||||
DiffHunkStatus::modified(),
|
||||
DiffHunkStatus::modified(),
|
||||
DiffHunkStatus::modified(),
|
||||
DiffHunkStatus::modified(),
|
||||
],
|
||||
indoc! {r#"struct Row;
|
||||
ˇstruct Row1;
|
||||
@@ -12265,7 +12450,7 @@ struct Row10;"#};
|
||||
ˇ
|
||||
struct Row8;
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
|
||||
vec![DiffHunkStatus::removed(), DiffHunkStatus::removed()],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row2;
|
||||
|
||||
@@ -12288,7 +12473,7 @@ struct Row10;"#};
|
||||
ˇ»
|
||||
struct Row8;
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
|
||||
vec![DiffHunkStatus::removed(), DiffHunkStatus::removed()],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row2;
|
||||
|
||||
@@ -12313,7 +12498,7 @@ struct Row10;"#};
|
||||
|
||||
struct Row8;ˇ
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
|
||||
vec![DiffHunkStatus::removed(), DiffHunkStatus::removed()],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
ˇstruct Row2;
|
||||
@@ -12338,9 +12523,9 @@ struct Row10;"#};
|
||||
struct Row8;ˇ»
|
||||
struct Row10;"#},
|
||||
vec![
|
||||
DiffHunkStatus::Removed,
|
||||
DiffHunkStatus::Removed,
|
||||
DiffHunkStatus::Removed,
|
||||
DiffHunkStatus::removed(),
|
||||
DiffHunkStatus::removed(),
|
||||
DiffHunkStatus::removed(),
|
||||
],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
@@ -13862,6 +14047,59 @@ async fn test_edit_after_expanded_modification_hunk(
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_stage_and_unstage_added_file_hunk(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
editor.set_expand_all_diff_hunks(cx);
|
||||
});
|
||||
|
||||
let working_copy = r#"
|
||||
ˇfn main() {
|
||||
println!("hello, world!");
|
||||
}
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
cx.set_state(&working_copy);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
+ ˇfn main() {
|
||||
+ println!("hello, world!");
|
||||
+ }
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
cx.assert_index_text(None);
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_staged_selected_diff_hunks(&ToggleStagedSelectedDiffHunks, window, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
+ ˇfn main() {
|
||||
+ println!("hello, world!");
|
||||
+ }
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_staged_selected_diff_hunks(&ToggleStagedSelectedDiffHunks, window, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_index_text(None);
|
||||
}
|
||||
|
||||
async fn setup_indent_guides_editor(
|
||||
text: &str,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
@@ -14875,7 +15113,7 @@ async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
path!("/a"),
|
||||
json!({
|
||||
"first.rs": sample_text_1,
|
||||
"second.rs": sample_text_2,
|
||||
@@ -14883,7 +15121,7 @@ async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
let project = Project::test(fs, [path!("/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| {
|
||||
@@ -15059,7 +15297,7 @@ async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
path!("/a"),
|
||||
json!({
|
||||
"first.rs": sample_text_1,
|
||||
"second.rs": sample_text_2,
|
||||
@@ -15067,7 +15305,7 @@ async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
let project = Project::test(fs, [path!("/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| {
|
||||
@@ -15206,13 +15444,13 @@ async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppCon
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
path!("/a"),
|
||||
json!({
|
||||
"main.rs": sample_text,
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
let project = Project::test(fs, [path!("/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| {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use git::{
|
||||
blame::{Blame, BlameEntry},
|
||||
parse_git_remote_url, GitHostingProvider, GitHostingProviderRegistry, Oid, PullRequest,
|
||||
parse_git_remote_url, GitHostingProvider, GitHostingProviderRegistry, Oid,
|
||||
};
|
||||
use gpui::{App, Context, Entity, Subscription, Task};
|
||||
use http_client::HttpClient;
|
||||
@@ -12,8 +12,11 @@ use project::{Project, ProjectItem};
|
||||
use smallvec::SmallVec;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use sum_tree::SumTree;
|
||||
use ui::SharedString;
|
||||
use url::Url;
|
||||
|
||||
use crate::commit_tooltip::ParsedCommitMessage;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GitBlameEntry {
|
||||
pub rows: u32,
|
||||
@@ -77,7 +80,11 @@ impl GitRemote {
|
||||
self.host.supports_avatars()
|
||||
}
|
||||
|
||||
pub async fn avatar_url(&self, commit: Oid, client: Arc<dyn HttpClient>) -> Option<Url> {
|
||||
pub async fn avatar_url(
|
||||
&self,
|
||||
commit: SharedString,
|
||||
client: Arc<dyn HttpClient>,
|
||||
) -> Option<Url> {
|
||||
self.host
|
||||
.commit_author_avatar_url(&self.owner, &self.repo, commit, client)
|
||||
.await
|
||||
@@ -85,21 +92,11 @@ impl GitRemote {
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommitDetails {
|
||||
pub message: String,
|
||||
pub parsed_message: ParsedMarkdown,
|
||||
pub permalink: Option<Url>,
|
||||
pub pull_request: Option<PullRequest>,
|
||||
pub remote: Option<GitRemote>,
|
||||
}
|
||||
|
||||
pub struct GitBlame {
|
||||
project: Entity<Project>,
|
||||
buffer: Entity<Buffer>,
|
||||
entries: SumTree<GitBlameEntry>,
|
||||
commit_details: HashMap<Oid, CommitDetails>,
|
||||
commit_details: HashMap<Oid, crate::commit_tooltip::ParsedCommitMessage>,
|
||||
buffer_snapshot: BufferSnapshot,
|
||||
buffer_edits: text::Subscription,
|
||||
task: Task<Result<()>>,
|
||||
@@ -187,7 +184,7 @@ impl GitBlame {
|
||||
self.generated
|
||||
}
|
||||
|
||||
pub fn details_for_entry(&self, entry: &BlameEntry) -> Option<CommitDetails> {
|
||||
pub fn details_for_entry(&self, entry: &BlameEntry) -> Option<ParsedCommitMessage> {
|
||||
self.commit_details.get(&entry.sha).cloned()
|
||||
}
|
||||
|
||||
@@ -480,7 +477,7 @@ async fn parse_commit_messages(
|
||||
deprecated_permalinks: &HashMap<Oid, Url>,
|
||||
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
languages: &Arc<LanguageRegistry>,
|
||||
) -> HashMap<Oid, CommitDetails> {
|
||||
) -> HashMap<Oid, ParsedCommitMessage> {
|
||||
let mut commit_details = HashMap::default();
|
||||
|
||||
let parsed_remote_url = remote_url
|
||||
@@ -519,8 +516,8 @@ async fn parse_commit_messages(
|
||||
|
||||
commit_details.insert(
|
||||
oid,
|
||||
CommitDetails {
|
||||
message,
|
||||
ParsedCommitMessage {
|
||||
message: message.into(),
|
||||
parsed_message,
|
||||
permalink,
|
||||
remote,
|
||||
|
||||
@@ -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,8 +885,10 @@ mod tests {
|
||||
let slice = data;
|
||||
|
||||
for (range, event) in slice.iter() {
|
||||
if [MarkdownEvent::Text, MarkdownEvent::Code].contains(event) {
|
||||
rendered_text.push_str(&text[range.clone()])
|
||||
match event {
|
||||
MarkdownEvent::Text(parsed) => rendered_text.push_str(parsed),
|
||||
MarkdownEvent::Code => 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(
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new(
|
||||
"toggle_editor_selections_icon",
|
||||
IconName::EllipsisVertical,
|
||||
@@ -774,19 +774,8 @@ 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,10 +1,9 @@
|
||||
use gpui::{prelude::*, Entity};
|
||||
use indoc::indoc;
|
||||
use inline_completion::InlineCompletionProvider;
|
||||
use language::{Language, LanguageConfig};
|
||||
use inline_completion::EditPredictionProvider;
|
||||
use multi_buffer::{Anchor, MultiBufferSnapshot, ToPoint};
|
||||
use project::Project;
|
||||
use std::{num::NonZeroU32, ops::Range, sync::Arc};
|
||||
use std::ops::Range;
|
||||
use text::{Point, ToOffset};
|
||||
|
||||
use crate::{
|
||||
@@ -124,54 +123,6 @@ async fn test_inline_completion_jump_button(cx: &mut gpui::TestAppContext) {
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_indentation(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |settings| {
|
||||
settings.defaults.tab_size = NonZeroU32::new(4)
|
||||
});
|
||||
|
||||
let language = Arc::new(
|
||||
Language::new(
|
||||
LanguageConfig::default(),
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
)
|
||||
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
let provider = cx.new(|_| FakeInlineCompletionProvider::default());
|
||||
assign_editor_completion_provider(provider.clone(), &mut cx);
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
const a: A = (
|
||||
ˇ
|
||||
);
|
||||
"});
|
||||
|
||||
propose_edits(
|
||||
&provider,
|
||||
vec![(Point::new(1, 0)..Point::new(1, 0), " const function()")],
|
||||
&mut cx,
|
||||
);
|
||||
cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
|
||||
|
||||
assert_editor_active_edit_completion(&mut cx, |_, edits| {
|
||||
assert_eq!(edits.len(), 1);
|
||||
assert_eq!(edits[0].1.as_str(), " const function()");
|
||||
});
|
||||
|
||||
// When the cursor is before the suggested indentation level, accepting a
|
||||
// completion should just indent.
|
||||
accept_completion(&mut cx);
|
||||
cx.assert_editor_state(indoc! {"
|
||||
const a: A = (
|
||||
ˇ
|
||||
);
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_inline_completion_invalidation_range(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -315,7 +266,7 @@ fn assert_editor_active_move_completion(
|
||||
|
||||
fn accept_completion(cx: &mut EditorTestContext) {
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.accept_inline_completion(&crate::AcceptInlineCompletion, window, cx)
|
||||
editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -333,6 +284,7 @@ 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,
|
||||
}))
|
||||
@@ -345,7 +297,7 @@ fn assign_editor_completion_provider(
|
||||
cx: &mut EditorTestContext,
|
||||
) {
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.set_inline_completion_provider(Some(provider), window, cx);
|
||||
editor.set_edit_prediction_provider(Some(provider), window, cx);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -363,7 +315,7 @@ impl FakeInlineCompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl InlineCompletionProvider for FakeInlineCompletionProvider {
|
||||
impl EditPredictionProvider for FakeInlineCompletionProvider {
|
||||
fn name() -> &'static str {
|
||||
"fake-completion-provider"
|
||||
}
|
||||
@@ -376,10 +328,6 @@ impl InlineCompletionProvider for FakeInlineCompletionProvider {
|
||||
false
|
||||
}
|
||||
|
||||
fn show_completions_in_normal_mode() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
_buffer: &gpui::Entity<language::Buffer>,
|
||||
|
||||
@@ -36,7 +36,7 @@ use std::{
|
||||
};
|
||||
use text::{BufferId, Selection};
|
||||
use theme::{Theme, ThemeSettings};
|
||||
use ui::{h_flex, prelude::*, IconDecorationKind, Label};
|
||||
use ui::{prelude::*, IconDecorationKind};
|
||||
use util::{paths::PathExt, ResultExt, TryFutureExt};
|
||||
use workspace::item::{Dedup, ItemSettings, SerializableItem, TabContentParams};
|
||||
use workspace::{
|
||||
@@ -679,8 +679,8 @@ impl Item for Editor {
|
||||
.child(
|
||||
Label::new(self.title(cx).to_string())
|
||||
.color(label_color)
|
||||
.italic(params.preview)
|
||||
.strikethrough(was_deleted),
|
||||
.when(params.preview, |this| this.italic())
|
||||
.when(was_deleted, |this| this.strikethrough()),
|
||||
)
|
||||
.when_some(description, |this, description| {
|
||||
this.child(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SemanticsProvider};
|
||||
use buffer_diff::BufferDiff;
|
||||
use collections::HashSet;
|
||||
use diff::BufferDiff;
|
||||
use futures::{channel::mpsc, future::join_all};
|
||||
use gpui::{App, Entity, EventEmitter, Focusable, Render, Subscription, Task};
|
||||
use language::{Buffer, BufferEvent, Capability};
|
||||
@@ -185,7 +185,7 @@ impl ProposedChangesEditor {
|
||||
} else {
|
||||
branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx));
|
||||
new_diffs.push(cx.new(|cx| {
|
||||
let mut diff = BufferDiff::new(&branch_buffer, cx);
|
||||
let mut diff = BufferDiff::new(branch_buffer.read(cx));
|
||||
let _ = diff.set_base_text(
|
||||
location.buffer.clone(),
|
||||
branch_buffer.read(cx).text_snapshot(),
|
||||
|
||||
@@ -3,6 +3,7 @@ pub(crate) mod autoscroll;
|
||||
pub(crate) mod scroll_amount;
|
||||
|
||||
use crate::editor_settings::{ScrollBeyondLastLine, ScrollbarAxes};
|
||||
use crate::EditPredictionPreview;
|
||||
use crate::{
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
hover_popover::hide_hover,
|
||||
@@ -495,6 +496,15 @@ impl Editor {
|
||||
hide_hover(self, cx);
|
||||
let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
|
||||
|
||||
if let EditPredictionPreview::Active {
|
||||
previous_scroll_position,
|
||||
} = &mut self.edit_prediction_preview
|
||||
{
|
||||
if !autoscroll {
|
||||
previous_scroll_position.take();
|
||||
}
|
||||
}
|
||||
|
||||
self.scroll_manager.set_scroll_position(
|
||||
scroll_position,
|
||||
&display_map,
|
||||
|
||||
@@ -113,6 +113,7 @@ impl Editor {
|
||||
target_bottom = target_top + 1.;
|
||||
} else {
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
|
||||
target_top = selections
|
||||
.first()
|
||||
.unwrap()
|
||||
|
||||
@@ -2,8 +2,8 @@ use crate::{
|
||||
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
|
||||
RowExt,
|
||||
};
|
||||
use buffer_diff::DiffHunkStatus;
|
||||
use collections::BTreeMap;
|
||||
use diff::DiffHunkStatus;
|
||||
use futures::Future;
|
||||
|
||||
use gpui::{
|
||||
@@ -298,6 +298,18 @@ impl EditorTestContext {
|
||||
self.cx.run_until_parked();
|
||||
}
|
||||
|
||||
pub fn assert_index_text(&mut self, expected: Option<&str>) {
|
||||
let fs = self.update_editor(|editor, _, cx| {
|
||||
editor.project.as_ref().unwrap().read(cx).fs().as_fake()
|
||||
});
|
||||
let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
|
||||
let mut found = None;
|
||||
fs.with_git_state(&Self::root_path().join(".git"), false, |git_state| {
|
||||
found = git_state.index_contents.get(path.as_ref()).cloned();
|
||||
});
|
||||
assert_eq!(expected, found.as_deref());
|
||||
}
|
||||
|
||||
/// Change the editor's text and selections using a string containing
|
||||
/// embedded range markers that represent the ranges and directions of
|
||||
/// each selection.
|
||||
@@ -459,9 +471,9 @@ pub fn assert_state_with_diff(
|
||||
.zip(line_infos)
|
||||
.map(|(line, info)| {
|
||||
let mut marker = match info.diff_status {
|
||||
Some(DiffHunkStatus::Added) => "+ ",
|
||||
Some(DiffHunkStatus::Removed) => "- ",
|
||||
Some(DiffHunkStatus::Modified) => unreachable!(),
|
||||
Some(DiffHunkStatus::Added(_)) => "+ ",
|
||||
Some(DiffHunkStatus::Removed(_)) => "- ",
|
||||
Some(DiffHunkStatus::Modified(_)) => unreachable!(),
|
||||
None => {
|
||||
if has_diff {
|
||||
" "
|
||||
|
||||
@@ -118,6 +118,8 @@ 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 {
|
||||
@@ -185,6 +187,14 @@ 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,6 +1292,7 @@ impl ExtensionStore {
|
||||
|
||||
this.wasm_extensions.extend(wasm_extensions);
|
||||
this.proxy.reload_current_theme(cx);
|
||||
this.proxy.reload_current_icon_theme(cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
|
||||
@@ -84,7 +84,7 @@ impl HostWorktree for WasmState {
|
||||
latest::HostWorktree::which(self, delegate, binary_name).await
|
||||
}
|
||||
|
||||
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ impl HostWorktree for WasmState {
|
||||
latest::HostWorktree::which(self, delegate, binary_name).await
|
||||
}
|
||||
|
||||
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
// We only ever hand out borrows of worktrees.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ impl HostWorktree for WasmState {
|
||||
latest::HostWorktree::which(self, delegate, binary_name).await
|
||||
}
|
||||
|
||||
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
// We only ever hand out borrows of worktrees.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ impl HostKeyValueStore for WasmState {
|
||||
kv_store.insert(key, value).await.to_wasmtime_result()
|
||||
}
|
||||
|
||||
fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
|
||||
async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
|
||||
// We only ever hand out borrows of key-value stores.
|
||||
Ok(())
|
||||
}
|
||||
@@ -282,7 +282,7 @@ impl HostWorktree for WasmState {
|
||||
latest::HostWorktree::which(self, delegate, binary_name).await
|
||||
}
|
||||
|
||||
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
// We only ever hand out borrows of worktrees.
|
||||
Ok(())
|
||||
}
|
||||
@@ -350,7 +350,7 @@ impl http_client::HostHttpResponseStream for WasmState {
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
|
||||
async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ impl HostKeyValueStore for WasmState {
|
||||
kv_store.insert(key, value).await.to_wasmtime_result()
|
||||
}
|
||||
|
||||
fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
|
||||
async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
|
||||
// We only ever hand out borrows of key-value stores.
|
||||
Ok(())
|
||||
}
|
||||
@@ -275,7 +275,7 @@ impl HostProject for WasmState {
|
||||
Ok(project.worktree_ids())
|
||||
}
|
||||
|
||||
fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
|
||||
async fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
|
||||
// We only ever hand out borrows of projects.
|
||||
Ok(())
|
||||
}
|
||||
@@ -325,7 +325,7 @@ impl HostWorktree for WasmState {
|
||||
Ok(delegate.which(binary_name).await)
|
||||
}
|
||||
|
||||
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
// We only ever hand out borrows of worktrees.
|
||||
Ok(())
|
||||
}
|
||||
@@ -393,7 +393,7 @@ impl http_client::HostHttpResponseStream for WasmState {
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
|
||||
async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,11 +64,28 @@ impl FeatureFlag for PredictEditsFeatureFlag {
|
||||
const NAME: &'static str = "predict-edits";
|
||||
}
|
||||
|
||||
/// A feature flag that controls things that shouldn't go live until the predictive edits launch.
|
||||
pub struct PredictEditsLaunchFeatureFlag;
|
||||
impl FeatureFlag for PredictEditsLaunchFeatureFlag {
|
||||
const NAME: &'static str = "predict-edits-launch";
|
||||
}
|
||||
|
||||
pub struct PredictEditsRateCompletionsFeatureFlag;
|
||||
impl FeatureFlag for PredictEditsRateCompletionsFeatureFlag {
|
||||
const NAME: &'static str = "predict-edits-rate-completions";
|
||||
}
|
||||
|
||||
/// A feature flag that controls whether "non eager mode" (holding `alt` to preview) is publicized.
|
||||
pub struct PredictEditsNonEagerModeFeatureFlag;
|
||||
impl FeatureFlag for PredictEditsNonEagerModeFeatureFlag {
|
||||
const NAME: &'static str = "predict-edits-non-eager-mode";
|
||||
|
||||
fn enabled_for_staff() -> bool {
|
||||
// Don't show to staff so it doesn't leak into media for the launch.
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GitUiFeatureFlag;
|
||||
impl FeatureFlag for GitUiFeatureFlag {
|
||||
const NAME: &'static str = "git-ui";
|
||||
|
||||
@@ -191,7 +191,7 @@ impl FeedbackModal {
|
||||
);
|
||||
editor.set_show_gutter(false, cx);
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
editor.set_show_inline_completions(Some(false), window, cx);
|
||||
editor.set_show_edit_predictions(Some(false), window, cx);
|
||||
editor.set_vertical_scroll_margin(5, cx);
|
||||
editor.set_use_modal_editing(false);
|
||||
editor.set_soft_wrap();
|
||||
|
||||
@@ -64,12 +64,17 @@ 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,7 +817,10 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
|
||||
.as_u64() as usize,
|
||||
)
|
||||
});
|
||||
cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
|
||||
cx.dispatch_action(workspace::CloseActiveItem {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
});
|
||||
|
||||
let initial_history_items =
|
||||
open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
|
||||
@@ -2000,7 +2003,10 @@ async fn open_close_queried_buffer(
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
|
||||
cx.dispatch_action(workspace::CloseActiveItem {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
});
|
||||
|
||||
history_items
|
||||
}
|
||||
|
||||
@@ -93,6 +93,8 @@ impl PickerDelegate for OpenPathDelegate {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
// todo(windows)
|
||||
// Is this method woring correctly on Windows? This method uses `/` for path separator.
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
|
||||
@@ -11,6 +11,9 @@ workspace = true
|
||||
[lib]
|
||||
path = "src/git.rs"
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
@@ -32,10 +35,7 @@ url.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
unindent.workspace = true
|
||||
serde_json.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
text = {workspace = true, features = ["test-support"]}
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
serde_json.workspace = true
|
||||
text = { workspace = true, features = ["test-support"] }
|
||||
unindent.workspace = true
|
||||
|
||||
@@ -132,8 +132,8 @@ pub struct BlameEntry {
|
||||
pub author_time: Option<i64>,
|
||||
pub author_tz: Option<String>,
|
||||
|
||||
pub committer: Option<String>,
|
||||
pub committer_mail: Option<String>,
|
||||
pub committer_name: Option<String>,
|
||||
pub committer_email: Option<String>,
|
||||
pub committer_time: Option<i64>,
|
||||
pub committer_tz: Option<String>,
|
||||
|
||||
@@ -255,10 +255,12 @@ fn parse_git_blame(output: &str) -> Result<Vec<BlameEntry>> {
|
||||
.clone_from(&existing_entry.author_mail);
|
||||
new_entry.author_time = existing_entry.author_time;
|
||||
new_entry.author_tz.clone_from(&existing_entry.author_tz);
|
||||
new_entry.committer.clone_from(&existing_entry.committer);
|
||||
new_entry
|
||||
.committer_mail
|
||||
.clone_from(&existing_entry.committer_mail);
|
||||
.committer_name
|
||||
.clone_from(&existing_entry.committer_name);
|
||||
new_entry
|
||||
.committer_email
|
||||
.clone_from(&existing_entry.committer_email);
|
||||
new_entry.committer_time = existing_entry.committer_time;
|
||||
new_entry
|
||||
.committer_tz
|
||||
@@ -288,8 +290,8 @@ fn parse_git_blame(output: &str) -> Result<Vec<BlameEntry>> {
|
||||
}
|
||||
"author-tz" if is_committed => entry.author_tz = Some(value.into()),
|
||||
|
||||
"committer" if is_committed => entry.committer = Some(value.into()),
|
||||
"committer-mail" if is_committed => entry.committer_mail = Some(value.into()),
|
||||
"committer" if is_committed => entry.committer_name = Some(value.into()),
|
||||
"committer-mail" if is_committed => entry.committer_email = Some(value.into()),
|
||||
"committer-time" if is_committed => {
|
||||
entry.committer_time = Some(value.parse::<i64>()?)
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ actions!(
|
||||
StageAll,
|
||||
UnstageAll,
|
||||
RevertAll,
|
||||
CommitChanges,
|
||||
CommitAllChanges,
|
||||
Uncommit,
|
||||
Commit,
|
||||
ClearCommitMessage
|
||||
]
|
||||
);
|
||||
|
||||
@@ -4,13 +4,11 @@ use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use collections::BTreeMap;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use gpui::{App, Global};
|
||||
use gpui::{App, Global, SharedString};
|
||||
use http_client::HttpClient;
|
||||
use parking_lot::RwLock;
|
||||
use url::Url;
|
||||
|
||||
use crate::Oid;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct PullRequest {
|
||||
pub number: u32,
|
||||
@@ -83,7 +81,7 @@ pub trait GitHostingProvider {
|
||||
&self,
|
||||
_repo_owner: &str,
|
||||
_repo: &str,
|
||||
_commit: Oid,
|
||||
_commit: SharedString,
|
||||
_http_client: Arc<dyn HttpClient>,
|
||||
) -> Result<Option<Url>> {
|
||||
Ok(None)
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
use crate::status::FileStatus;
|
||||
use crate::GitHostingProviderRegistry;
|
||||
use crate::{blame::Blame, status::GitStatus};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::{HashMap, HashSet};
|
||||
use git2::BranchType;
|
||||
use gpui::SharedString;
|
||||
use parking_lot::Mutex;
|
||||
use rope::Rope;
|
||||
use std::borrow::Borrow;
|
||||
use std::io::Write as _;
|
||||
use std::process::Stdio;
|
||||
use std::sync::LazyLock;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
@@ -18,12 +20,63 @@ use sum_tree::MapSeekTarget;
|
||||
use util::command::new_std_command;
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Branch {
|
||||
pub is_head: bool,
|
||||
pub name: SharedString,
|
||||
/// Timestamp of most recent commit, normalized to Unix Epoch format.
|
||||
pub unix_timestamp: Option<i64>,
|
||||
pub upstream: Option<Upstream>,
|
||||
pub most_recent_commit: Option<CommitSummary>,
|
||||
}
|
||||
|
||||
impl Branch {
|
||||
pub fn priority_key(&self) -> (bool, Option<i64>) {
|
||||
(
|
||||
self.is_head,
|
||||
self.most_recent_commit
|
||||
.as_ref()
|
||||
.map(|commit| commit.commit_timestamp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Upstream {
|
||||
pub ref_name: SharedString,
|
||||
pub tracking: Option<UpstreamTracking>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct UpstreamTracking {
|
||||
pub ahead: u32,
|
||||
pub behind: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct CommitSummary {
|
||||
pub sha: SharedString,
|
||||
pub subject: SharedString,
|
||||
/// This is a unix timestamp
|
||||
pub commit_timestamp: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct CommitDetails {
|
||||
pub sha: SharedString,
|
||||
pub message: SharedString,
|
||||
pub commit_timestamp: i64,
|
||||
pub committer_email: SharedString,
|
||||
pub committer_name: SharedString,
|
||||
}
|
||||
|
||||
pub enum ResetMode {
|
||||
// reset the branch pointer, leave index and worktree unchanged
|
||||
// (this will make it look like things that were committed are now
|
||||
// staged)
|
||||
Soft,
|
||||
// reset the branch pointer and index, leave worktree unchanged
|
||||
// (this makes it look as though things that were committed are now
|
||||
// unstaged)
|
||||
Mixed,
|
||||
}
|
||||
|
||||
pub trait GitRepository: Send + Sync {
|
||||
@@ -39,9 +92,10 @@ pub trait GitRepository: Send + Sync {
|
||||
/// Note that for symlink entries, this will return the contents of the symlink, not the target.
|
||||
fn load_committed_text(&self, path: &RepoPath) -> Option<String>;
|
||||
|
||||
fn set_index_text(&self, path: &RepoPath, content: Option<String>) -> anyhow::Result<()>;
|
||||
|
||||
/// Returns the URL of the remote with the given name.
|
||||
fn remote_url(&self, name: &str) -> Option<String>;
|
||||
fn branch_name(&self) -> Option<String>;
|
||||
|
||||
/// Returns the SHA of the current HEAD.
|
||||
fn head_sha(&self) -> Option<String>;
|
||||
@@ -56,10 +110,23 @@ pub trait GitRepository: Send + Sync {
|
||||
fn create_branch(&self, _: &str) -> Result<()>;
|
||||
fn branch_exits(&self, _: &str) -> Result<bool>;
|
||||
|
||||
fn reset(&self, commit: &str, mode: ResetMode) -> Result<()>;
|
||||
|
||||
fn show(&self, commit: &str) -> Result<CommitDetails>;
|
||||
|
||||
fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame>;
|
||||
|
||||
/// Returns the path to the repository, typically the `.git` folder.
|
||||
fn dot_git_dir(&self) -> PathBuf;
|
||||
/// 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;
|
||||
|
||||
/// Updates the index to match the worktree at the given paths.
|
||||
///
|
||||
@@ -109,11 +176,63 @@ impl GitRepository for RealGitRepository {
|
||||
}
|
||||
}
|
||||
|
||||
fn dot_git_dir(&self) -> PathBuf {
|
||||
fn path(&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 show(&self, commit: &str) -> Result<CommitDetails> {
|
||||
let repo = self.repository.lock();
|
||||
let Ok(commit) = repo.revparse_single(commit)?.into_commit() else {
|
||||
anyhow::bail!("{} is not a commit", commit);
|
||||
};
|
||||
let details = CommitDetails {
|
||||
sha: commit.id().to_string().into(),
|
||||
message: String::from_utf8_lossy(commit.message_raw_bytes())
|
||||
.to_string()
|
||||
.into(),
|
||||
commit_timestamp: commit.time().seconds(),
|
||||
committer_email: String::from_utf8_lossy(commit.committer().email_bytes())
|
||||
.to_string()
|
||||
.into(),
|
||||
committer_name: String::from_utf8_lossy(commit.committer().name_bytes())
|
||||
.to_string()
|
||||
.into(),
|
||||
};
|
||||
Ok(details)
|
||||
}
|
||||
|
||||
fn reset(&self, commit: &str, mode: ResetMode) -> Result<()> {
|
||||
let working_directory = self
|
||||
.repository
|
||||
.lock()
|
||||
.workdir()
|
||||
.context("failed to read git work directory")?
|
||||
.to_path_buf();
|
||||
|
||||
let mode_flag = match mode {
|
||||
ResetMode::Mixed => "--mixed",
|
||||
ResetMode::Soft => "--soft",
|
||||
};
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["reset", mode_flag, commit])
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to reset:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_index_text(&self, path: &RepoPath) -> Option<String> {
|
||||
fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
|
||||
const STAGE_NORMAL: i32 = 0;
|
||||
@@ -147,19 +266,56 @@ impl GitRepository for RealGitRepository {
|
||||
Some(content)
|
||||
}
|
||||
|
||||
fn set_index_text(&self, path: &RepoPath, content: Option<String>) -> anyhow::Result<()> {
|
||||
let working_directory = self
|
||||
.repository
|
||||
.lock()
|
||||
.workdir()
|
||||
.context("failed to read git work directory")?
|
||||
.to_path_buf();
|
||||
if let Some(content) = content {
|
||||
let mut child = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["hash-object", "-w", "--stdin"])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
child.stdin.take().unwrap().write_all(content.as_bytes())?;
|
||||
let output = child.wait_with_output()?.stdout;
|
||||
let sha = String::from_utf8(output)?;
|
||||
|
||||
log::debug!("indexing SHA: {sha}, path {path:?}");
|
||||
|
||||
let status = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["update-index", "--add", "--cacheinfo", "100644", &sha])
|
||||
.arg(path.as_ref())
|
||||
.status()?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(anyhow!("Failed to add to index: {status:?}"));
|
||||
}
|
||||
} else {
|
||||
let status = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["update-index", "--force-remove"])
|
||||
.arg(path.as_ref())
|
||||
.status()?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(anyhow!("Failed to remove from index: {status:?}"));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remote_url(&self, name: &str) -> Option<String> {
|
||||
let repo = self.repository.lock();
|
||||
let remote = repo.find_remote(name).ok()?;
|
||||
remote.url().map(|url| url.to_string())
|
||||
}
|
||||
|
||||
fn branch_name(&self) -> Option<String> {
|
||||
let repo = self.repository.lock();
|
||||
let head = repo.head().log_err()?;
|
||||
let branch = String::from_utf8_lossy(head.shorthand_bytes());
|
||||
Some(branch.to_string())
|
||||
}
|
||||
|
||||
fn head_sha(&self) -> Option<String> {
|
||||
Some(self.repository.lock().head().ok()?.target()?.to_string())
|
||||
}
|
||||
@@ -199,33 +355,62 @@ impl GitRepository for RealGitRepository {
|
||||
}
|
||||
|
||||
fn branches(&self) -> Result<Vec<Branch>> {
|
||||
let repo = self.repository.lock();
|
||||
let local_branches = repo.branches(Some(BranchType::Local))?;
|
||||
let valid_branches = local_branches
|
||||
.filter_map(|branch| {
|
||||
branch.ok().and_then(|(branch, _)| {
|
||||
let is_head = branch.is_head();
|
||||
let name = branch
|
||||
.name()
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|name| name.to_string().into())?;
|
||||
let timestamp = branch.get().peel_to_commit().ok()?.time();
|
||||
let unix_timestamp = timestamp.seconds();
|
||||
let timezone_offset = timestamp.offset_minutes();
|
||||
let utc_offset =
|
||||
time::UtcOffset::from_whole_seconds(timezone_offset * 60).ok()?;
|
||||
let unix_timestamp =
|
||||
time::OffsetDateTime::from_unix_timestamp(unix_timestamp).ok()?;
|
||||
Some(Branch {
|
||||
is_head,
|
||||
name,
|
||||
unix_timestamp: Some(unix_timestamp.to_offset(utc_offset).unix_timestamp()),
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
Ok(valid_branches)
|
||||
let working_directory = self
|
||||
.repository
|
||||
.lock()
|
||||
.workdir()
|
||||
.context("failed to read git work directory")?
|
||||
.to_path_buf();
|
||||
let fields = [
|
||||
"%(HEAD)",
|
||||
"%(objectname)",
|
||||
"%(refname)",
|
||||
"%(upstream)",
|
||||
"%(upstream:track)",
|
||||
"%(committerdate:unix)",
|
||||
"%(contents:subject)",
|
||||
]
|
||||
.join("%00");
|
||||
let args = vec!["for-each-ref", "refs/heads/*", "--format", &fields];
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(args)
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to git git branches:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
let input = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
let mut branches = parse_branch_input(&input)?;
|
||||
if branches.is_empty() {
|
||||
let args = vec!["symbolic-ref", "--quiet", "--short", "HEAD"];
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(args)
|
||||
.output()?;
|
||||
|
||||
// git symbolic-ref returns a non-0 exit code if HEAD points
|
||||
// to something other than a branch
|
||||
if output.status.success() {
|
||||
let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
|
||||
branches.push(Branch {
|
||||
name: name.into(),
|
||||
is_head: true,
|
||||
upstream: None,
|
||||
most_recent_commit: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(branches)
|
||||
}
|
||||
|
||||
fn change_branch(&self, name: &str) -> Result<()> {
|
||||
@@ -279,13 +464,16 @@ impl GitRepository for RealGitRepository {
|
||||
.to_path_buf();
|
||||
|
||||
if !paths.is_empty() {
|
||||
let status = new_std_command(&self.git_binary_path)
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["update-index", "--add", "--remove", "--"])
|
||||
.args(paths.iter().map(|p| p.as_ref()))
|
||||
.status()?;
|
||||
if !status.success() {
|
||||
return Err(anyhow!("Failed to stage paths: {status}"));
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to stage paths:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -300,13 +488,16 @@ impl GitRepository for RealGitRepository {
|
||||
.to_path_buf();
|
||||
|
||||
if !paths.is_empty() {
|
||||
let cmd = new_std_command(&self.git_binary_path)
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["reset", "--quiet", "--"])
|
||||
.args(paths.iter().map(|p| p.as_ref()))
|
||||
.status()?;
|
||||
if !cmd.success() {
|
||||
return Err(anyhow!("Failed to unstage paths: {cmd}"));
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to unstage:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -326,12 +517,16 @@ impl GitRepository for RealGitRepository {
|
||||
args.push(author);
|
||||
}
|
||||
|
||||
let cmd = new_std_command(&self.git_binary_path)
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(args)
|
||||
.status()?;
|
||||
if !cmd.success() {
|
||||
return Err(anyhow!("Failed to commit: {cmd}"));
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to commit:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -344,7 +539,7 @@ pub struct FakeGitRepository {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FakeGitRepositoryState {
|
||||
pub dot_git_dir: PathBuf,
|
||||
pub path: PathBuf,
|
||||
pub event_emitter: smol::channel::Sender<PathBuf>,
|
||||
pub head_contents: HashMap<RepoPath, String>,
|
||||
pub index_contents: HashMap<RepoPath, String>,
|
||||
@@ -361,9 +556,9 @@ impl FakeGitRepository {
|
||||
}
|
||||
|
||||
impl FakeGitRepositoryState {
|
||||
pub fn new(dot_git_dir: PathBuf, event_emitter: smol::channel::Sender<PathBuf>) -> Self {
|
||||
pub fn new(path: PathBuf, event_emitter: smol::channel::Sender<PathBuf>) -> Self {
|
||||
FakeGitRepositoryState {
|
||||
dot_git_dir,
|
||||
path,
|
||||
event_emitter,
|
||||
head_contents: Default::default(),
|
||||
index_contents: Default::default(),
|
||||
@@ -388,13 +583,22 @@ impl GitRepository for FakeGitRepository {
|
||||
state.head_contents.get(path.as_ref()).cloned()
|
||||
}
|
||||
|
||||
fn remote_url(&self, _name: &str) -> Option<String> {
|
||||
None
|
||||
fn set_index_text(&self, path: &RepoPath, content: Option<String>) -> anyhow::Result<()> {
|
||||
let mut state = self.state.lock();
|
||||
if let Some(content) = content {
|
||||
state.index_contents.insert(path.clone(), content);
|
||||
} else {
|
||||
state.index_contents.remove(path);
|
||||
}
|
||||
state
|
||||
.event_emitter
|
||||
.try_send(state.path.clone())
|
||||
.expect("Dropped repo change event");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn branch_name(&self) -> Option<String> {
|
||||
let state = self.state.lock();
|
||||
state.current_branch_name.clone()
|
||||
fn remote_url(&self, _name: &str) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn head_sha(&self) -> Option<String> {
|
||||
@@ -405,9 +609,21 @@ impl GitRepository for FakeGitRepository {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn dot_git_dir(&self) -> PathBuf {
|
||||
fn show(&self, _: &str) -> Result<CommitDetails> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn reset(&self, _: &str, _: ResetMode) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn path(&self) -> PathBuf {
|
||||
let state = self.state.lock();
|
||||
state.dot_git_dir.clone()
|
||||
state.path.clone()
|
||||
}
|
||||
|
||||
fn main_repository_path(&self) -> PathBuf {
|
||||
self.path()
|
||||
}
|
||||
|
||||
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
|
||||
@@ -443,7 +659,8 @@ impl GitRepository for FakeGitRepository {
|
||||
.map(|branch_name| Branch {
|
||||
is_head: Some(branch_name) == current_branch.as_ref(),
|
||||
name: branch_name.into(),
|
||||
unix_timestamp: None,
|
||||
most_recent_commit: None,
|
||||
upstream: None,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -458,7 +675,7 @@ impl GitRepository for FakeGitRepository {
|
||||
state.current_branch_name = Some(name.to_owned());
|
||||
state
|
||||
.event_emitter
|
||||
.try_send(state.dot_git_dir.clone())
|
||||
.try_send(state.path.clone())
|
||||
.expect("Dropped repo change event");
|
||||
Ok(())
|
||||
}
|
||||
@@ -468,7 +685,7 @@ impl GitRepository for FakeGitRepository {
|
||||
state.branches.insert(name.to_owned());
|
||||
state
|
||||
.event_emitter
|
||||
.try_send(state.dot_git_dir.clone())
|
||||
.try_send(state.path.clone())
|
||||
.expect("Dropped repo change event");
|
||||
Ok(())
|
||||
}
|
||||
@@ -543,10 +760,6 @@ impl RepoPath {
|
||||
|
||||
RepoPath(path.into())
|
||||
}
|
||||
|
||||
pub fn to_proto(&self) -> String {
|
||||
self.0.to_string_lossy().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RepoPath {
|
||||
@@ -617,3 +830,106 @@ impl<'a> MapSeekTarget<RepoPath> for RepoPathDescendants<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_branch_input(input: &str) -> Result<Vec<Branch>> {
|
||||
let mut branches = Vec::new();
|
||||
for line in input.split('\n') {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut fields = line.split('\x00');
|
||||
let is_current_branch = fields.next().context("no HEAD")? == "*";
|
||||
let head_sha: SharedString = fields.next().context("no objectname")?.to_string().into();
|
||||
let ref_name: SharedString = fields
|
||||
.next()
|
||||
.context("no refname")?
|
||||
.strip_prefix("refs/heads/")
|
||||
.context("unexpected format for refname")?
|
||||
.to_string()
|
||||
.into();
|
||||
let upstream_name = fields.next().context("no upstream")?.to_string();
|
||||
let upstream_tracking = parse_upstream_track(fields.next().context("no upstream:track")?)?;
|
||||
let commiterdate = fields.next().context("no committerdate")?.parse::<i64>()?;
|
||||
let subject: SharedString = fields
|
||||
.next()
|
||||
.context("no contents:subject")?
|
||||
.to_string()
|
||||
.into();
|
||||
|
||||
branches.push(Branch {
|
||||
is_head: is_current_branch,
|
||||
name: ref_name,
|
||||
most_recent_commit: Some(CommitSummary {
|
||||
sha: head_sha,
|
||||
subject,
|
||||
commit_timestamp: commiterdate,
|
||||
}),
|
||||
upstream: if upstream_name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Upstream {
|
||||
ref_name: upstream_name.into(),
|
||||
tracking: upstream_tracking,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Ok(branches)
|
||||
}
|
||||
|
||||
fn parse_upstream_track(upstream_track: &str) -> Result<Option<UpstreamTracking>> {
|
||||
if upstream_track == "" {
|
||||
return Ok(Some(UpstreamTracking {
|
||||
ahead: 0,
|
||||
behind: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
let upstream_track = upstream_track
|
||||
.strip_prefix("[")
|
||||
.ok_or_else(|| anyhow!("missing ["))?;
|
||||
let upstream_track = upstream_track
|
||||
.strip_suffix("]")
|
||||
.ok_or_else(|| anyhow!("missing ["))?;
|
||||
let mut ahead: u32 = 0;
|
||||
let mut behind: u32 = 0;
|
||||
for component in upstream_track.split(", ") {
|
||||
if component == "gone" {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(ahead_num) = component.strip_prefix("ahead ") {
|
||||
ahead = ahead_num.parse::<u32>()?;
|
||||
}
|
||||
if let Some(behind_num) = component.strip_prefix("behind ") {
|
||||
behind = behind_num.parse::<u32>()?;
|
||||
}
|
||||
}
|
||||
Ok(Some(UpstreamTracking { ahead, behind }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_branches_parsing() {
|
||||
// suppress "help: octal escapes are not supported, `\0` is always null"
|
||||
#[allow(clippy::octal_escapes)]
|
||||
let input = "*\0060964da10574cd9bf06463a53bf6e0769c5c45e\0refs/heads/zed-patches\0refs/remotes/origin/zed-patches\0\01733187470\0generated protobuf\n";
|
||||
assert_eq!(
|
||||
parse_branch_input(&input).unwrap(),
|
||||
vec![Branch {
|
||||
is_head: true,
|
||||
name: "zed-patches".into(),
|
||||
upstream: Some(Upstream {
|
||||
ref_name: "refs/remotes/origin/zed-patches".into(),
|
||||
tracking: Some(UpstreamTracking {
|
||||
ahead: 0,
|
||||
behind: 0
|
||||
})
|
||||
}),
|
||||
most_recent_commit: Some(CommitSummary {
|
||||
sha: "060964da10574cd9bf06463a53bf6e0769c5c45e".into(),
|
||||
subject: "generated protobuf".into(),
|
||||
commit_timestamp: 1733187470,
|
||||
})
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -29,8 +29,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -48,8 +48,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -67,8 +67,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -86,8 +86,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -105,8 +105,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -124,8 +124,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -143,8 +143,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -162,8 +162,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -181,8 +181,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -200,8 +200,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -219,8 +219,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -238,8 +238,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -257,8 +257,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -276,8 +276,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -295,8 +295,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -314,8 +314,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -333,8 +333,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -352,8 +352,8 @@
|
||||
"author_mail": "<maxbrunsfeld@gmail.com>",
|
||||
"author_time": 1705619094,
|
||||
"author_tz": "-0800",
|
||||
"committer": "Max Brunsfeld",
|
||||
"committer_mail": "<maxbrunsfeld@gmail.com>",
|
||||
"committer_name": "Max Brunsfeld",
|
||||
"committer_email": "<maxbrunsfeld@gmail.com>",
|
||||
"committer_time": 1705619205,
|
||||
"committer_tz": "-0800",
|
||||
"summary": "Merge branch 'main' into language-api-docs",
|
||||
@@ -371,8 +371,8 @@
|
||||
"author_mail": "<maxbrunsfeld@gmail.com>",
|
||||
"author_time": 1705619094,
|
||||
"author_tz": "-0800",
|
||||
"committer": "Max Brunsfeld",
|
||||
"committer_mail": "<maxbrunsfeld@gmail.com>",
|
||||
"committer_name": "Max Brunsfeld",
|
||||
"committer_email": "<maxbrunsfeld@gmail.com>",
|
||||
"committer_time": 1705619205,
|
||||
"committer_tz": "-0800",
|
||||
"summary": "Merge branch 'main' into language-api-docs",
|
||||
@@ -390,8 +390,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1694798044,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1694798044,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "Fix Y on last line with no trailing new line",
|
||||
@@ -409,8 +409,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1694798044,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1694798044,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "Fix Y on last line with no trailing new line",
|
||||
@@ -428,8 +428,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692855942,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692856812,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "vim: Fix linewise copy of last line with no trailing newline",
|
||||
@@ -447,8 +447,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692855942,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692856812,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "vim: Fix linewise copy of last line with no trailing newline",
|
||||
@@ -466,8 +466,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692855942,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692856812,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "vim: Fix linewise copy of last line with no trailing newline",
|
||||
@@ -485,8 +485,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692855942,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692856812,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "vim: Fix linewise copy of last line with no trailing newline",
|
||||
@@ -504,8 +504,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692855942,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692856812,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "vim: Fix linewise copy of last line with no trailing newline",
|
||||
@@ -523,8 +523,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692644159,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692732477,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "Rewrite paste",
|
||||
@@ -542,8 +542,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692644159,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692732477,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "Rewrite paste",
|
||||
@@ -561,8 +561,8 @@
|
||||
"author_mail": "<maxbrunsfeld@gmail.com>",
|
||||
"author_time": 1659072896,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Max Brunsfeld",
|
||||
"committer_mail": "<maxbrunsfeld@gmail.com>",
|
||||
"committer_name": "Max Brunsfeld",
|
||||
"committer_email": "<maxbrunsfeld@gmail.com>",
|
||||
"committer_time": 1659073230,
|
||||
"committer_tz": "-0700",
|
||||
"summary": ":art: Rename and simplify some autoindent stuff",
|
||||
@@ -580,8 +580,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653424557,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Unify visual line_mode and non line_mode operators",
|
||||
@@ -599,8 +599,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -618,8 +618,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -637,8 +637,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -656,8 +656,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -675,8 +675,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -694,8 +694,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -713,8 +713,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -732,8 +732,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -751,8 +751,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -770,8 +770,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1710764113,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1710764113,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Another commit",
|
||||
@@ -29,8 +29,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1710764113,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1710764113,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Another commit",
|
||||
@@ -48,8 +48,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1710764087,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1710764087,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Another commit",
|
||||
@@ -67,8 +67,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1710764087,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1710764087,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Another commit",
|
||||
@@ -86,8 +86,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1709299737,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1709299737,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Initial",
|
||||
@@ -105,8 +105,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1709299737,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1709299737,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Initial",
|
||||
@@ -124,8 +124,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1709299737,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1709299737,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Initial",
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"author_mail": "<mrnugget@example.com>",
|
||||
"author_time": 1709808710,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@example.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@example.com>",
|
||||
"committer_time": 1709808710,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Make a commit",
|
||||
@@ -29,8 +29,8 @@
|
||||
"author_mail": "<joe.schmoe@example.com>",
|
||||
"author_time": 1709741400,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Joe Schmoe",
|
||||
"committer_mail": "<joe.schmoe@example.com>",
|
||||
"committer_name": "Joe Schmoe",
|
||||
"committer_email": "<joe.schmoe@example.com>",
|
||||
"committer_time": 1709741400,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Joe's cool commit",
|
||||
@@ -48,8 +48,8 @@
|
||||
"author_mail": "<joe.schmoe@example.com>",
|
||||
"author_time": 1709741400,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Joe Schmoe",
|
||||
"committer_mail": "<joe.schmoe@example.com>",
|
||||
"committer_name": "Joe Schmoe",
|
||||
"committer_email": "<joe.schmoe@example.com>",
|
||||
"committer_time": 1709741400,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Joe's cool commit",
|
||||
@@ -67,8 +67,8 @@
|
||||
"author_mail": "<joe.schmoe@example.com>",
|
||||
"author_time": 1709741400,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Joe Schmoe",
|
||||
"committer_mail": "<joe.schmoe@example.com>",
|
||||
"committer_name": "Joe Schmoe",
|
||||
"committer_email": "<joe.schmoe@example.com>",
|
||||
"committer_time": 1709741400,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Joe's cool commit",
|
||||
@@ -86,8 +86,8 @@
|
||||
"author_mail": "<mrnugget@example.com>",
|
||||
"author_time": 1709129122,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@example.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@example.com>",
|
||||
"committer_time": 1709129122,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Get to a state where eslint would change code and imports",
|
||||
@@ -105,8 +105,8 @@
|
||||
"author_mail": "<mrnugget@example.com>",
|
||||
"author_time": 1709128963,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@example.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@example.com>",
|
||||
"committer_time": 1709128963,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Add some stuff",
|
||||
@@ -124,8 +124,8 @@
|
||||
"author_mail": "<mrnugget@example.com>",
|
||||
"author_time": 1709128963,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@example.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@example.com>",
|
||||
"committer_time": 1709128963,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Add some stuff",
|
||||
|
||||
@@ -4,12 +4,13 @@ use std::sync::Arc;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use gpui::SharedString;
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request};
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use git::{
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, Oid, ParsedGitRemote,
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
|
||||
RemoteUrl,
|
||||
};
|
||||
|
||||
@@ -160,7 +161,7 @@ impl GitHostingProvider for Codeberg {
|
||||
&self,
|
||||
repo_owner: &str,
|
||||
repo: &str,
|
||||
commit: Oid,
|
||||
commit: SharedString,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
) -> Result<Option<Url>> {
|
||||
let commit = commit.to_string();
|
||||
|
||||
@@ -4,13 +4,14 @@ use std::sync::{Arc, LazyLock};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use gpui::SharedString;
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request};
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use git::{
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, Oid, ParsedGitRemote,
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
|
||||
PullRequest, RemoteUrl,
|
||||
};
|
||||
|
||||
@@ -178,7 +179,7 @@ impl GitHostingProvider for Github {
|
||||
&self,
|
||||
repo_owner: &str,
|
||||
repo: &str,
|
||||
commit: Oid,
|
||||
commit: SharedString,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
) -> Result<Option<Url>> {
|
||||
let commit = commit.to_string();
|
||||
|
||||
@@ -14,14 +14,16 @@ path = "src/git_ui.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
collections.workspace = true
|
||||
db.workspace = true
|
||||
diff.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
@@ -35,9 +37,11 @@ serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
time.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
@@ -1,27 +1,49 @@
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
|
||||
use git::repository::Branch;
|
||||
use gpui::{
|
||||
rems, AnyElement, App, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled,
|
||||
Subscription, Task, WeakEntity, Window,
|
||||
rems, App, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription,
|
||||
Task, WeakEntity, Window,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::ProjectPath;
|
||||
use std::{ops::Not, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
|
||||
use util::ResultExt;
|
||||
use workspace::notifications::DetachAndPromptErr;
|
||||
use workspace::{ModalView, Workspace};
|
||||
use zed_actions::branches::OpenRecent;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.observe_new(|workspace: &mut Workspace, _, _| {
|
||||
workspace.register_action(BranchList::open);
|
||||
workspace.register_action(open);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn open(
|
||||
_: &mut Workspace,
|
||||
_: &zed_actions::git::Branch,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
let this = cx.entity().clone();
|
||||
cx.spawn_in(window, |_, mut cx| async move {
|
||||
// Modal branch picker has a longer trailoff than a popover one.
|
||||
let delegate = BranchListDelegate::new(this.clone(), 70, &cx).await?;
|
||||
|
||||
this.update_in(&mut cx, |workspace, window, cx| {
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
BranchList::new(delegate, 34., window, cx)
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.detach_and_prompt_err("Failed to read branches", window, cx, |_, _, _| None)
|
||||
}
|
||||
|
||||
pub struct BranchList {
|
||||
pub picker: Entity<Picker<BranchListDelegate>>,
|
||||
rem_width: f32,
|
||||
@@ -29,29 +51,7 @@ pub struct BranchList {
|
||||
}
|
||||
|
||||
impl BranchList {
|
||||
pub fn open(
|
||||
_: &mut Workspace,
|
||||
_: &OpenRecent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
let this = cx.entity().clone();
|
||||
cx.spawn_in(window, |_, mut cx| async move {
|
||||
// Modal branch picker has a longer trailoff than a popover one.
|
||||
let delegate = BranchListDelegate::new(this.clone(), 70, &cx).await?;
|
||||
|
||||
this.update_in(&mut cx, |workspace, window, cx| {
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
BranchList::new(delegate, 34., window, cx)
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.detach_and_prompt_err("Failed to read branches", window, cx, |_, _, _| None)
|
||||
}
|
||||
|
||||
fn new(
|
||||
pub fn new(
|
||||
delegate: BranchListDelegate,
|
||||
rem_width: f32,
|
||||
window: &mut Window,
|
||||
@@ -91,6 +91,7 @@ impl Render for BranchList {
|
||||
#[derive(Debug, Clone)]
|
||||
enum BranchEntry {
|
||||
Branch(StringMatch),
|
||||
History(String),
|
||||
NewBranch { name: String },
|
||||
}
|
||||
|
||||
@@ -98,6 +99,7 @@ impl BranchEntry {
|
||||
fn name(&self) -> &str {
|
||||
match self {
|
||||
Self::Branch(branch) => &branch.string,
|
||||
Self::History(branch) => &branch,
|
||||
Self::NewBranch { name } => &name,
|
||||
}
|
||||
}
|
||||
@@ -114,7 +116,7 @@ pub struct BranchListDelegate {
|
||||
}
|
||||
|
||||
impl BranchListDelegate {
|
||||
async fn new(
|
||||
pub async fn new(
|
||||
workspace: Entity<Workspace>,
|
||||
branch_name_trailoff_after: usize,
|
||||
cx: &AsyncApp,
|
||||
@@ -141,7 +143,7 @@ impl BranchListDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
fn branch_count(&self) -> usize {
|
||||
pub fn branch_count(&self) -> usize {
|
||||
self.matches
|
||||
.iter()
|
||||
.filter(|item| matches!(item, BranchEntry::Branch(_)))
|
||||
@@ -188,9 +190,7 @@ impl PickerDelegate for BranchListDelegate {
|
||||
// Truncate list of recent branches
|
||||
// Do a partial sort to show recent-ish branches first.
|
||||
branches.select_nth_unstable_by(RECENT_BRANCHES_COUNT - 1, |lhs, rhs| {
|
||||
rhs.is_head
|
||||
.cmp(&lhs.is_head)
|
||||
.then(rhs.unix_timestamp.cmp(&lhs.unix_timestamp))
|
||||
rhs.priority_key().cmp(&lhs.priority_key())
|
||||
});
|
||||
branches.truncate(RECENT_BRANCHES_COUNT);
|
||||
}
|
||||
@@ -207,16 +207,10 @@ impl PickerDelegate for BranchListDelegate {
|
||||
let Some(candidates) = candidates.log_err() else {
|
||||
return;
|
||||
};
|
||||
let matches = if query.is_empty() {
|
||||
let matches: Vec<BranchEntry> = if query.is_empty() {
|
||||
candidates
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, candidate)| StringMatch {
|
||||
candidate_id: index,
|
||||
string: candidate.string,
|
||||
positions: Vec::new(),
|
||||
score: 0.0,
|
||||
})
|
||||
.map(|candidate| BranchEntry::History(candidate.string))
|
||||
.collect()
|
||||
} else {
|
||||
fuzzy::match_strings(
|
||||
@@ -228,11 +222,15 @@ impl PickerDelegate for BranchListDelegate {
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(BranchEntry::Branch)
|
||||
.collect()
|
||||
};
|
||||
picker
|
||||
.update(&mut cx, |picker, _| {
|
||||
let delegate = &mut picker.delegate;
|
||||
delegate.matches = matches.into_iter().map(BranchEntry::Branch).collect();
|
||||
delegate.matches = matches;
|
||||
if delegate.matches.is_empty() {
|
||||
if !query.is_empty() {
|
||||
delegate.matches.push(BranchEntry::NewBranch {
|
||||
@@ -255,6 +253,25 @@ impl PickerDelegate for BranchListDelegate {
|
||||
let Some(branch) = self.matches.get(self.selected_index()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let current_branch = self
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.active_repository(cx)
|
||||
.and_then(|repo| repo.read(cx).branch())
|
||||
.map(|branch| branch.name.to_string())
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
if current_branch == Some(branch.name().to_string()) {
|
||||
cx.emit(DismissEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
cx.spawn_in(window, {
|
||||
let branch = branch.clone();
|
||||
|picker, mut cx| async move {
|
||||
@@ -268,6 +285,7 @@ impl PickerDelegate for BranchListDelegate {
|
||||
let project = workspace.read(cx).project().read(cx);
|
||||
let branch_to_checkout = match branch {
|
||||
BranchEntry::Branch(branch) => branch.string,
|
||||
BranchEntry::History(string) => string,
|
||||
BranchEntry::NewBranch { name: branch_name } => branch_name,
|
||||
};
|
||||
let worktree = project
|
||||
@@ -311,7 +329,14 @@ impl PickerDelegate for BranchListDelegate {
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.map(|parent| match hit {
|
||||
.when(matches!(hit, BranchEntry::History(_)), |el| {
|
||||
el.end_slot(
|
||||
Icon::new(IconName::HistoryRerun)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
})
|
||||
.map(|el| match hit {
|
||||
BranchEntry::Branch(branch) => {
|
||||
let highlights: Vec<_> = branch
|
||||
.positions
|
||||
@@ -320,40 +345,13 @@ impl PickerDelegate for BranchListDelegate {
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
parent.child(HighlightedLabel::new(shortened_branch_name, highlights))
|
||||
el.child(HighlightedLabel::new(shortened_branch_name, highlights))
|
||||
}
|
||||
BranchEntry::History(_) => el.child(Label::new(shortened_branch_name)),
|
||||
BranchEntry::NewBranch { name } => {
|
||||
parent.child(Label::new(format!("Create branch '{name}'")))
|
||||
el.child(Label::new(format!("Create branch '{name}'")))
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_header(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
_: &mut Context<Picker<Self>>,
|
||||
) -> Option<AnyElement> {
|
||||
let label = if self.last_query.is_empty() {
|
||||
Label::new("Recent Branches")
|
||||
.size(LabelSize::Small)
|
||||
.mt_1()
|
||||
.ml_3()
|
||||
.into_any_element()
|
||||
} else {
|
||||
let match_label = self.matches.is_empty().not().then(|| {
|
||||
let suffix = if self.branch_count() == 1 { "" } else { "es" };
|
||||
Label::new(format!("{} match{}", self.branch_count(), suffix))
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
});
|
||||
h_flex()
|
||||
.px_3()
|
||||
.justify_between()
|
||||
.child(Label::new("Branches").size(LabelSize::Small))
|
||||
.children(match_label)
|
||||
.into_any_element()
|
||||
};
|
||||
Some(v_flex().mt_1().child(label).into_any_element())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,14 +5,18 @@ 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
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use anyhow::Result;
|
||||
use buffer_diff::BufferDiff;
|
||||
use collections::HashSet;
|
||||
use diff::BufferDiff;
|
||||
use editor::{scroll::Autoscroll, Editor, EditorEvent};
|
||||
use feature_flags::FeatureFlagViewExt;
|
||||
use futures::StreamExt;
|
||||
@@ -12,7 +12,7 @@ use gpui::{
|
||||
};
|
||||
use language::{Anchor, Buffer, Capability, OffsetRangeExt, Point};
|
||||
use multi_buffer::{MultiBuffer, PathKey};
|
||||
use project::{git::GitState, Project, ProjectPath};
|
||||
use project::{git::GitStore, Project, ProjectPath};
|
||||
use theme::ActiveTheme;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt as _;
|
||||
@@ -31,7 +31,7 @@ pub(crate) struct ProjectDiff {
|
||||
editor: Entity<Editor>,
|
||||
project: Entity<Project>,
|
||||
git_panel: Entity<GitPanel>,
|
||||
git_state: Entity<GitState>,
|
||||
git_store: Entity<GitStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
update_needed: postage::watch::Sender<()>,
|
||||
@@ -69,6 +69,7 @@ impl ProjectDiff {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
workspace.open_panel::<GitPanel>(window, cx);
|
||||
Self::deploy_at(workspace, None, window, cx)
|
||||
}
|
||||
|
||||
@@ -126,6 +127,7 @@ impl ProjectDiff {
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
diff_display_editor.set_distinguish_unstaged_diff_hunks();
|
||||
diff_display_editor.set_expand_all_diff_hunks(cx);
|
||||
diff_display_editor.register_addon(GitPanelAddon {
|
||||
git_panel: git_panel.clone(),
|
||||
@@ -135,11 +137,11 @@ impl ProjectDiff {
|
||||
cx.subscribe_in(&editor, window, Self::handle_editor_event)
|
||||
.detach();
|
||||
|
||||
let git_state = project.read(cx).git_state().clone();
|
||||
let git_state_subscription = cx.subscribe_in(
|
||||
&git_state,
|
||||
let git_store = project.read(cx).git_store().clone();
|
||||
let git_store_subscription = cx.subscribe_in(
|
||||
&git_store,
|
||||
window,
|
||||
move |this, _git_state, _event, _window, _cx| {
|
||||
move |this, _git_store, _event, _window, _cx| {
|
||||
*this.update_needed.borrow_mut() = ();
|
||||
},
|
||||
);
|
||||
@@ -154,7 +156,7 @@ impl ProjectDiff {
|
||||
|
||||
Self {
|
||||
project,
|
||||
git_state: git_state.clone(),
|
||||
git_store: git_store.clone(),
|
||||
git_panel: git_panel.clone(),
|
||||
workspace: workspace.downgrade(),
|
||||
focus_handle,
|
||||
@@ -163,7 +165,7 @@ impl ProjectDiff {
|
||||
pending_scroll: None,
|
||||
update_needed: send,
|
||||
_task: worker,
|
||||
_subscription: git_state_subscription,
|
||||
_subscription: git_store_subscription,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +175,7 @@ impl ProjectDiff {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(git_repo) = self.git_state.read(cx).active_repository() else {
|
||||
let Some(git_repo) = self.git_store.read(cx).active_repository() else {
|
||||
return;
|
||||
};
|
||||
let repo = git_repo.read(cx);
|
||||
@@ -235,7 +237,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.set_focused_path(project_path.into(), window, cx)
|
||||
git_panel.select_entry_by_path(project_path.into(), window, cx)
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -246,7 +248,7 @@ impl ProjectDiff {
|
||||
}
|
||||
|
||||
fn load_buffers(&mut self, cx: &mut Context<Self>) -> Vec<Task<Result<DiffBuffer>>> {
|
||||
let Some(repo) = self.git_state.read(cx).active_repository() else {
|
||||
let Some(repo) = self.git_store.read(cx).active_repository() else {
|
||||
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.clear(cx);
|
||||
});
|
||||
@@ -317,10 +319,10 @@ impl ProjectDiff {
|
||||
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let diff = diff.read(cx);
|
||||
let diff_hunk_ranges = if diff.snapshot.base_text.is_none() {
|
||||
let diff_hunk_ranges = if diff.base_text().is_none() {
|
||||
vec![Point::zero()..snapshot.max_point()]
|
||||
} else {
|
||||
diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot)
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx)
|
||||
.map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
@@ -334,6 +336,22 @@ impl ProjectDiff {
|
||||
cx,
|
||||
);
|
||||
});
|
||||
if self.multibuffer.read(cx).is_empty()
|
||||
&& self
|
||||
.editor
|
||||
.read(cx)
|
||||
.focus_handle(cx)
|
||||
.contains_focused(window, cx)
|
||||
{
|
||||
self.focus_handle.focus(window);
|
||||
} else if self.focus_handle.contains_focused(window, cx)
|
||||
&& !self.multibuffer.read(cx).is_empty()
|
||||
{
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.focus_handle(cx).focus(window);
|
||||
editor.move_to_beginning(&Default::default(), window, cx);
|
||||
});
|
||||
}
|
||||
if self.pending_scroll.as_ref() == Some(&path_key) {
|
||||
self.scroll_to_path(path_key, window, cx);
|
||||
}
|
||||
@@ -364,8 +382,12 @@ impl ProjectDiff {
|
||||
impl EventEmitter<EditorEvent> for ProjectDiff {}
|
||||
|
||||
impl Focusable for ProjectDiff {
|
||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
if self.multibuffer.read(cx).is_empty() {
|
||||
self.focus_handle.clone()
|
||||
} else {
|
||||
self.editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +428,7 @@ impl Item for ProjectDiff {
|
||||
}
|
||||
|
||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||
Some("project diagnostics")
|
||||
Some("Project Diff Opened")
|
||||
}
|
||||
|
||||
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
@@ -536,22 +558,17 @@ impl Item for ProjectDiff {
|
||||
impl Render for ProjectDiff {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let is_empty = self.multibuffer.read(cx).is_empty();
|
||||
if is_empty {
|
||||
div()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.size_full()
|
||||
.child(Label::new("No uncommitted changes"))
|
||||
} else {
|
||||
div()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.size_full()
|
||||
.child(self.editor.clone())
|
||||
}
|
||||
|
||||
div()
|
||||
.track_focus(&self.focus_handle)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.size_full()
|
||||
.when(is_empty, |el| {
|
||||
el.child(Label::new("No uncommitted changes"))
|
||||
})
|
||||
.when(!is_empty, |el| el.child(self.editor.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
307
crates/git_ui/src/quick_commit.rs
Normal file
307
crates/git_ui/src/quick_commit.rs
Normal file
@@ -0,0 +1,307 @@
|
||||
#![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_store = project.read(cx).git_store().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_store()
|
||||
.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,10 +1,10 @@
|
||||
use gpui::{
|
||||
AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
|
||||
Task, WeakEntity,
|
||||
AnyElement, AnyView, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
Subscription, Task, WeakEntity,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{
|
||||
git::{GitState, Repository},
|
||||
git::{GitStore, Repository},
|
||||
Project,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
@@ -20,8 +20,8 @@ pub struct RepositorySelector {
|
||||
|
||||
impl RepositorySelector {
|
||||
pub fn new(project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let git_state = project.read(cx).git_state().clone();
|
||||
let all_repositories = git_state.read(cx).all_repositories();
|
||||
let git_store = project.read(cx).git_store().clone();
|
||||
let all_repositories = git_store.read(cx).all_repositories();
|
||||
let filtered_repositories = all_repositories.clone();
|
||||
let delegate = RepositorySelectorDelegate {
|
||||
project: project.downgrade(),
|
||||
@@ -34,10 +34,11 @@ 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 =
|
||||
vec![cx.subscribe_in(&git_state, window, Self::handle_project_git_event)];
|
||||
vec![cx.subscribe_in(&git_store, window, Self::handle_project_git_event)];
|
||||
|
||||
RepositorySelector {
|
||||
picker,
|
||||
@@ -48,7 +49,7 @@ impl RepositorySelector {
|
||||
|
||||
fn handle_project_git_event(
|
||||
&mut self,
|
||||
git_state: &Entity<GitState>,
|
||||
git_store: &Entity<GitStore>,
|
||||
_event: &project::git::GitEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -56,7 +57,7 @@ impl RepositorySelector {
|
||||
// TODO handle events individually
|
||||
let task = self.picker.update(cx, |this, cx| {
|
||||
let query = this.query(cx);
|
||||
this.delegate.repository_entries = git_state.read(cx).all_repositories();
|
||||
this.delegate.repository_entries = git_store.read(cx).all_repositories();
|
||||
this.delegate.update_matches(query, window, cx)
|
||||
});
|
||||
self.update_matches_task = Some(task);
|
||||
@@ -78,20 +79,27 @@ impl Render for RepositorySelector {
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct RepositorySelectorPopoverMenu<T>
|
||||
pub struct RepositorySelectorPopoverMenu<T, TT>
|
||||
where
|
||||
T: PopoverTrigger,
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
repository_selector: Entity<RepositorySelector>,
|
||||
trigger: T,
|
||||
tooltip: TT,
|
||||
handle: Option<PopoverMenuHandle<RepositorySelector>>,
|
||||
}
|
||||
|
||||
impl<T: PopoverTrigger> RepositorySelectorPopoverMenu<T> {
|
||||
pub fn new(repository_selector: Entity<RepositorySelector>, trigger: T) -> Self {
|
||||
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 {
|
||||
Self {
|
||||
repository_selector,
|
||||
trigger,
|
||||
tooltip,
|
||||
handle: None,
|
||||
}
|
||||
}
|
||||
@@ -102,13 +110,17 @@ impl<T: PopoverTrigger> RepositorySelectorPopoverMenu<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PopoverTrigger> RenderOnce for RepositorySelectorPopoverMenu<T> {
|
||||
impl<T, TT> RenderOnce for RepositorySelectorPopoverMenu<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
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(self.trigger)
|
||||
.trigger_with_tooltip(self.trigger, self.tooltip)
|
||||
.attach(gpui::Corner::BottomLeft)
|
||||
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user