Compare commits
42 Commits
faster_mac
...
always-adj
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d063874f49 | ||
|
|
fab2f22a89 | ||
|
|
a451bcc3c4 | ||
|
|
5e9ff3e313 | ||
|
|
cc81f19c68 | ||
|
|
5e89fba681 | ||
|
|
67eb652bf1 | ||
|
|
e0ea9a9ab5 | ||
|
|
ff29a34298 | ||
|
|
6686f66949 | ||
|
|
8a96ea25c4 | ||
|
|
cdddb4d360 | ||
|
|
03bd95405b | ||
|
|
177dfdf900 | ||
|
|
2ab0b3b819 | ||
|
|
888fec9299 | ||
|
|
e86b096b92 | ||
|
|
ffe36c9beb | ||
|
|
2d16d2d036 | ||
|
|
c69da2df70 | ||
|
|
5506669b06 | ||
|
|
b13940720a | ||
|
|
db61711753 | ||
|
|
c12a9f2673 | ||
|
|
2e32f1c8a1 | ||
|
|
03a1c8d2b8 | ||
|
|
d7a277607b | ||
|
|
fc8a72cdd8 | ||
|
|
1acebb3c47 | ||
|
|
78ed0c9312 | ||
|
|
98d2e5fe73 | ||
|
|
4325819075 | ||
|
|
c19c89e6df | ||
|
|
507929cb79 | ||
|
|
7d0a7aff44 | ||
|
|
92ba18342c | ||
|
|
6de5ace116 | ||
|
|
c9db1b9a7b | ||
|
|
24cb694494 | ||
|
|
85bdd9329b | ||
|
|
5d7b63ca87 | ||
|
|
29db144434 |
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@@ -25,6 +25,7 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: 1
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
jobs:
|
||||
migration_checks:
|
||||
@@ -91,6 +92,7 @@ jobs:
|
||||
macos_tests:
|
||||
timeout-minutes: 60
|
||||
name: (macOS) Run Clippy and tests
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
@@ -115,17 +117,18 @@ jobs:
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
- name: Build collab
|
||||
run: RUSTFLAGS="-D warnings" cargo build -p collab
|
||||
run: cargo build -p collab
|
||||
|
||||
- name: Build other binaries and features
|
||||
run: |
|
||||
RUSTFLAGS="-D warnings" cargo build --workspace --bins --all-features
|
||||
cargo build --workspace --bins --all-features
|
||||
cargo check -p gpui --features "macos-blade"
|
||||
RUSTFLAGS="-D warnings" cargo build -p remote_server
|
||||
cargo build -p remote_server
|
||||
|
||||
linux_tests:
|
||||
timeout-minutes: 60
|
||||
name: (Linux) Run Clippy and tests
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
@@ -153,11 +156,12 @@ jobs:
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
- name: Build Zed
|
||||
run: RUSTFLAGS="-D warnings" cargo build -p zed
|
||||
run: cargo build -p zed
|
||||
|
||||
build_remote_server:
|
||||
timeout-minutes: 60
|
||||
name: (Linux) Build Remote Server
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
@@ -179,14 +183,18 @@ jobs:
|
||||
run: ./script/remote-server && ./script/install-mold 2.34.0
|
||||
|
||||
- name: Build Remote Server
|
||||
run: RUSTFLAGS="-D warnings" cargo build -p remote_server
|
||||
run: cargo build -p remote_server
|
||||
|
||||
# todo(windows): Actually run the tests
|
||||
windows_tests:
|
||||
timeout-minutes: 60
|
||||
name: (Windows) Run Clippy and tests
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: hosted-windows-1
|
||||
steps:
|
||||
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
||||
- name: Enable longer pathnames for git
|
||||
run: git config --system core.longpaths true
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
@@ -203,7 +211,7 @@ jobs:
|
||||
run: cargo xtask clippy
|
||||
|
||||
- name: Build Zed
|
||||
run: $env:RUSTFLAGS="-D warnings"; cargo build
|
||||
run: cargo build
|
||||
|
||||
bundle-mac:
|
||||
timeout-minutes: 60
|
||||
|
||||
499
Cargo.lock
generated
499
Cargo.lock
generated
@@ -291,6 +291,12 @@ dependencies = [
|
||||
"syn 2.0.76",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arraydeque"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.8"
|
||||
@@ -385,7 +391,7 @@ dependencies = [
|
||||
"ctor",
|
||||
"db",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
@@ -2551,7 +2557,7 @@ dependencies = [
|
||||
"dashmap 6.0.1",
|
||||
"derive_more",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"envy",
|
||||
"file_finder",
|
||||
"fs",
|
||||
@@ -2706,7 +2712,7 @@ dependencies = [
|
||||
"command_palette_hooks",
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"fuzzy",
|
||||
"go_to_line",
|
||||
"gpui",
|
||||
@@ -3483,7 +3489,7 @@ dependencies = [
|
||||
"collections",
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"language",
|
||||
@@ -3671,7 +3677,7 @@ dependencies = [
|
||||
"ctor",
|
||||
"db",
|
||||
"emojis",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"file_icons",
|
||||
"futures 0.3.30",
|
||||
"fuzzy",
|
||||
@@ -3711,7 +3717,6 @@ dependencies = [
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"ui",
|
||||
"unicode-segmentation",
|
||||
"unindent",
|
||||
"url",
|
||||
"util",
|
||||
@@ -3878,6 +3883,19 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.5"
|
||||
@@ -3986,7 +4004,7 @@ dependencies = [
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"git",
|
||||
@@ -4081,7 +4099,7 @@ dependencies = [
|
||||
"client",
|
||||
"collections",
|
||||
"ctor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
@@ -4123,7 +4141,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"extension",
|
||||
"fs",
|
||||
"language",
|
||||
@@ -4282,7 +4300,7 @@ dependencies = [
|
||||
"collections",
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"file_icons",
|
||||
"futures 0.3.30",
|
||||
"fuzzy",
|
||||
@@ -5037,7 +5055,7 @@ dependencies = [
|
||||
"ctor",
|
||||
"derive_more",
|
||||
"embed-resource",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"etagere",
|
||||
"filedescriptor",
|
||||
"flume",
|
||||
@@ -5227,6 +5245,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.9.1"
|
||||
@@ -6185,7 +6212,7 @@ dependencies = [
|
||||
"collections",
|
||||
"ctor",
|
||||
"ec4rs",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.30",
|
||||
"fuzzy",
|
||||
"git",
|
||||
@@ -6242,7 +6269,7 @@ dependencies = [
|
||||
"copilot",
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"feature_flags",
|
||||
"futures 0.3.30",
|
||||
"google_ai",
|
||||
@@ -6299,7 +6326,7 @@ dependencies = [
|
||||
"collections",
|
||||
"copilot",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"language",
|
||||
@@ -6333,6 +6360,11 @@ dependencies = [
|
||||
"lsp",
|
||||
"node_runtime",
|
||||
"paths",
|
||||
"pet",
|
||||
"pet-conda",
|
||||
"pet-core",
|
||||
"pet-poetry",
|
||||
"pet-reporter",
|
||||
"project",
|
||||
"regex",
|
||||
"rope",
|
||||
@@ -6629,7 +6661,7 @@ dependencies = [
|
||||
"async-pipe",
|
||||
"collections",
|
||||
"ctor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"log",
|
||||
@@ -6712,7 +6744,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assets",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"language",
|
||||
@@ -6825,7 +6857,7 @@ dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"elasticlunr-rs",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"futures-util",
|
||||
"handlebars 5.1.2",
|
||||
"ignore",
|
||||
@@ -7007,6 +7039,15 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msvc_spectre_libs"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8661ace213a0a130c7c5b9542df5023aedf092a02008ccf477b39ff108990305"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multi_buffer"
|
||||
version = "0.1.0"
|
||||
@@ -7015,7 +7056,7 @@ dependencies = [
|
||||
"clock",
|
||||
"collections",
|
||||
"ctor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"itertools 0.13.0",
|
||||
@@ -7729,8 +7770,10 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
"worktree",
|
||||
@@ -7973,6 +8016,366 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-conda",
|
||||
"pet-core",
|
||||
"pet-env-var-path",
|
||||
"pet-fs",
|
||||
"pet-global-virtualenvs",
|
||||
"pet-homebrew",
|
||||
"pet-jsonrpc",
|
||||
"pet-linux-global-python",
|
||||
"pet-mac-commandlinetools",
|
||||
"pet-mac-python-org",
|
||||
"pet-mac-xcode",
|
||||
"pet-pipenv",
|
||||
"pet-poetry",
|
||||
"pet-pyenv",
|
||||
"pet-python-utils",
|
||||
"pet-reporter",
|
||||
"pet-telemetry",
|
||||
"pet-venv",
|
||||
"pet-virtualenv",
|
||||
"pet-virtualenvwrapper",
|
||||
"pet-windows-registry",
|
||||
"pet-windows-store",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-conda"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-reporter",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"yaml-rust2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-core"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-fs",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-env-var-path"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-conda",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-virtualenv",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-fs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-global-virtualenvs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-conda",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-virtualenv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-homebrew"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-conda",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-virtualenv",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-jsonrpc"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-linux-global-python"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-virtualenv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-mac-commandlinetools"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-virtualenv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-mac-python-org"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-virtualenv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-mac-xcode"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-virtualenv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-pipenv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-virtualenv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-poetry"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-reporter",
|
||||
"pet-virtualenv",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"toml 0.8.19",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-pyenv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-conda",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-reporter",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-python-utils"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-reporter"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-jsonrpc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-telemetry"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-venv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-python-utils",
|
||||
"pet-virtualenv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-virtualenv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-virtualenvwrapper"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-virtualenv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-windows-registry"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-conda",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-virtualenv",
|
||||
"pet-windows-store",
|
||||
"regex",
|
||||
"winreg 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-windows-store"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-python-utils",
|
||||
"pet-virtualenv",
|
||||
"regex",
|
||||
"winreg 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.5"
|
||||
@@ -8061,7 +8464,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"gpui",
|
||||
"menu",
|
||||
"serde",
|
||||
@@ -8407,7 +8810,7 @@ dependencies = [
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
"fuzzy",
|
||||
@@ -9122,7 +9525,7 @@ dependencies = [
|
||||
"clap",
|
||||
"client",
|
||||
"clock",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"fork",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
@@ -9173,7 +9576,7 @@ dependencies = [
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"http_client",
|
||||
@@ -9453,7 +9856,7 @@ dependencies = [
|
||||
"arrayvec",
|
||||
"criterion",
|
||||
"ctor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"gpui",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
@@ -9484,7 +9887,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"collections",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"parking_lot",
|
||||
@@ -10073,7 +10476,7 @@ dependencies = [
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
@@ -10766,7 +11169,7 @@ dependencies = [
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"hashbrown 0.14.5",
|
||||
"hashlink",
|
||||
"hashlink 0.9.1",
|
||||
"hex",
|
||||
"indexmap 2.4.0",
|
||||
"log",
|
||||
@@ -11090,7 +11493,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"ctor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"rayon",
|
||||
@@ -11104,7 +11507,7 @@ dependencies = [
|
||||
"client",
|
||||
"collections",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"http_client",
|
||||
@@ -11403,7 +11806,7 @@ dependencies = [
|
||||
"collections",
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"gpui",
|
||||
"language",
|
||||
"menu",
|
||||
@@ -11610,7 +12013,7 @@ dependencies = [
|
||||
"clock",
|
||||
"collections",
|
||||
"ctor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"log",
|
||||
@@ -12099,6 +12502,21 @@ dependencies = [
|
||||
"winnow 0.6.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toolchain_selector"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"editor",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"language",
|
||||
"picker",
|
||||
"project",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "topological-sort"
|
||||
version = "0.2.2"
|
||||
@@ -12844,6 +13262,7 @@ dependencies = [
|
||||
"git",
|
||||
"gpui",
|
||||
"picker",
|
||||
"project",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
@@ -14267,7 +14686,7 @@ dependencies = [
|
||||
"collections",
|
||||
"db",
|
||||
"derive_more",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
"git",
|
||||
@@ -14304,7 +14723,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"clock",
|
||||
"collections",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
"fuzzy",
|
||||
@@ -14474,6 +14893,17 @@ dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust2"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8"
|
||||
dependencies = [
|
||||
"arraydeque",
|
||||
"encoding_rs",
|
||||
"hashlink 0.8.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
@@ -14587,7 +15017,7 @@ dependencies = [
|
||||
"db",
|
||||
"diagnostics",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"env_logger 0.11.5",
|
||||
"extension",
|
||||
"extensions_ui",
|
||||
"feature_flags",
|
||||
@@ -14654,6 +15084,7 @@ dependencies = [
|
||||
"theme",
|
||||
"theme_selector",
|
||||
"time",
|
||||
"toolchain_selector",
|
||||
"tree-sitter-md",
|
||||
"tree-sitter-rust",
|
||||
"ui",
|
||||
@@ -14701,7 +15132,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_dart"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
@@ -117,6 +117,7 @@ members = [
|
||||
"crates/theme_selector",
|
||||
"crates/time_format",
|
||||
"crates/title_bar",
|
||||
"crates/toolchain_selector",
|
||||
"crates/ui",
|
||||
"crates/ui_input",
|
||||
"crates/ui_macros",
|
||||
@@ -290,6 +291,7 @@ theme_importer = { path = "crates/theme_importer" }
|
||||
theme_selector = { path = "crates/theme_selector" }
|
||||
time_format = { path = "crates/time_format" }
|
||||
title_bar = { path = "crates/title_bar" }
|
||||
toolchain_selector = { path = "crates/toolchain_selector" }
|
||||
ui = { path = "crates/ui" }
|
||||
ui_input = { path = "crates/ui_input" }
|
||||
ui_macros = { path = "crates/ui_macros" }
|
||||
@@ -376,6 +378,11 @@ ordered-float = "2.1.1"
|
||||
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||
parking_lot = "0.12.1"
|
||||
pathdiff = "0.2"
|
||||
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
postage = { version = "0.5", features = ["futures-traits"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
profiling = "1"
|
||||
@@ -464,7 +471,7 @@ tree-sitter-typescript = "0.23"
|
||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
|
||||
unicase = "2.6"
|
||||
unindent = "0.1.7"
|
||||
unicode-segmentation = "1.11"
|
||||
unicode-segmentation = "1.10"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
|
||||
wasmparser = "0.215"
|
||||
|
||||
@@ -313,6 +313,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-0": "editor::FoldAll",
|
||||
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
@@ -505,6 +514,13 @@
|
||||
"ctrl-enter": "assistant::InlineAssist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProposedChangesEditor",
|
||||
"bindings": {
|
||||
"ctrl-shift-y": "editor::ApplyDiffHunk",
|
||||
"ctrl-alt-a": "editor::ApplyAllDiffHunks"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && jupyter && !ContextEditor",
|
||||
"bindings": {
|
||||
|
||||
@@ -349,7 +349,15 @@
|
||||
"alt-cmd-]": "editor::UnfoldLines",
|
||||
"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-0": "editor::FoldAll",
|
||||
"cmd-k cmd-j": "editor::UnfoldAll",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
@@ -538,6 +546,13 @@
|
||||
"ctrl-enter": "assistant::InlineAssist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProposedChangesEditor",
|
||||
"bindings": {
|
||||
"cmd-shift-y": "editor::ApplyDiffHunk",
|
||||
"cmd-shift-a": "editor::ApplyAllDiffHunks"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "PromptEditor",
|
||||
"bindings": {
|
||||
|
||||
@@ -88,7 +88,6 @@ origin: (f64, f64),
|
||||
|
||||
<edit>
|
||||
<path>src/shapes/rectangle.rs</path>
|
||||
<description>Update the Rectangle's new function to take an origin parameter</description>
|
||||
<operation>update</operation>
|
||||
<old_text>
|
||||
fn new(width: f64, height: f64) -> Self {
|
||||
@@ -117,7 +116,6 @@ pub struct Circle {
|
||||
|
||||
<edit>
|
||||
<path>src/shapes/circle.rs</path>
|
||||
<description>Update the Circle's new function to take an origin parameter</description>
|
||||
<operation>update</operation>
|
||||
<old_text>
|
||||
fn new(radius: f64) -> Self {
|
||||
@@ -134,7 +132,6 @@ fn new(origin: (f64, f64), radius: f64) -> Self {
|
||||
|
||||
<edit>
|
||||
<path>src/shapes/rectangle.rs</path>
|
||||
<description>Add an import for the std::fmt module</description>
|
||||
<operation>insert_before</operation>
|
||||
<old_text>
|
||||
struct Rectangle {
|
||||
@@ -147,7 +144,10 @@ use std::fmt;
|
||||
|
||||
<edit>
|
||||
<path>src/shapes/rectangle.rs</path>
|
||||
<description>Add a Display implementation for Rectangle</description>
|
||||
<description>
|
||||
Add a manual Display implementation for Rectangle.
|
||||
Currently, this is the same as a derived Display implementation.
|
||||
</description>
|
||||
<operation>insert_after</operation>
|
||||
<old_text>
|
||||
Rectangle { width, height }
|
||||
@@ -169,7 +169,6 @@ impl fmt::Display for Rectangle {
|
||||
|
||||
<edit>
|
||||
<path>src/shapes/circle.rs</path>
|
||||
<description>Add an import for the `std::fmt` module</description>
|
||||
<operation>insert_before</operation>
|
||||
<old_text>
|
||||
struct Circle {
|
||||
@@ -181,7 +180,6 @@ use std::fmt;
|
||||
|
||||
<edit>
|
||||
<path>src/shapes/circle.rs</path>
|
||||
<description>Add a Display implementation for Circle</description>
|
||||
<operation>insert_after</operation>
|
||||
<old_text>
|
||||
Circle { radius }
|
||||
|
||||
@@ -388,6 +388,8 @@
|
||||
"git_status": true,
|
||||
// Amount of indentation for nested items.
|
||||
"indent_size": 20,
|
||||
// Whether to show indent guides in the outline panel.
|
||||
"indent_guides": true,
|
||||
// Whether to reveal it in the outline panel automatically,
|
||||
// when a corresponding outline entry becomes active.
|
||||
// Gitignored entries are never auto revealed.
|
||||
@@ -777,6 +779,7 @@
|
||||
"tasks": {
|
||||
"variables": {}
|
||||
},
|
||||
"toolchain": { "name": "default", "path": "default" },
|
||||
// An object whose keys are language names, and whose values
|
||||
// are arrays of filenames or extensions of files that should
|
||||
// use those languages.
|
||||
|
||||
@@ -298,25 +298,64 @@ fn register_context_server_handlers(cx: &mut AppContext) {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(prompts) = protocol.list_prompts().await.log_err() {
|
||||
for prompt in prompts
|
||||
.into_iter()
|
||||
.filter(context_server_command::acceptable_prompt)
|
||||
{
|
||||
log::info!(
|
||||
"registering context server command: {:?}",
|
||||
prompt.name
|
||||
);
|
||||
context_server_registry.register_command(
|
||||
server.id.clone(),
|
||||
prompt.name.as_str(),
|
||||
);
|
||||
slash_command_registry.register_command(
|
||||
context_server_command::ContextServerSlashCommand::new(
|
||||
&server, prompt,
|
||||
),
|
||||
true,
|
||||
);
|
||||
if protocol.capable(context_servers::protocol::ServerCapability::Prompts) {
|
||||
if let Some(prompts) = protocol.list_prompts().await.log_err() {
|
||||
for prompt in prompts
|
||||
.into_iter()
|
||||
.filter(context_server_command::acceptable_prompt)
|
||||
{
|
||||
log::info!(
|
||||
"registering context server command: {:?}",
|
||||
prompt.name
|
||||
);
|
||||
context_server_registry.register_command(
|
||||
server.id.clone(),
|
||||
prompt.name.as_str(),
|
||||
);
|
||||
slash_command_registry.register_command(
|
||||
context_server_command::ContextServerSlashCommand::new(
|
||||
&server, prompt,
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
cx.update_model(
|
||||
&manager,
|
||||
|manager: &mut context_servers::manager::ContextServerManager, cx| {
|
||||
let tool_registry = ToolRegistry::global(cx);
|
||||
let context_server_registry = ContextServerRegistry::global(cx);
|
||||
if let Some(server) = manager.get_server(server_id) {
|
||||
cx.spawn(|_, _| async move {
|
||||
let Some(protocol) = server.client.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if protocol.capable(context_servers::protocol::ServerCapability::Tools) {
|
||||
if let Some(tools) = protocol.list_tools().await.log_err() {
|
||||
for tool in tools.tools {
|
||||
log::info!(
|
||||
"registering context server tool: {:?}",
|
||||
tool.name
|
||||
);
|
||||
context_server_registry.register_tool(
|
||||
server.id.clone(),
|
||||
tool.name.as_str(),
|
||||
);
|
||||
tool_registry.register_tool(
|
||||
tools::context_server_tool::ContextServerTool::new(
|
||||
server.id.clone(),
|
||||
tool
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -334,6 +373,14 @@ fn register_context_server_handlers(cx: &mut AppContext) {
|
||||
context_server_registry.unregister_command(&server_id, &command_name);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tools) = context_server_registry.get_tools(server_id) {
|
||||
let tool_registry = ToolRegistry::global(cx);
|
||||
for tool_name in tools {
|
||||
tool_registry.unregister_tool_by_name(&tool_name);
|
||||
context_server_registry.unregister_tool(&server_id, &tool_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1667,8 +1667,10 @@ impl ContextEditor {
|
||||
});
|
||||
}
|
||||
|
||||
fn cursors(&self, cx: &AppContext) -> Vec<usize> {
|
||||
let selections = self.editor.read(cx).selections.all::<usize>(cx);
|
||||
fn cursors(&self, cx: &mut WindowContext) -> Vec<usize> {
|
||||
let selections = self
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.selections.all::<usize>(cx));
|
||||
selections
|
||||
.into_iter()
|
||||
.map(|selection| selection.head())
|
||||
@@ -2964,7 +2966,7 @@ impl ContextEditor {
|
||||
|
||||
let mut creases = vec![];
|
||||
editor.update(cx, |editor, cx| {
|
||||
let selections = editor.selections.all_adjusted(cx);
|
||||
let selections = editor.selections.all::<Point>(cx);
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
for selection in selections {
|
||||
let range = editor::ToOffset::to_offset(&selection.start, &buffer)
|
||||
|
||||
@@ -636,7 +636,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
kind: AssistantEditKind::InsertAfter {
|
||||
old_text: "fn one".into(),
|
||||
new_text: "fn two() {}".into(),
|
||||
description: "add a `two` function".into(),
|
||||
description: Some("add a `two` function".into()),
|
||||
},
|
||||
}]],
|
||||
cx,
|
||||
@@ -690,7 +690,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
kind: AssistantEditKind::InsertAfter {
|
||||
old_text: "fn zero".into(),
|
||||
new_text: "fn two() {}".into(),
|
||||
description: "add a `two` function".into(),
|
||||
description: Some("add a `two` function".into()),
|
||||
},
|
||||
}]],
|
||||
cx,
|
||||
@@ -754,7 +754,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
kind: AssistantEditKind::InsertAfter {
|
||||
old_text: "fn zero".into(),
|
||||
new_text: "fn two() {}".into(),
|
||||
description: "add a `two` function".into(),
|
||||
description: Some("add a `two` function".into()),
|
||||
},
|
||||
}]],
|
||||
cx,
|
||||
@@ -798,7 +798,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
kind: AssistantEditKind::InsertAfter {
|
||||
old_text: "fn zero".into(),
|
||||
new_text: "fn two() {}".into(),
|
||||
description: "add a `two` function".into(),
|
||||
description: Some("add a `two` function".into()),
|
||||
},
|
||||
}]],
|
||||
cx,
|
||||
|
||||
@@ -54,7 +54,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, Popover, Tooltip};
|
||||
use ui::{prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, Popover, Tooltip};
|
||||
use util::{RangeExt, ResultExt};
|
||||
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
|
||||
|
||||
@@ -189,11 +189,16 @@ impl InlineAssistant {
|
||||
initial_prompt: Option<String>,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
let (snapshot, initial_selections) = editor.update(cx, |editor, cx| {
|
||||
(
|
||||
editor.buffer().read(cx).snapshot(cx),
|
||||
editor.selections.all::<Point>(cx),
|
||||
)
|
||||
});
|
||||
|
||||
let mut selections = Vec::<Selection<Point>>::new();
|
||||
let mut newest_selection = None;
|
||||
for mut selection in editor.read(cx).selections.all::<Point>(cx) {
|
||||
for mut selection in initial_selections {
|
||||
if selection.end > selection.start {
|
||||
selection.start.column = 0;
|
||||
// If the selection ends at the start of the line, we don't want to include it.
|
||||
@@ -1596,7 +1601,7 @@ impl PromptEditor {
|
||||
// always show the cursor (even when it isn't focused) because
|
||||
// typing in one will make what you typed appear in all of them.
|
||||
editor.set_show_cursor_when_unfocused(true, cx);
|
||||
editor.set_placeholder_text("Add a prompt…", cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx), cx), cx);
|
||||
editor
|
||||
});
|
||||
|
||||
@@ -1653,6 +1658,7 @@ impl PromptEditor {
|
||||
self.editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx);
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(self.codegen.read(cx), cx), cx);
|
||||
editor.set_placeholder_text("Add a prompt…", cx);
|
||||
editor.set_text(prompt, cx);
|
||||
if focus {
|
||||
@@ -1663,6 +1669,20 @@ impl PromptEditor {
|
||||
self.subscribe_to_editor(cx);
|
||||
}
|
||||
|
||||
fn placeholder_text(codegen: &Codegen, cx: &WindowContext) -> String {
|
||||
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
|
||||
.map(|keybinding| format!(" • {keybinding} for context"))
|
||||
.unwrap_or_default();
|
||||
|
||||
let action = if codegen.is_insertion {
|
||||
"Generate"
|
||||
} else {
|
||||
"Transform"
|
||||
};
|
||||
|
||||
format!("{action}…{context_keybinding} • ↓↑ for history")
|
||||
}
|
||||
|
||||
fn prompt(&self, cx: &AppContext) -> String {
|
||||
self.editor.read(cx).text(cx)
|
||||
}
|
||||
@@ -2260,6 +2280,7 @@ pub struct Codegen {
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
is_insertion: bool,
|
||||
}
|
||||
|
||||
impl Codegen {
|
||||
@@ -2282,6 +2303,7 @@ impl Codegen {
|
||||
)
|
||||
});
|
||||
let mut this = Self {
|
||||
is_insertion: range.to_offset(&buffer.read(cx).snapshot(cx)).is_empty(),
|
||||
alternatives: vec![codegen],
|
||||
active_alternative: 0,
|
||||
seen_alternatives: HashSet::default(),
|
||||
@@ -2683,7 +2705,7 @@ impl CodegenAlternative {
|
||||
|
||||
let prompt = self
|
||||
.builder
|
||||
.generate_content_prompt(user_prompt, language_name, buffer, range)
|
||||
.generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?;
|
||||
|
||||
let mut messages = Vec::new();
|
||||
|
||||
@@ -33,21 +33,21 @@ pub enum AssistantEditKind {
|
||||
Update {
|
||||
old_text: String,
|
||||
new_text: String,
|
||||
description: String,
|
||||
description: Option<String>,
|
||||
},
|
||||
Create {
|
||||
new_text: String,
|
||||
description: String,
|
||||
description: Option<String>,
|
||||
},
|
||||
InsertBefore {
|
||||
old_text: String,
|
||||
new_text: String,
|
||||
description: String,
|
||||
description: Option<String>,
|
||||
},
|
||||
InsertAfter {
|
||||
old_text: String,
|
||||
new_text: String,
|
||||
description: String,
|
||||
description: Option<String>,
|
||||
},
|
||||
Delete {
|
||||
old_text: String,
|
||||
@@ -86,19 +86,37 @@ enum SearchDirection {
|
||||
Diagonal,
|
||||
}
|
||||
|
||||
// A measure of the currently quality of an in-progress fuzzy search.
|
||||
//
|
||||
// Uses 60 bits to store a numeric cost, and 4 bits to store the preceding
|
||||
// operation in the search.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct SearchState {
|
||||
score: u32,
|
||||
cost: u32,
|
||||
direction: SearchDirection,
|
||||
}
|
||||
|
||||
impl SearchState {
|
||||
fn new(score: u32, direction: SearchDirection) -> Self {
|
||||
Self { score, direction }
|
||||
fn new(cost: u32, direction: SearchDirection) -> Self {
|
||||
Self { cost, direction }
|
||||
}
|
||||
}
|
||||
|
||||
struct SearchMatrix {
|
||||
cols: usize,
|
||||
data: Vec<SearchState>,
|
||||
}
|
||||
|
||||
impl SearchMatrix {
|
||||
fn new(rows: usize, cols: usize) -> Self {
|
||||
SearchMatrix {
|
||||
cols,
|
||||
data: vec![SearchState::new(0, SearchDirection::Diagonal); rows * cols],
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, row: usize, col: usize) -> SearchState {
|
||||
self.data[row * self.cols + col]
|
||||
}
|
||||
|
||||
fn set(&mut self, row: usize, col: usize, cost: SearchState) {
|
||||
self.data[row * self.cols + col] = cost;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,23 +205,23 @@ impl AssistantEdit {
|
||||
"update" => AssistantEditKind::Update {
|
||||
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
|
||||
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
|
||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
||||
description,
|
||||
},
|
||||
"insert_before" => AssistantEditKind::InsertBefore {
|
||||
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
|
||||
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
|
||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
||||
description,
|
||||
},
|
||||
"insert_after" => AssistantEditKind::InsertAfter {
|
||||
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
|
||||
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
|
||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
||||
description,
|
||||
},
|
||||
"delete" => AssistantEditKind::Delete {
|
||||
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
|
||||
},
|
||||
"create" => AssistantEditKind::Create {
|
||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
||||
description,
|
||||
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
|
||||
},
|
||||
_ => Err(anyhow!("unknown operation {operation:?}"))?,
|
||||
@@ -264,7 +282,7 @@ impl AssistantEditKind {
|
||||
ResolvedEdit {
|
||||
range,
|
||||
new_text,
|
||||
description: Some(description),
|
||||
description,
|
||||
}
|
||||
}
|
||||
Self::Create {
|
||||
@@ -272,7 +290,7 @@ impl AssistantEditKind {
|
||||
description,
|
||||
} => ResolvedEdit {
|
||||
range: text::Anchor::MIN..text::Anchor::MAX,
|
||||
description: Some(description),
|
||||
description,
|
||||
new_text,
|
||||
},
|
||||
Self::InsertBefore {
|
||||
@@ -285,7 +303,7 @@ impl AssistantEditKind {
|
||||
ResolvedEdit {
|
||||
range: range.start..range.start,
|
||||
new_text,
|
||||
description: Some(description),
|
||||
description,
|
||||
}
|
||||
}
|
||||
Self::InsertAfter {
|
||||
@@ -298,7 +316,7 @@ impl AssistantEditKind {
|
||||
ResolvedEdit {
|
||||
range: range.end..range.end,
|
||||
new_text,
|
||||
description: Some(description),
|
||||
description,
|
||||
}
|
||||
}
|
||||
Self::Delete { old_text } => {
|
||||
@@ -314,44 +332,29 @@ impl AssistantEditKind {
|
||||
|
||||
fn resolve_location(buffer: &text::BufferSnapshot, search_query: &str) -> Range<text::Anchor> {
|
||||
const INSERTION_COST: u32 = 3;
|
||||
const DELETION_COST: u32 = 10;
|
||||
const WHITESPACE_INSERTION_COST: u32 = 1;
|
||||
const DELETION_COST: u32 = 3;
|
||||
const WHITESPACE_DELETION_COST: u32 = 1;
|
||||
const EQUALITY_BONUS: u32 = 5;
|
||||
|
||||
struct Matrix {
|
||||
cols: usize,
|
||||
data: Vec<SearchState>,
|
||||
}
|
||||
|
||||
impl Matrix {
|
||||
fn new(rows: usize, cols: usize) -> Self {
|
||||
Matrix {
|
||||
cols,
|
||||
data: vec![SearchState::new(0, SearchDirection::Diagonal); rows * cols],
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, row: usize, col: usize) -> SearchState {
|
||||
self.data[row * self.cols + col]
|
||||
}
|
||||
|
||||
fn set(&mut self, row: usize, col: usize, cost: SearchState) {
|
||||
self.data[row * self.cols + col] = cost;
|
||||
}
|
||||
}
|
||||
|
||||
let buffer_len = buffer.len();
|
||||
let query_len = search_query.len();
|
||||
let mut matrix = Matrix::new(query_len + 1, buffer_len + 1);
|
||||
|
||||
let mut matrix = SearchMatrix::new(query_len + 1, buffer_len + 1);
|
||||
let mut leading_deletion_cost = 0_u32;
|
||||
for (row, query_byte) in search_query.bytes().enumerate() {
|
||||
let deletion_cost = if query_byte.is_ascii_whitespace() {
|
||||
WHITESPACE_DELETION_COST
|
||||
} else {
|
||||
DELETION_COST
|
||||
};
|
||||
|
||||
leading_deletion_cost = leading_deletion_cost.saturating_add(deletion_cost);
|
||||
matrix.set(
|
||||
row + 1,
|
||||
0,
|
||||
SearchState::new(leading_deletion_cost, SearchDirection::Diagonal),
|
||||
);
|
||||
|
||||
for (col, buffer_byte) in buffer.bytes_in_range(0..buffer.len()).flatten().enumerate() {
|
||||
let deletion_cost = if query_byte.is_ascii_whitespace() {
|
||||
WHITESPACE_DELETION_COST
|
||||
} else {
|
||||
DELETION_COST
|
||||
};
|
||||
let insertion_cost = if buffer_byte.is_ascii_whitespace() {
|
||||
WHITESPACE_INSERTION_COST
|
||||
} else {
|
||||
@@ -359,38 +362,35 @@ impl AssistantEditKind {
|
||||
};
|
||||
|
||||
let up = SearchState::new(
|
||||
matrix.get(row, col + 1).score.saturating_sub(deletion_cost),
|
||||
matrix.get(row, col + 1).cost.saturating_add(deletion_cost),
|
||||
SearchDirection::Up,
|
||||
);
|
||||
let left = SearchState::new(
|
||||
matrix
|
||||
.get(row + 1, col)
|
||||
.score
|
||||
.saturating_sub(insertion_cost),
|
||||
matrix.get(row + 1, col).cost.saturating_add(insertion_cost),
|
||||
SearchDirection::Left,
|
||||
);
|
||||
let diagonal = SearchState::new(
|
||||
if query_byte == *buffer_byte {
|
||||
matrix.get(row, col).score.saturating_add(EQUALITY_BONUS)
|
||||
matrix.get(row, col).cost
|
||||
} else {
|
||||
matrix
|
||||
.get(row, col)
|
||||
.score
|
||||
.saturating_sub(deletion_cost + insertion_cost)
|
||||
.cost
|
||||
.saturating_add(deletion_cost + insertion_cost)
|
||||
},
|
||||
SearchDirection::Diagonal,
|
||||
);
|
||||
matrix.set(row + 1, col + 1, up.max(left).max(diagonal));
|
||||
matrix.set(row + 1, col + 1, up.min(left).min(diagonal));
|
||||
}
|
||||
}
|
||||
|
||||
// Traceback to find the best match
|
||||
let mut best_buffer_end = buffer_len;
|
||||
let mut best_score = 0;
|
||||
let mut best_cost = u32::MAX;
|
||||
for col in 1..=buffer_len {
|
||||
let score = matrix.get(query_len, col).score;
|
||||
if score > best_score {
|
||||
best_score = score;
|
||||
let cost = matrix.get(query_len, col).cost;
|
||||
if cost < best_cost {
|
||||
best_cost = cost;
|
||||
best_buffer_end = col;
|
||||
}
|
||||
}
|
||||
@@ -560,89 +560,84 @@ mod tests {
|
||||
language_settings::AllLanguageSettings, Language, LanguageConfig, LanguageMatcher,
|
||||
};
|
||||
use settings::SettingsStore;
|
||||
use text::{OffsetRangeExt, Point};
|
||||
use ui::BorrowAppContext;
|
||||
use unindent::Unindent as _;
|
||||
use util::test::{generate_marked_text, marked_text_ranges};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_resolve_location(cx: &mut AppContext) {
|
||||
{
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::local(
|
||||
concat!(
|
||||
" Lorem\n",
|
||||
" ipsum\n",
|
||||
" dolor sit amet\n",
|
||||
" consecteur",
|
||||
),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
assert_eq!(
|
||||
AssistantEditKind::resolve_location(&snapshot, "ipsum\ndolor").to_point(&snapshot),
|
||||
Point::new(1, 0)..Point::new(2, 18)
|
||||
);
|
||||
}
|
||||
assert_location_resolution(
|
||||
concat!(
|
||||
" Lorem\n",
|
||||
"« ipsum\n",
|
||||
" dolor sit amet»\n",
|
||||
" consecteur",
|
||||
),
|
||||
"ipsum\ndolor",
|
||||
cx,
|
||||
);
|
||||
|
||||
{
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::local(
|
||||
concat!(
|
||||
"fn foo1(a: usize) -> usize {\n",
|
||||
" 40\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"fn foo2(b: usize) -> usize {\n",
|
||||
" 42\n",
|
||||
"}\n",
|
||||
),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
assert_eq!(
|
||||
AssistantEditKind::resolve_location(&snapshot, "fn foo1(b: usize) {\n40\n}")
|
||||
.to_point(&snapshot),
|
||||
Point::new(0, 0)..Point::new(2, 1)
|
||||
);
|
||||
}
|
||||
assert_location_resolution(
|
||||
&"
|
||||
«fn foo1(a: usize) -> usize {
|
||||
40
|
||||
}»
|
||||
|
||||
{
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::local(
|
||||
concat!(
|
||||
"fn main() {\n",
|
||||
" Foo\n",
|
||||
" .bar()\n",
|
||||
" .baz()\n",
|
||||
" .qux()\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"fn foo2(b: usize) -> usize {\n",
|
||||
" 42\n",
|
||||
"}\n",
|
||||
),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
assert_eq!(
|
||||
AssistantEditKind::resolve_location(&snapshot, "Foo.bar.baz.qux()")
|
||||
.to_point(&snapshot),
|
||||
Point::new(1, 0)..Point::new(4, 14)
|
||||
);
|
||||
}
|
||||
fn foo2(b: usize) -> usize {
|
||||
42
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
"fn foo1(b: usize) {\n40\n}",
|
||||
cx,
|
||||
);
|
||||
|
||||
assert_location_resolution(
|
||||
&"
|
||||
fn main() {
|
||||
« Foo
|
||||
.bar()
|
||||
.baz()
|
||||
.qux()»
|
||||
}
|
||||
|
||||
fn foo2(b: usize) -> usize {
|
||||
42
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
"Foo.bar.baz.qux()",
|
||||
cx,
|
||||
);
|
||||
|
||||
assert_location_resolution(
|
||||
&"
|
||||
class Something {
|
||||
one() { return 1; }
|
||||
« two() { return 2222; }
|
||||
three() { return 333; }
|
||||
four() { return 4444; }
|
||||
five() { return 5555; }
|
||||
six() { return 6666; }
|
||||
» seven() { return 7; }
|
||||
eight() { return 8; }
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
&"
|
||||
two() { return 2222; }
|
||||
four() { return 4444; }
|
||||
five() { return 5555; }
|
||||
six() { return 6666; }
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_resolve_edits(cx: &mut AppContext) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
language::init(cx);
|
||||
cx.update_global::<SettingsStore, _>(|settings, cx| {
|
||||
settings.update_user_settings::<AllLanguageSettings>(cx, |_| {});
|
||||
});
|
||||
init_test(cx);
|
||||
|
||||
assert_edits(
|
||||
"
|
||||
@@ -675,7 +670,7 @@ mod tests {
|
||||
last_name: String,
|
||||
"
|
||||
.unindent(),
|
||||
description: "".into(),
|
||||
description: None,
|
||||
},
|
||||
AssistantEditKind::Update {
|
||||
old_text: "
|
||||
@@ -690,7 +685,7 @@ mod tests {
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
description: "".into(),
|
||||
description: None,
|
||||
},
|
||||
],
|
||||
"
|
||||
@@ -734,7 +729,7 @@ mod tests {
|
||||
qux();
|
||||
}"
|
||||
.unindent(),
|
||||
description: "implement bar".into(),
|
||||
description: Some("implement bar".into()),
|
||||
},
|
||||
AssistantEditKind::Update {
|
||||
old_text: "
|
||||
@@ -747,7 +742,7 @@ mod tests {
|
||||
bar();
|
||||
}"
|
||||
.unindent(),
|
||||
description: "call bar in foo".into(),
|
||||
description: Some("call bar in foo".into()),
|
||||
},
|
||||
AssistantEditKind::InsertAfter {
|
||||
old_text: "
|
||||
@@ -762,7 +757,7 @@ mod tests {
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
description: "implement qux".into(),
|
||||
description: Some("implement qux".into()),
|
||||
},
|
||||
],
|
||||
"
|
||||
@@ -814,7 +809,7 @@ mod tests {
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
description: "pick better number".into(),
|
||||
description: None,
|
||||
},
|
||||
AssistantEditKind::Update {
|
||||
old_text: "
|
||||
@@ -829,7 +824,7 @@ mod tests {
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
description: "pick better number".into(),
|
||||
description: None,
|
||||
},
|
||||
AssistantEditKind::Update {
|
||||
old_text: "
|
||||
@@ -844,7 +839,7 @@ mod tests {
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
description: "pick better number".into(),
|
||||
description: None,
|
||||
},
|
||||
],
|
||||
"
|
||||
@@ -865,6 +860,69 @@ mod tests {
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
assert_edits(
|
||||
"
|
||||
impl Person {
|
||||
fn set_name(&mut self, name: String) {
|
||||
self.name = name;
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
return self.name;
|
||||
}
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
vec![
|
||||
AssistantEditKind::Update {
|
||||
old_text: "self.name = name;".unindent(),
|
||||
new_text: "self._name = name;".unindent(),
|
||||
description: None,
|
||||
},
|
||||
AssistantEditKind::Update {
|
||||
old_text: "return self.name;\n".unindent(),
|
||||
new_text: "return self._name;\n".unindent(),
|
||||
description: None,
|
||||
},
|
||||
],
|
||||
"
|
||||
impl Person {
|
||||
fn set_name(&mut self, name: String) {
|
||||
self._name = name;
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
return self._name;
|
||||
}
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut AppContext) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
language::init(cx);
|
||||
cx.update_global::<SettingsStore, _>(|settings, cx| {
|
||||
settings.update_user_settings::<AllLanguageSettings>(cx, |_| {});
|
||||
});
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_location_resolution(
|
||||
text_with_expected_range: &str,
|
||||
query: &str,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
let (text, _) = marked_text_ranges(text_with_expected_range, false);
|
||||
let buffer = cx.new_model(|cx| Buffer::local(text.clone(), cx));
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let range = AssistantEditKind::resolve_location(&snapshot, query).to_offset(&snapshot);
|
||||
let text_with_actual_range = generate_marked_text(&text, &[range], false);
|
||||
pretty_assertions::assert_eq!(text_with_actual_range, text_with_expected_range);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
||||
@@ -204,7 +204,7 @@ impl PromptBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_content_prompt(
|
||||
pub fn generate_inline_transformation_prompt(
|
||||
&self,
|
||||
user_prompt: String,
|
||||
language_name: Option<&LanguageName>,
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod context_server_tool;
|
||||
pub mod now_tool;
|
||||
|
||||
82
crates/assistant/src/tools/context_server_tool.rs
Normal file
82
crates/assistant/src/tools/context_server_tool.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use anyhow::{anyhow, bail};
|
||||
use assistant_tool::Tool;
|
||||
use context_servers::manager::ContextServerManager;
|
||||
use context_servers::types;
|
||||
use gpui::Task;
|
||||
|
||||
pub struct ContextServerTool {
|
||||
server_id: String,
|
||||
tool: types::Tool,
|
||||
}
|
||||
|
||||
impl ContextServerTool {
|
||||
pub fn new(server_id: impl Into<String>, tool: types::Tool) -> Self {
|
||||
Self {
|
||||
server_id: server_id.into(),
|
||||
tool,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tool for ContextServerTool {
|
||||
fn name(&self) -> String {
|
||||
self.tool.name.clone()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
self.tool.description.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn input_schema(&self) -> serde_json::Value {
|
||||
match &self.tool.input_schema {
|
||||
serde_json::Value::Null => {
|
||||
serde_json::json!({ "type": "object", "properties": [] })
|
||||
}
|
||||
serde_json::Value::Object(map) if map.is_empty() => {
|
||||
serde_json::json!({ "type": "object", "properties": [] })
|
||||
}
|
||||
_ => self.tool.input_schema.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: std::sync::Arc<Self>,
|
||||
input: serde_json::Value,
|
||||
_workspace: gpui::WeakView<workspace::Workspace>,
|
||||
cx: &mut ui::WindowContext,
|
||||
) -> gpui::Task<gpui::Result<String>> {
|
||||
let manager = ContextServerManager::global(cx);
|
||||
let manager = manager.read(cx);
|
||||
if let Some(server) = manager.get_server(&self.server_id) {
|
||||
cx.foreground_executor().spawn({
|
||||
let tool_name = self.tool.name.clone();
|
||||
async move {
|
||||
let Some(protocol) = server.client.read().clone() else {
|
||||
bail!("Context server not initialized");
|
||||
};
|
||||
|
||||
let arguments = if let serde_json::Value::Object(map) = input {
|
||||
Some(map.into_iter().collect())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
log::trace!(
|
||||
"Running tool: {} with arguments: {:?}",
|
||||
tool_name,
|
||||
arguments
|
||||
);
|
||||
let response = protocol.run_tool(tool_name, arguments).await?;
|
||||
|
||||
let tool_result = match response.tool_result {
|
||||
serde_json::Value::String(s) => s,
|
||||
_ => serde_json::to_string(&response.tool_result)?,
|
||||
};
|
||||
Ok(tool_result)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("Context server not found")))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ mod channel_index;
|
||||
use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage};
|
||||
use anyhow::{anyhow, Result};
|
||||
use channel_index::ChannelIndex;
|
||||
use client::{ChannelId, Client, ClientSettings, ProjectId, Subscription, User, UserId, UserStore};
|
||||
use client::{ChannelId, Client, ClientSettings, Subscription, User, UserId, UserStore};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
||||
use gpui::{
|
||||
@@ -33,30 +33,11 @@ struct NotesVersion {
|
||||
version: clock::Global,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HostedProject {
|
||||
project_id: ProjectId,
|
||||
channel_id: ChannelId,
|
||||
name: SharedString,
|
||||
_visibility: proto::ChannelVisibility,
|
||||
}
|
||||
impl From<proto::HostedProject> for HostedProject {
|
||||
fn from(project: proto::HostedProject) -> Self {
|
||||
Self {
|
||||
project_id: ProjectId(project.project_id),
|
||||
channel_id: ChannelId(project.channel_id),
|
||||
_visibility: project.visibility(),
|
||||
name: project.name.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct ChannelStore {
|
||||
pub channel_index: ChannelIndex,
|
||||
channel_invitations: Vec<Arc<Channel>>,
|
||||
channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
|
||||
channel_states: HashMap<ChannelId, ChannelState>,
|
||||
hosted_projects: HashMap<ProjectId, HostedProject>,
|
||||
|
||||
outgoing_invites: HashSet<(ChannelId, UserId)>,
|
||||
update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
|
||||
opened_buffers: HashMap<ChannelId, OpenedModelHandle<ChannelBuffer>>,
|
||||
@@ -85,7 +66,6 @@ pub struct ChannelState {
|
||||
observed_notes_version: NotesVersion,
|
||||
observed_chat_message: Option<u64>,
|
||||
role: Option<ChannelRole>,
|
||||
projects: HashSet<ProjectId>,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
@@ -216,7 +196,6 @@ impl ChannelStore {
|
||||
channel_invitations: Vec::default(),
|
||||
channel_index: ChannelIndex::default(),
|
||||
channel_participants: Default::default(),
|
||||
hosted_projects: Default::default(),
|
||||
outgoing_invites: Default::default(),
|
||||
opened_buffers: Default::default(),
|
||||
opened_chats: Default::default(),
|
||||
@@ -316,19 +295,6 @@ impl ChannelStore {
|
||||
self.channel_index.by_id().get(&channel_id)
|
||||
}
|
||||
|
||||
pub fn projects_for_id(&self, channel_id: ChannelId) -> Vec<(SharedString, ProjectId)> {
|
||||
let mut projects: Vec<(SharedString, ProjectId)> = self
|
||||
.channel_states
|
||||
.get(&channel_id)
|
||||
.map(|state| state.projects.clone())
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.flat_map(|id| Some((self.hosted_projects.get(&id)?.name.clone(), id)))
|
||||
.collect();
|
||||
projects.sort();
|
||||
projects
|
||||
}
|
||||
|
||||
pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool {
|
||||
if let Some(buffer) = self.opened_buffers.get(&channel_id) {
|
||||
if let OpenedModelHandle::Open(buffer) = buffer {
|
||||
@@ -1102,9 +1068,7 @@ impl ChannelStore {
|
||||
let channels_changed = !payload.channels.is_empty()
|
||||
|| !payload.delete_channels.is_empty()
|
||||
|| !payload.latest_channel_message_ids.is_empty()
|
||||
|| !payload.latest_channel_buffer_versions.is_empty()
|
||||
|| !payload.hosted_projects.is_empty()
|
||||
|| !payload.deleted_hosted_projects.is_empty();
|
||||
|| !payload.latest_channel_buffer_versions.is_empty();
|
||||
|
||||
if channels_changed {
|
||||
if !payload.delete_channels.is_empty() {
|
||||
@@ -1161,34 +1125,6 @@ impl ChannelStore {
|
||||
.or_default()
|
||||
.update_latest_message_id(latest_channel_message.message_id);
|
||||
}
|
||||
|
||||
for hosted_project in payload.hosted_projects {
|
||||
let hosted_project: HostedProject = hosted_project.into();
|
||||
if let Some(old_project) = self
|
||||
.hosted_projects
|
||||
.insert(hosted_project.project_id, hosted_project.clone())
|
||||
{
|
||||
self.channel_states
|
||||
.entry(old_project.channel_id)
|
||||
.or_default()
|
||||
.remove_hosted_project(old_project.project_id);
|
||||
}
|
||||
self.channel_states
|
||||
.entry(hosted_project.channel_id)
|
||||
.or_default()
|
||||
.add_hosted_project(hosted_project.project_id);
|
||||
}
|
||||
|
||||
for hosted_project_id in payload.deleted_hosted_projects {
|
||||
let hosted_project_id = ProjectId(hosted_project_id);
|
||||
|
||||
if let Some(old_project) = self.hosted_projects.remove(&hosted_project_id) {
|
||||
self.channel_states
|
||||
.entry(old_project.channel_id)
|
||||
.or_default()
|
||||
.remove_hosted_project(old_project.project_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
@@ -1295,12 +1231,4 @@ impl ChannelState {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn add_hosted_project(&mut self, project_id: ProjectId) {
|
||||
self.projects.insert(project_id);
|
||||
}
|
||||
|
||||
fn remove_hosted_project(&mut self, project_id: ProjectId) {
|
||||
self.projects.remove(&project_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +252,10 @@ async fn create_billing_subscription(
|
||||
|
||||
let default_model = llm_db.model(rpc::LanguageModelProvider::Anthropic, "claude-3-5-sonnet")?;
|
||||
let stripe_model = stripe_billing.register_model(default_model).await?;
|
||||
let success_url = format!("{}/account", app.config.zed_dot_dev_url());
|
||||
let success_url = format!(
|
||||
"{}/account?checkout_complete=1",
|
||||
app.config.zed_dot_dev_url()
|
||||
);
|
||||
let checkout_session_url = stripe_billing
|
||||
.checkout(customer_id, &user.github_login, &stripe_model, &success_url)
|
||||
.await?;
|
||||
|
||||
@@ -617,7 +617,6 @@ pub struct ChannelsForUser {
|
||||
pub channels: Vec<Channel>,
|
||||
pub channel_memberships: Vec<channel_member::Model>,
|
||||
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
|
||||
pub hosted_projects: Vec<proto::HostedProject>,
|
||||
pub invited_channels: Vec<Channel>,
|
||||
|
||||
pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
|
||||
|
||||
@@ -10,7 +10,6 @@ pub mod contacts;
|
||||
pub mod contributors;
|
||||
pub mod embeddings;
|
||||
pub mod extensions;
|
||||
pub mod hosted_projects;
|
||||
pub mod messages;
|
||||
pub mod notifications;
|
||||
pub mod processed_stripe_events;
|
||||
|
||||
@@ -615,15 +615,10 @@ impl Database {
|
||||
.observed_channel_messages(&channel_ids, user_id, tx)
|
||||
.await?;
|
||||
|
||||
let hosted_projects = self
|
||||
.get_hosted_projects(&channel_ids, &roles_by_channel_id, tx)
|
||||
.await?;
|
||||
|
||||
Ok(ChannelsForUser {
|
||||
channel_memberships,
|
||||
channels,
|
||||
invited_channels,
|
||||
hosted_projects,
|
||||
channel_participants,
|
||||
latest_buffer_versions,
|
||||
latest_channel_messages,
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
use rpc::{proto, ErrorCode};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl Database {
|
||||
pub async fn get_hosted_projects(
|
||||
&self,
|
||||
channel_ids: &[ChannelId],
|
||||
roles: &HashMap<ChannelId, ChannelRole>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<proto::HostedProject>> {
|
||||
let projects = hosted_project::Entity::find()
|
||||
.find_also_related(project::Entity)
|
||||
.filter(hosted_project::Column::ChannelId.is_in(channel_ids.iter().map(|id| id.0)))
|
||||
.all(tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|(hosted_project, project)| {
|
||||
if hosted_project.deleted_at.is_some() {
|
||||
return None;
|
||||
}
|
||||
match hosted_project.visibility {
|
||||
ChannelVisibility::Public => {}
|
||||
ChannelVisibility::Members => {
|
||||
let is_visible = roles
|
||||
.get(&hosted_project.channel_id)
|
||||
.map(|role| role.can_see_all_descendants())
|
||||
.unwrap_or(false);
|
||||
if !is_visible {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
Some(proto::HostedProject {
|
||||
project_id: project?.id.to_proto(),
|
||||
channel_id: hosted_project.channel_id.to_proto(),
|
||||
name: hosted_project.name.clone(),
|
||||
visibility: hosted_project.visibility.into(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(projects)
|
||||
}
|
||||
|
||||
pub async fn get_hosted_project(
|
||||
&self,
|
||||
hosted_project_id: HostedProjectId,
|
||||
user_id: UserId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<(hosted_project::Model, ChannelRole)> {
|
||||
let project = hosted_project::Entity::find_by_id(hosted_project_id)
|
||||
.one(tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!(ErrorCode::NoSuchProject))?;
|
||||
let channel = channel::Entity::find_by_id(project.channel_id)
|
||||
.one(tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!(ErrorCode::NoSuchChannel))?;
|
||||
|
||||
let role = match project.visibility {
|
||||
ChannelVisibility::Public => {
|
||||
self.check_user_is_channel_participant(&channel, user_id, tx)
|
||||
.await?
|
||||
}
|
||||
ChannelVisibility::Members => {
|
||||
self.check_user_is_channel_member(&channel, user_id, tx)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
Ok((project, role))
|
||||
}
|
||||
|
||||
pub async fn is_hosted_project(&self, project_id: ProjectId) -> Result<bool> {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(project::Entity::find_by_id(project_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.map(|project| project.hosted_project_id.is_some())
|
||||
.ok_or_else(|| anyhow!(ErrorCode::NoSuchProject))?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,6 @@ impl Database {
|
||||
connection.owner_id as i32,
|
||||
))),
|
||||
id: ActiveValue::NotSet,
|
||||
hosted_project_id: ActiveValue::Set(None),
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?;
|
||||
@@ -536,39 +535,6 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Adds the given connection to the specified hosted project
|
||||
pub async fn join_hosted_project(
|
||||
&self,
|
||||
id: ProjectId,
|
||||
user_id: UserId,
|
||||
connection: ConnectionId,
|
||||
) -> Result<(Project, ReplicaId)> {
|
||||
self.transaction(|tx| async move {
|
||||
let (project, hosted_project) = project::Entity::find_by_id(id)
|
||||
.find_also_related(hosted_project::Entity)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("hosted project is no longer shared"))?;
|
||||
|
||||
let Some(hosted_project) = hosted_project else {
|
||||
return Err(anyhow!("project is not hosted"))?;
|
||||
};
|
||||
|
||||
let channel = channel::Entity::find_by_id(hosted_project.channel_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no such channel"))?;
|
||||
|
||||
let role = self
|
||||
.check_user_is_channel_participant(&channel, user_id, &tx)
|
||||
.await?;
|
||||
|
||||
self.join_project_internal(project, user_id, connection, role, &tx)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_project(&self, id: ProjectId) -> Result<project::Model> {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(project::Entity::find_by_id(id)
|
||||
|
||||
@@ -18,7 +18,6 @@ pub mod extension;
|
||||
pub mod extension_version;
|
||||
pub mod feature_flag;
|
||||
pub mod follower;
|
||||
pub mod hosted_project;
|
||||
pub mod language_server;
|
||||
pub mod notification;
|
||||
pub mod notification_kind;
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
use crate::db::{ChannelId, ChannelVisibility, HostedProjectId};
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "hosted_projects")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: HostedProjectId,
|
||||
pub channel_id: ChannelId,
|
||||
pub name: String,
|
||||
pub visibility: ChannelVisibility,
|
||||
pub deleted_at: Option<DateTime>,
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_one = "super::project::Entity")]
|
||||
Project,
|
||||
}
|
||||
|
||||
impl Related<super::project::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Project.def()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::db::{HostedProjectId, ProjectId, Result, RoomId, ServerId, UserId};
|
||||
use crate::db::{ProjectId, Result, RoomId, ServerId, UserId};
|
||||
use anyhow::anyhow;
|
||||
use rpc::ConnectionId;
|
||||
use sea_orm::entity::prelude::*;
|
||||
@@ -12,7 +12,6 @@ pub struct Model {
|
||||
pub host_user_id: Option<UserId>,
|
||||
pub host_connection_id: Option<i32>,
|
||||
pub host_connection_server_id: Option<ServerId>,
|
||||
pub hosted_project_id: Option<HostedProjectId>,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
@@ -50,12 +49,6 @@ pub enum Relation {
|
||||
Collaborators,
|
||||
#[sea_orm(has_many = "super::language_server::Entity")]
|
||||
LanguageServers,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::hosted_project::Entity",
|
||||
from = "Column::HostedProjectId",
|
||||
to = "super::hosted_project::Column::Id"
|
||||
)]
|
||||
HostedProject,
|
||||
}
|
||||
|
||||
impl Related<super::user::Entity> for Entity {
|
||||
@@ -88,10 +81,4 @@ impl Related<super::language_server::Entity> for Entity {
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::hosted_project::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::HostedProject.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
@@ -449,6 +449,10 @@ async fn check_usage_limit(
|
||||
model_name: &str,
|
||||
claims: &LlmTokenClaims,
|
||||
) -> Result<()> {
|
||||
if claims.is_staff {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let model = state.db.model(provider, model_name)?;
|
||||
let usage = state
|
||||
.db
|
||||
@@ -513,11 +517,6 @@ async fn check_usage_limit(
|
||||
];
|
||||
|
||||
for (used, limit, usage_measure) in checks {
|
||||
// Temporarily bypass rate-limiting for staff members.
|
||||
if claims.is_staff {
|
||||
continue;
|
||||
}
|
||||
|
||||
if used > limit {
|
||||
let resource = match usage_measure {
|
||||
UsageMeasure::RequestsPerMinute => "requests_per_minute",
|
||||
|
||||
@@ -287,7 +287,6 @@ impl Server {
|
||||
.add_request_handler(share_project)
|
||||
.add_message_handler(unshare_project)
|
||||
.add_request_handler(join_project)
|
||||
.add_request_handler(join_hosted_project)
|
||||
.add_message_handler(leave_project)
|
||||
.add_request_handler(update_project)
|
||||
.add_request_handler(update_worktree)
|
||||
@@ -308,6 +307,8 @@ impl Server {
|
||||
.add_request_handler(forward_read_only_project_request::<proto::InlayHints>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::ResolveInlayHint>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GitBranches>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::UpdateGitBranch>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::GetCompletions>)
|
||||
.add_request_handler(
|
||||
forward_mutating_project_request::<proto::ApplyCompletionAdditionalEdits>,
|
||||
@@ -1793,11 +1794,6 @@ impl JoinProjectInternalResponse for Response<proto::JoinProject> {
|
||||
Response::<proto::JoinProject>::send(self, result)
|
||||
}
|
||||
}
|
||||
impl JoinProjectInternalResponse for Response<proto::JoinHostedProject> {
|
||||
fn send(self, result: proto::JoinProjectResponse) -> Result<()> {
|
||||
Response::<proto::JoinHostedProject>::send(self, result)
|
||||
}
|
||||
}
|
||||
|
||||
fn join_project_internal(
|
||||
response: impl JoinProjectInternalResponse,
|
||||
@@ -1921,11 +1917,6 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result
|
||||
let sender_id = session.connection_id;
|
||||
let project_id = ProjectId::from_proto(request.project_id);
|
||||
let db = session.db().await;
|
||||
if db.is_hosted_project(project_id).await? {
|
||||
let project = db.leave_hosted_project(project_id, sender_id).await?;
|
||||
project_left(&project, &session);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (room, project) = &*db.leave_project(project_id, sender_id).await?;
|
||||
tracing::info!(
|
||||
@@ -1941,24 +1932,6 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn join_hosted_project(
|
||||
request: proto::JoinHostedProject,
|
||||
response: Response<proto::JoinHostedProject>,
|
||||
session: Session,
|
||||
) -> Result<()> {
|
||||
let (mut project, replica_id) = session
|
||||
.db()
|
||||
.await
|
||||
.join_hosted_project(
|
||||
ProjectId(request.project_id as i32),
|
||||
session.user_id(),
|
||||
session.connection_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
join_project_internal(response, session, &mut project, &replica_id)
|
||||
}
|
||||
|
||||
/// Updates other participants with changes to the project
|
||||
async fn update_project(
|
||||
request: proto::UpdateProject,
|
||||
@@ -4200,7 +4173,6 @@ fn build_channels_update(channels: ChannelsForUser) -> proto::UpdateChannels {
|
||||
update.channel_invitations.push(channel.to_proto());
|
||||
}
|
||||
|
||||
update.hosted_projects = channels.hosted_projects;
|
||||
update
|
||||
}
|
||||
|
||||
|
||||
@@ -1957,9 +1957,10 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||
});
|
||||
channel_notes_1_b.update(cx_b, |notes, cx| {
|
||||
assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
|
||||
let editor = notes.editor.read(cx);
|
||||
assert_eq!(editor.text(cx), "Hello from A.");
|
||||
assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]);
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "Hello from A.");
|
||||
assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]);
|
||||
})
|
||||
});
|
||||
|
||||
// Client A opens the notes for channel 2.
|
||||
|
||||
@@ -6575,3 +6575,95 @@ async fn test_context_collaboration_with_reconnect(
|
||||
assert!(context.buffer().read(cx).read_only());
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_remote_git_branches(
|
||||
executor: BackgroundExecutor,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
let mut server = TestServer::start(executor.clone()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
.insert_tree("/project", serde_json::json!({ ".git":{} }))
|
||||
.await;
|
||||
let branches = ["main", "dev", "feature-1"];
|
||||
client_a
|
||||
.fs()
|
||||
.insert_branches(Path::new("/project/.git"), &branches);
|
||||
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/project", cx_a).await;
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let root_path = ProjectPath::root_path(worktree_id);
|
||||
// Client A sees that a guest has joined.
|
||||
executor.run_until_parked();
|
||||
|
||||
let branches_b = cx_b
|
||||
.update(|cx| project_b.update(cx, |project, cx| project.branches(root_path.clone(), cx)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let new_branch = branches[2];
|
||||
|
||||
let branches_b = branches_b
|
||||
.into_iter()
|
||||
.map(|branch| branch.name)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(&branches_b, &branches);
|
||||
|
||||
cx_b.update(|cx| {
|
||||
project_b.update(cx, |project, cx| {
|
||||
project.update_or_create_branch(root_path.clone(), new_branch.to_string(), cx)
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
let host_branch = cx_a.update(|cx| {
|
||||
project_a.update(cx, |project, cx| {
|
||||
project.worktree_store().update(cx, |worktree_store, cx| {
|
||||
worktree_store
|
||||
.current_branch(root_path.clone(), cx)
|
||||
.unwrap()
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(host_branch.as_ref(), branches[2]);
|
||||
|
||||
// Also try creating a new branch
|
||||
cx_b.update(|cx| {
|
||||
project_b.update(cx, |project, cx| {
|
||||
project.update_or_create_branch(root_path.clone(), "totally-new-branch".to_string(), cx)
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
let host_branch = cx_a.update(|cx| {
|
||||
project_a.update(cx, |project, cx| {
|
||||
project.worktree_store().update(cx, |worktree_store, cx| {
|
||||
worktree_store.current_branch(root_path, cx).unwrap()
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(host_branch.as_ref(), "totally-new-branch");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::tests::TestServer;
|
||||
use call::ActiveCall;
|
||||
use fs::{FakeFs, Fs as _};
|
||||
use gpui::{Context as _, TestAppContext};
|
||||
use gpui::{BackgroundExecutor, Context as _, TestAppContext};
|
||||
use http_client::BlockedHttpClient;
|
||||
use language::{language_settings::language_settings, LanguageRegistry};
|
||||
use node_runtime::NodeRuntime;
|
||||
@@ -174,3 +174,133 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_ssh_collaboration_git_branches(
|
||||
executor: BackgroundExecutor,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
server_cx: &mut TestAppContext,
|
||||
) {
|
||||
cx_a.set_name("a");
|
||||
cx_b.set_name("b");
|
||||
server_cx.set_name("server");
|
||||
|
||||
let mut server = TestServer::start(executor.clone()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
|
||||
// Set up project on remote FS
|
||||
let (opts, server_ssh) = SshRemoteClient::fake_server(cx_a, server_cx);
|
||||
let remote_fs = FakeFs::new(server_cx.executor());
|
||||
remote_fs
|
||||
.insert_tree("/project", serde_json::json!({ ".git":{} }))
|
||||
.await;
|
||||
|
||||
let branches = ["main", "dev", "feature-1"];
|
||||
remote_fs.insert_branches(Path::new("/project/.git"), &branches);
|
||||
|
||||
// User A connects to the remote project via SSH.
|
||||
server_cx.update(HeadlessProject::init);
|
||||
let remote_http_client = Arc::new(BlockedHttpClient);
|
||||
let node = NodeRuntime::unavailable();
|
||||
let languages = Arc::new(LanguageRegistry::new(server_cx.executor()));
|
||||
let headless_project = server_cx.new_model(|cx| {
|
||||
client::init_settings(cx);
|
||||
HeadlessProject::new(
|
||||
HeadlessAppState {
|
||||
session: server_ssh,
|
||||
fs: remote_fs.clone(),
|
||||
http_client: remote_http_client,
|
||||
node_runtime: node,
|
||||
languages,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let client_ssh = SshRemoteClient::fake_client(opts, cx_a).await;
|
||||
let (project_a, worktree_id) = client_a
|
||||
.build_ssh_project("/project", client_ssh, cx_a)
|
||||
.await;
|
||||
|
||||
// While the SSH worktree is being scanned, user A shares the remote project.
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// User B joins the project.
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Give client A sometime to see that B has joined, and that the headless server
|
||||
// has some git repositories
|
||||
executor.run_until_parked();
|
||||
|
||||
let root_path = ProjectPath::root_path(worktree_id);
|
||||
|
||||
let branches_b = cx_b
|
||||
.update(|cx| project_b.update(cx, |project, cx| project.branches(root_path.clone(), cx)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let new_branch = branches[2];
|
||||
|
||||
let branches_b = branches_b
|
||||
.into_iter()
|
||||
.map(|branch| branch.name)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(&branches_b, &branches);
|
||||
|
||||
cx_b.update(|cx| {
|
||||
project_b.update(cx, |project, cx| {
|
||||
project.update_or_create_branch(root_path.clone(), new_branch.to_string(), cx)
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
let server_branch = server_cx.update(|cx| {
|
||||
headless_project.update(cx, |headless_project, cx| {
|
||||
headless_project
|
||||
.worktree_store
|
||||
.update(cx, |worktree_store, cx| {
|
||||
worktree_store
|
||||
.current_branch(root_path.clone(), cx)
|
||||
.unwrap()
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(server_branch.as_ref(), branches[2]);
|
||||
|
||||
// Also try creating a new branch
|
||||
cx_b.update(|cx| {
|
||||
project_b.update(cx, |project, cx| {
|
||||
project.update_or_create_branch(root_path.clone(), "totally-new-branch".to_string(), cx)
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
let server_branch = server_cx.update(|cx| {
|
||||
headless_project.update(cx, |headless_project, cx| {
|
||||
headless_project
|
||||
.worktree_store
|
||||
.update(cx, |worktree_store, cx| {
|
||||
worktree_store.current_branch(root_path, cx).unwrap()
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(server_branch.as_ref(), "totally-new-branch");
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use self::channel_modal::ChannelModal;
|
||||
use crate::{channel_view::ChannelView, chat_panel::ChatPanel, CollaborationPanelSettings};
|
||||
use call::ActiveCall;
|
||||
use channel::{Channel, ChannelEvent, ChannelStore};
|
||||
use client::{ChannelId, Client, Contact, ProjectId, User, UserStore};
|
||||
use client::{ChannelId, Client, Contact, User, UserStore};
|
||||
use contact_finder::ContactFinder;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
@@ -182,10 +182,6 @@ enum ListEntry {
|
||||
ChannelEditor {
|
||||
depth: usize,
|
||||
},
|
||||
HostedProject {
|
||||
id: ProjectId,
|
||||
name: SharedString,
|
||||
},
|
||||
Contact {
|
||||
contact: Arc<Contact>,
|
||||
calling: bool,
|
||||
@@ -566,7 +562,6 @@ impl CollabPanel {
|
||||
}
|
||||
}
|
||||
|
||||
let hosted_projects = channel_store.projects_for_id(channel.id);
|
||||
let has_children = channel_store
|
||||
.channel_at_index(mat.candidate_id + 1)
|
||||
.map_or(false, |next_channel| {
|
||||
@@ -600,10 +595,6 @@ impl CollabPanel {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (name, id) in hosted_projects {
|
||||
self.entries.push(ListEntry::HostedProject { id, name });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1029,40 +1020,6 @@ impl CollabPanel {
|
||||
.tooltip(move |cx| Tooltip::text("Open Chat", cx))
|
||||
}
|
||||
|
||||
fn render_channel_project(
|
||||
&self,
|
||||
id: ProjectId,
|
||||
name: &SharedString,
|
||||
is_selected: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
ListItem::new(ElementId::NamedInteger(
|
||||
"channel-project".into(),
|
||||
id.0 as usize,
|
||||
))
|
||||
.indent_level(2)
|
||||
.indent_step_size(px(20.))
|
||||
.selected(is_selected)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
if let Some(workspace) = this.workspace.upgrade() {
|
||||
let app_state = workspace.read(cx).app_state().clone();
|
||||
workspace::join_hosted_project(id, app_state, cx).detach_and_prompt_err(
|
||||
"Failed to open project",
|
||||
cx,
|
||||
|_, _| None,
|
||||
)
|
||||
}
|
||||
}))
|
||||
.start_slot(
|
||||
h_flex()
|
||||
.relative()
|
||||
.gap_1()
|
||||
.child(IconButton::new(0, IconName::FileTree)),
|
||||
)
|
||||
.child(Label::new(name.clone()))
|
||||
.tooltip(move |cx| Tooltip::text("Open Project", cx))
|
||||
}
|
||||
|
||||
fn has_subchannels(&self, ix: usize) -> bool {
|
||||
self.entries.get(ix).map_or(false, |entry| {
|
||||
if let ListEntry::Channel { has_children, .. } = entry {
|
||||
@@ -1538,12 +1495,6 @@ impl CollabPanel {
|
||||
ListEntry::ChannelChat { channel_id } => {
|
||||
self.join_channel_chat(*channel_id, cx)
|
||||
}
|
||||
ListEntry::HostedProject {
|
||||
id: _id,
|
||||
name: _name,
|
||||
} => {
|
||||
// todo()
|
||||
}
|
||||
ListEntry::OutgoingRequest(_) => {}
|
||||
ListEntry::ChannelEditor { .. } => {}
|
||||
}
|
||||
@@ -2157,10 +2108,6 @@ impl CollabPanel {
|
||||
ListEntry::ChannelChat { channel_id } => self
|
||||
.render_channel_chat(*channel_id, is_selected, cx)
|
||||
.into_any_element(),
|
||||
|
||||
ListEntry::HostedProject { id, name } => self
|
||||
.render_channel_project(*id, name, is_selected, cx)
|
||||
.into_any_element(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2898,11 +2845,6 @@ impl PartialEq for ListEntry {
|
||||
return channel_1.id == channel_2.id;
|
||||
}
|
||||
}
|
||||
ListEntry::HostedProject { id, .. } => {
|
||||
if let ListEntry::HostedProject { id: other_id, .. } = other {
|
||||
return id == other_id;
|
||||
}
|
||||
}
|
||||
ListEntry::ChannelNotes { channel_id } => {
|
||||
if let ListEntry::ChannelNotes {
|
||||
channel_id: other_id,
|
||||
|
||||
@@ -180,6 +180,39 @@ impl InitializedContextServerProtocol {
|
||||
|
||||
Ok(completion)
|
||||
}
|
||||
|
||||
/// List MCP tools.
|
||||
pub async fn list_tools(&self) -> Result<types::ListToolsResponse> {
|
||||
self.check_capability(ServerCapability::Tools)?;
|
||||
|
||||
let response = self
|
||||
.inner
|
||||
.request::<types::ListToolsResponse>(types::RequestType::ListTools.as_str(), ())
|
||||
.await?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Executes a tool with the given arguments
|
||||
pub async fn run_tool<P: AsRef<str>>(
|
||||
&self,
|
||||
tool: P,
|
||||
arguments: Option<HashMap<String, serde_json::Value>>,
|
||||
) -> Result<types::CallToolResponse> {
|
||||
self.check_capability(ServerCapability::Tools)?;
|
||||
|
||||
let params = types::CallToolParams {
|
||||
name: tool.as_ref().to_string(),
|
||||
arguments,
|
||||
};
|
||||
|
||||
let response: types::CallToolResponse = self
|
||||
.inner
|
||||
.request(types::RequestType::CallTool.as_str(), params)
|
||||
.await?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl InitializedContextServerProtocol {
|
||||
|
||||
@@ -9,7 +9,8 @@ struct GlobalContextServerRegistry(Arc<ContextServerRegistry>);
|
||||
impl Global for GlobalContextServerRegistry {}
|
||||
|
||||
pub struct ContextServerRegistry {
|
||||
registry: RwLock<HashMap<String, Vec<Arc<str>>>>,
|
||||
command_registry: RwLock<HashMap<String, Vec<Arc<str>>>>,
|
||||
tool_registry: RwLock<HashMap<String, Vec<Arc<str>>>>,
|
||||
}
|
||||
|
||||
impl ContextServerRegistry {
|
||||
@@ -20,13 +21,14 @@ impl ContextServerRegistry {
|
||||
pub fn register(cx: &mut AppContext) {
|
||||
cx.set_global(GlobalContextServerRegistry(Arc::new(
|
||||
ContextServerRegistry {
|
||||
registry: RwLock::new(HashMap::default()),
|
||||
command_registry: RwLock::new(HashMap::default()),
|
||||
tool_registry: RwLock::new(HashMap::default()),
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn register_command(&self, server_id: String, command_name: &str) {
|
||||
let mut registry = self.registry.write();
|
||||
let mut registry = self.command_registry.write();
|
||||
registry
|
||||
.entry(server_id)
|
||||
.or_default()
|
||||
@@ -34,14 +36,34 @@ impl ContextServerRegistry {
|
||||
}
|
||||
|
||||
pub fn unregister_command(&self, server_id: &str, command_name: &str) {
|
||||
let mut registry = self.registry.write();
|
||||
let mut registry = self.command_registry.write();
|
||||
if let Some(commands) = registry.get_mut(server_id) {
|
||||
commands.retain(|name| name.as_ref() != command_name);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_commands(&self, server_id: &str) -> Option<Vec<Arc<str>>> {
|
||||
let registry = self.registry.read();
|
||||
let registry = self.command_registry.read();
|
||||
registry.get(server_id).cloned()
|
||||
}
|
||||
|
||||
pub fn register_tool(&self, server_id: String, tool_name: &str) {
|
||||
let mut registry = self.tool_registry.write();
|
||||
registry
|
||||
.entry(server_id)
|
||||
.or_default()
|
||||
.push(tool_name.into());
|
||||
}
|
||||
|
||||
pub fn unregister_tool(&self, server_id: &str, tool_name: &str) {
|
||||
let mut registry = self.tool_registry.write();
|
||||
if let Some(tools) = registry.get_mut(server_id) {
|
||||
tools.retain(|name| name.as_ref() != tool_name);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tools(&self, server_id: &str) -> Option<Vec<Arc<str>>> {
|
||||
let registry = self.tool_registry.read();
|
||||
registry.get(server_id).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ pub enum RequestType {
|
||||
PromptsList,
|
||||
CompletionComplete,
|
||||
Ping,
|
||||
ListTools,
|
||||
ListResourceTemplates,
|
||||
}
|
||||
|
||||
impl RequestType {
|
||||
@@ -32,6 +34,8 @@ impl RequestType {
|
||||
RequestType::PromptsList => "prompts/list",
|
||||
RequestType::CompletionComplete => "completion/complete",
|
||||
RequestType::Ping => "ping",
|
||||
RequestType::ListTools => "tools/list",
|
||||
RequestType::ListResourceTemplates => "resources/templates/list",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,3 +406,17 @@ pub struct Completion {
|
||||
pub values: Vec<String>,
|
||||
pub total: CompletionTotal,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CallToolResponse {
|
||||
pub tool_result: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ListToolsResponse {
|
||||
pub tools: Vec<Tool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub next_cursor: Option<String>,
|
||||
}
|
||||
|
||||
@@ -81,7 +81,6 @@ ui.workspace = true
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
unicode-segmentation.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
|
||||
@@ -153,6 +153,10 @@ pub struct DeleteToPreviousWordStart {
|
||||
pub ignore_newlines: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct FoldAtLevel {
|
||||
pub level: u32,
|
||||
}
|
||||
impl_actions!(
|
||||
editor,
|
||||
[
|
||||
@@ -182,6 +186,7 @@ impl_actions!(
|
||||
ToggleCodeActions,
|
||||
ToggleComments,
|
||||
UnfoldAt,
|
||||
FoldAtLevel
|
||||
]
|
||||
);
|
||||
|
||||
@@ -193,6 +198,7 @@ gpui::actions!(
|
||||
AcceptPartialInlineCompletion,
|
||||
AddSelectionAbove,
|
||||
AddSelectionBelow,
|
||||
ApplyAllDiffHunks,
|
||||
ApplyDiffHunk,
|
||||
Backspace,
|
||||
Cancel,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//! of several smaller structures that form a hierarchy (starting at the bottom):
|
||||
//! - [`InlayMap`] that decides where the [`Inlay`]s should be displayed.
|
||||
//! - [`FoldMap`] that decides where the fold indicators should be; it also tracks parts of a source file that are currently folded.
|
||||
//! - [`CharMap`] that replaces tabs and non-printable characters
|
||||
//! - [`TabMap`] that keeps track of hard tabs in a buffer.
|
||||
//! - [`WrapMap`] that handles soft wrapping.
|
||||
//! - [`BlockMap`] that tracks custom blocks such as diagnostics that should be displayed within buffer.
|
||||
//! - [`DisplayMap`] that adds background highlights to the regions of text.
|
||||
@@ -18,11 +18,10 @@
|
||||
//! [EditorElement]: crate::element::EditorElement
|
||||
|
||||
mod block_map;
|
||||
mod char_map;
|
||||
mod crease_map;
|
||||
mod fold_map;
|
||||
mod inlay_map;
|
||||
mod invisibles;
|
||||
mod tab_map;
|
||||
mod wrap_map;
|
||||
|
||||
use crate::{
|
||||
@@ -33,7 +32,6 @@ pub use block_map::{
|
||||
BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
||||
};
|
||||
use block_map::{BlockRow, BlockSnapshot};
|
||||
use char_map::{CharMap, CharSnapshot};
|
||||
use collections::{HashMap, HashSet};
|
||||
pub use crease_map::*;
|
||||
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
|
||||
@@ -44,7 +42,6 @@ use gpui::{
|
||||
pub(crate) use inlay_map::Inlay;
|
||||
use inlay_map::{InlayMap, InlaySnapshot};
|
||||
pub use inlay_map::{InlayOffset, InlayPoint};
|
||||
pub use invisibles::is_invisible;
|
||||
use language::{
|
||||
language_settings::language_settings, ChunkRenderer, OffsetUtf16, Point,
|
||||
Subscription as BufferSubscription,
|
||||
@@ -64,9 +61,9 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
use sum_tree::{Bias, TreeMap};
|
||||
use tab_map::{TabMap, TabSnapshot};
|
||||
use text::LineIndent;
|
||||
use ui::{px, WindowContext};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use ui::WindowContext;
|
||||
use wrap_map::{WrapMap, WrapSnapshot};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
@@ -97,7 +94,7 @@ pub struct DisplayMap {
|
||||
/// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
|
||||
fold_map: FoldMap,
|
||||
/// Keeps track of hard tabs in a buffer.
|
||||
char_map: CharMap,
|
||||
tab_map: TabMap,
|
||||
/// Handles soft wrapping.
|
||||
wrap_map: Model<WrapMap>,
|
||||
/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
|
||||
@@ -134,7 +131,7 @@ impl DisplayMap {
|
||||
let crease_map = CreaseMap::new(&buffer_snapshot);
|
||||
let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot);
|
||||
let (fold_map, snapshot) = FoldMap::new(snapshot);
|
||||
let (char_map, snapshot) = CharMap::new(snapshot, tab_size);
|
||||
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
|
||||
let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
|
||||
let block_map = BlockMap::new(
|
||||
snapshot,
|
||||
@@ -151,7 +148,7 @@ impl DisplayMap {
|
||||
buffer_subscription,
|
||||
fold_map,
|
||||
inlay_map,
|
||||
char_map,
|
||||
tab_map,
|
||||
wrap_map,
|
||||
block_map,
|
||||
crease_map,
|
||||
@@ -169,17 +166,17 @@ impl DisplayMap {
|
||||
let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
|
||||
let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits);
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (char_snapshot, edits) = self.char_map.sync(fold_snapshot.clone(), edits, tab_size);
|
||||
let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size);
|
||||
let (wrap_snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(char_snapshot.clone(), edits, cx));
|
||||
.update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
|
||||
let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits).snapshot;
|
||||
|
||||
DisplaySnapshot {
|
||||
buffer_snapshot: self.buffer.read(cx).snapshot(cx),
|
||||
fold_snapshot,
|
||||
inlay_snapshot,
|
||||
char_snapshot,
|
||||
tab_snapshot,
|
||||
wrap_snapshot,
|
||||
block_snapshot,
|
||||
crease_snapshot: self.crease_map.snapshot(),
|
||||
@@ -215,13 +212,13 @@ impl DisplayMap {
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
|
||||
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = fold_map.fold(ranges);
|
||||
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
@@ -239,13 +236,13 @@ impl DisplayMap {
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
|
||||
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
|
||||
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
@@ -280,7 +277,7 @@ impl DisplayMap {
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
@@ -298,7 +295,7 @@ impl DisplayMap {
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
@@ -316,7 +313,7 @@ impl DisplayMap {
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
@@ -334,7 +331,7 @@ impl DisplayMap {
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
@@ -410,7 +407,7 @@ impl DisplayMap {
|
||||
let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
@@ -418,7 +415,7 @@ impl DisplayMap {
|
||||
|
||||
let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
@@ -470,7 +467,7 @@ pub struct DisplaySnapshot {
|
||||
pub fold_snapshot: FoldSnapshot,
|
||||
pub crease_snapshot: CreaseSnapshot,
|
||||
inlay_snapshot: InlaySnapshot,
|
||||
char_snapshot: CharSnapshot,
|
||||
tab_snapshot: TabSnapshot,
|
||||
wrap_snapshot: WrapSnapshot,
|
||||
block_snapshot: BlockSnapshot,
|
||||
text_highlights: TextHighlights,
|
||||
@@ -570,8 +567,8 @@ impl DisplaySnapshot {
|
||||
fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
|
||||
let inlay_point = self.inlay_snapshot.to_inlay_point(point);
|
||||
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
|
||||
let char_point = self.char_snapshot.to_char_point(fold_point);
|
||||
let wrap_point = self.wrap_snapshot.char_point_to_wrap_point(char_point);
|
||||
let tab_point = self.tab_snapshot.to_tab_point(fold_point);
|
||||
let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
|
||||
let block_point = self.block_snapshot.to_block_point(wrap_point);
|
||||
DisplayPoint(block_point)
|
||||
}
|
||||
@@ -599,21 +596,21 @@ impl DisplaySnapshot {
|
||||
fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
|
||||
let block_point = point.0;
|
||||
let wrap_point = self.block_snapshot.to_wrap_point(block_point);
|
||||
let char_point = self.wrap_snapshot.to_char_point(wrap_point);
|
||||
let fold_point = self.char_snapshot.to_fold_point(char_point, bias).0;
|
||||
let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
|
||||
let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0;
|
||||
fold_point.to_inlay_point(&self.fold_snapshot)
|
||||
}
|
||||
|
||||
pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
|
||||
let block_point = point.0;
|
||||
let wrap_point = self.block_snapshot.to_wrap_point(block_point);
|
||||
let char_point = self.wrap_snapshot.to_char_point(wrap_point);
|
||||
self.char_snapshot.to_fold_point(char_point, bias).0
|
||||
let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
|
||||
self.tab_snapshot.to_fold_point(tab_point, bias).0
|
||||
}
|
||||
|
||||
pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
|
||||
let char_point = self.char_snapshot.to_char_point(fold_point);
|
||||
let wrap_point = self.wrap_snapshot.char_point_to_wrap_point(char_point);
|
||||
let tab_point = self.tab_snapshot.to_tab_point(fold_point);
|
||||
let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
|
||||
let block_point = self.block_snapshot.to_block_point(wrap_point);
|
||||
DisplayPoint(block_point)
|
||||
}
|
||||
@@ -691,23 +688,6 @@ impl DisplaySnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
if chunk.is_invisible {
|
||||
let invisible_highlight = HighlightStyle {
|
||||
background_color: Some(editor_style.status.hint_background),
|
||||
underline: Some(UnderlineStyle {
|
||||
color: Some(editor_style.status.hint),
|
||||
thickness: px(1.),
|
||||
wavy: false,
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(highlight_style) = highlight_style.as_mut() {
|
||||
highlight_style.highlight(invisible_highlight);
|
||||
} else {
|
||||
highlight_style = Some(invisible_highlight);
|
||||
}
|
||||
}
|
||||
|
||||
let mut diagnostic_highlight = HighlightStyle::default();
|
||||
|
||||
if chunk.is_unnecessary {
|
||||
@@ -804,11 +784,12 @@ impl DisplaySnapshot {
|
||||
layout_line.closest_index_for_x(x) as u32
|
||||
}
|
||||
|
||||
pub fn grapheme_at(&self, mut point: DisplayPoint) -> Option<String> {
|
||||
pub fn display_chars_at(
|
||||
&self,
|
||||
mut point: DisplayPoint,
|
||||
) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
|
||||
point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
|
||||
|
||||
let chars = self
|
||||
.text_chunks(point.row())
|
||||
self.text_chunks(point.row())
|
||||
.flat_map(str::chars)
|
||||
.skip_while({
|
||||
let mut column = 0;
|
||||
@@ -818,21 +799,16 @@ impl DisplaySnapshot {
|
||||
!at_point
|
||||
}
|
||||
})
|
||||
.take_while({
|
||||
let mut prev = false;
|
||||
move |char| {
|
||||
let now = char.is_ascii();
|
||||
let end = char.is_ascii() && (char.is_ascii_whitespace() || prev);
|
||||
prev = now;
|
||||
!end
|
||||
.map(move |ch| {
|
||||
let result = (ch, point);
|
||||
if ch == '\n' {
|
||||
*point.row_mut() += 1;
|
||||
*point.column_mut() = 0;
|
||||
} else {
|
||||
*point.column_mut() += ch.len_utf8() as u32;
|
||||
}
|
||||
});
|
||||
|
||||
chars
|
||||
.collect::<String>()
|
||||
.graphemes(true)
|
||||
.next()
|
||||
.map(|s| s.to_owned())
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub fn buffer_chars_at(&self, mut offset: usize) -> impl Iterator<Item = (char, usize)> + '_ {
|
||||
@@ -1144,8 +1120,8 @@ impl DisplayPoint {
|
||||
|
||||
pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
|
||||
let wrap_point = map.block_snapshot.to_wrap_point(self.0);
|
||||
let char_point = map.wrap_snapshot.to_char_point(wrap_point);
|
||||
let fold_point = map.char_snapshot.to_fold_point(char_point, bias).0;
|
||||
let tab_point = map.wrap_snapshot.to_tab_point(wrap_point);
|
||||
let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0;
|
||||
let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot);
|
||||
map.inlay_snapshot
|
||||
.to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point))
|
||||
@@ -1192,6 +1168,7 @@ pub mod tests {
|
||||
use smol::stream::StreamExt;
|
||||
use std::{env, sync::Arc};
|
||||
use theme::{LoadThemes, SyntaxTheme};
|
||||
use unindent::Unindent as _;
|
||||
use util::test::{marked_text_ranges, sample_text};
|
||||
use Bias::*;
|
||||
|
||||
@@ -1253,7 +1230,7 @@ pub mod tests {
|
||||
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
|
||||
log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
|
||||
log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
|
||||
log::info!("char text: {:?}", snapshot.char_snapshot.text());
|
||||
log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
|
||||
log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
|
||||
log::info!("block text: {:?}", snapshot.block_snapshot.text());
|
||||
log::info!("display text: {:?}", snapshot.text());
|
||||
@@ -1368,7 +1345,7 @@ pub mod tests {
|
||||
fold_count = snapshot.fold_count();
|
||||
log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
|
||||
log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
|
||||
log::info!("char text: {:?}", snapshot.char_snapshot.text());
|
||||
log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
|
||||
log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
|
||||
log::info!("block text: {:?}", snapshot.block_snapshot.text());
|
||||
log::info!("display text: {:?}", snapshot.text());
|
||||
@@ -1648,8 +1625,6 @@ pub mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_chunks(cx: &mut gpui::TestAppContext) {
|
||||
use unindent::Unindent as _;
|
||||
|
||||
let text = r#"
|
||||
fn outer() {}
|
||||
|
||||
@@ -1746,12 +1721,110 @@ pub mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_chunks_with_syntax_highlighting_across_blocks(cx: &mut gpui::TestAppContext) {
|
||||
cx.background_executor
|
||||
.set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||
|
||||
let text = r#"
|
||||
const A: &str = "
|
||||
one
|
||||
two
|
||||
three
|
||||
";
|
||||
const B: &str = "four";
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
let theme = SyntaxTheme::new_test(vec![
|
||||
("string", Hsla::red()),
|
||||
("punctuation", Hsla::blue()),
|
||||
("keyword", Hsla::green()),
|
||||
]);
|
||||
let language = Arc::new(
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
)
|
||||
.with_highlights_query(
|
||||
r#"
|
||||
(string_literal) @string
|
||||
"const" @keyword
|
||||
[":" ";"] @punctuation
|
||||
"#,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
language.set_theme(&theme);
|
||||
|
||||
cx.update(|cx| init_test(cx, |_| {}));
|
||||
|
||||
let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
|
||||
cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
|
||||
let map = cx.new_model(|cx| {
|
||||
DisplayMap::new(
|
||||
buffer,
|
||||
font("Courier"),
|
||||
px(16.0),
|
||||
None,
|
||||
true,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
FoldPlaceholder::test(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
// Insert a block in the middle of a multi-line string literal
|
||||
map.update(cx, |map, cx| {
|
||||
map.insert_blocks(
|
||||
[BlockProperties {
|
||||
placement: BlockPlacement::Below(
|
||||
buffer_snapshot.anchor_before(Point::new(1, 0)),
|
||||
),
|
||||
height: 1,
|
||||
style: BlockStyle::Sticky,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
}],
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
pretty_assertions::assert_eq!(
|
||||
cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(7), &map, &theme, cx)),
|
||||
[
|
||||
("const".into(), Some(Hsla::green())),
|
||||
(" A".into(), None),
|
||||
(":".into(), Some(Hsla::blue())),
|
||||
(" &str = ".into(), None),
|
||||
("\"\n one\n".into(), Some(Hsla::red())),
|
||||
("\n".into(), None),
|
||||
(" two\n three\n\"".into(), Some(Hsla::red())),
|
||||
(";".into(), Some(Hsla::blue())),
|
||||
("\n".into(), None),
|
||||
("const".into(), Some(Hsla::green())),
|
||||
(" B".into(), None),
|
||||
(":".into(), Some(Hsla::blue())),
|
||||
(" &str = ".into(), None),
|
||||
("\"four\"".into(), Some(Hsla::red())),
|
||||
(";".into(), Some(Hsla::blue())),
|
||||
("\n".into(), None),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// todo(linux) fails due to pixel differences in text rendering
|
||||
#[cfg(target_os = "macos")]
|
||||
#[gpui::test]
|
||||
async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
|
||||
use unindent::Unindent as _;
|
||||
|
||||
cx.background_executor
|
||||
.set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||
|
||||
|
||||
@@ -1666,7 +1666,7 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::display_map::{
|
||||
char_map::CharMap, fold_map::FoldMap, inlay_map::InlayMap, wrap_map::WrapMap,
|
||||
fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
|
||||
};
|
||||
use gpui::{div, font, px, AppContext, Context as _, Element};
|
||||
use language::{Buffer, Capability};
|
||||
@@ -1701,9 +1701,9 @@ mod tests {
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (mut char_map, char_snapshot) = CharMap::new(fold_snapshot, 1.try_into().unwrap());
|
||||
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
|
||||
let (wrap_map, wraps_snapshot) =
|
||||
cx.update(|cx| WrapMap::new(char_snapshot, font("Helvetica"), px(14.0), None, cx));
|
||||
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
|
||||
let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
|
||||
|
||||
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
|
||||
@@ -1851,10 +1851,10 @@ mod tests {
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (char_snapshot, tab_edits) =
|
||||
char_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
wrap_map.sync(char_snapshot, tab_edits, cx)
|
||||
wrap_map.sync(tab_snapshot, tab_edits, cx)
|
||||
});
|
||||
let snapshot = block_map.read(wraps_snapshot, wrap_edits);
|
||||
assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
|
||||
@@ -1914,9 +1914,8 @@ mod tests {
|
||||
let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wraps_snapshot) =
|
||||
WrapMap::new(char_snapshot, font, font_size, Some(wrap_width), cx);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
|
||||
|
||||
let block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
|
||||
let snapshot = block_map.read(wraps_snapshot, Default::default());
|
||||
@@ -1953,9 +1952,9 @@ mod tests {
|
||||
let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_char_map, char_snapshot) = CharMap::new(fold_snapshot, 1.try_into().unwrap());
|
||||
let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
|
||||
let (_wrap_map, wraps_snapshot) =
|
||||
cx.update(|cx| WrapMap::new(char_snapshot, font("Helvetica"), px(14.0), None, cx));
|
||||
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
|
||||
let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
|
||||
|
||||
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
|
||||
@@ -2055,15 +2054,9 @@ mod tests {
|
||||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wraps_snapshot) = cx.update(|cx| {
|
||||
WrapMap::new(
|
||||
char_snapshot,
|
||||
font("Helvetica"),
|
||||
px(14.0),
|
||||
Some(px(60.)),
|
||||
cx,
|
||||
)
|
||||
WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
|
||||
});
|
||||
let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 0);
|
||||
|
||||
@@ -2106,7 +2099,7 @@ mod tests {
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let tab_size = 1.try_into().unwrap();
|
||||
let (mut tab_map, tab_snapshot) = CharMap::new(fold_snapshot, tab_size);
|
||||
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
|
||||
let (wrap_map, wraps_snapshot) =
|
||||
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
|
||||
let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
|
||||
@@ -2257,9 +2250,9 @@ mod tests {
|
||||
let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (mut char_map, char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (wrap_map, wraps_snapshot) = cx
|
||||
.update(|cx| WrapMap::new(char_snapshot, font("Helvetica"), font_size, wrap_width, cx));
|
||||
.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), font_size, wrap_width, cx));
|
||||
let mut block_map = BlockMap::new(
|
||||
wraps_snapshot,
|
||||
true,
|
||||
@@ -2321,10 +2314,10 @@ mod tests {
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), vec![]);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (char_snapshot, tab_edits) =
|
||||
char_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
wrap_map.sync(char_snapshot, tab_edits, cx)
|
||||
wrap_map.sync(tab_snapshot, tab_edits, cx)
|
||||
});
|
||||
let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
|
||||
block_map.insert(block_properties.iter().map(|props| BlockProperties {
|
||||
@@ -2346,10 +2339,10 @@ mod tests {
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), vec![]);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (char_snapshot, tab_edits) =
|
||||
char_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
wrap_map.sync(char_snapshot, tab_edits, cx)
|
||||
wrap_map.sync(tab_snapshot, tab_edits, cx)
|
||||
});
|
||||
let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
|
||||
block_map.remove(block_ids_to_remove);
|
||||
@@ -2369,9 +2362,9 @@ mod tests {
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (char_snapshot, tab_edits) = char_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
wrap_map.sync(char_snapshot, tab_edits, cx)
|
||||
wrap_map.sync(tab_snapshot, tab_edits, cx)
|
||||
});
|
||||
let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
|
||||
assert_eq!(
|
||||
@@ -2486,7 +2479,7 @@ mod tests {
|
||||
.row as usize];
|
||||
|
||||
let soft_wrapped = wraps_snapshot
|
||||
.to_char_point(WrapPoint::new(wrap_row, 0))
|
||||
.to_tab_point(WrapPoint::new(wrap_row, 0))
|
||||
.column()
|
||||
> 0;
|
||||
expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use collections::HashMap;
|
||||
|
||||
// Invisibility in a Unicode context is not well defined, so we have to guess.
|
||||
//
|
||||
// We highlight all ASCII control codes, and unicode whitespace because they are likely
|
||||
// confused with a normal space (U+0020).
|
||||
//
|
||||
// We also highlight the handful of blank non-space characters:
|
||||
// U+2800 BRAILLE PATTERN BLANK - Category: So
|
||||
// U+115F HANGUL CHOSEONG FILLER - Category: Lo
|
||||
// U+1160 HANGUL CHOSEONG FILLER - Category: Lo
|
||||
// U+3164 HANGUL FILLER - Category: Lo
|
||||
// U+FFA0 HALFWIDTH HANGUL FILLER - Category: Lo
|
||||
// U+FFFC OBJECT REPLACEMENT CHARACTER - Category: So
|
||||
//
|
||||
// For the rest of Unicode, invisibility happens for two reasons:
|
||||
// * A Format character (like a byte order mark or right-to-left override)
|
||||
// * An invisible Nonspacing Mark character (like U+034F, or variation selectors)
|
||||
//
|
||||
// We don't consider unassigned codepoints invisible as the font renderer already shows
|
||||
// a replacement character in that case (and there are a *lot* of them)
|
||||
//
|
||||
// Control characters are mostly fine to highlight; except:
|
||||
// * U+E0020..=U+E007F are used in emoji flags. We don't highlight them right now, but we could if we tightened our heuristics.
|
||||
// * U+200D is used to join characters. We highlight this but don't replace it. As our font system ignores mid-glyph highlights this mostly works to highlight unexpected uses.
|
||||
//
|
||||
// Nonspacing marks are handled like U+200D. This means that mid-glyph we ignore them, but
|
||||
// probably causes issues with end-of-glyph usage.
|
||||
//
|
||||
// ref: https://invisible-characters.com
|
||||
// ref: https://www.compart.com/en/unicode/category/Cf
|
||||
// ref: https://gist.github.com/ConradIrwin/f759e1fc29267143c4c7895aa495dca5?h=1
|
||||
// ref: https://unicode.org/Public/emoji/13.0/emoji-test.txt
|
||||
// https://github.com/bits/UTF-8-Unicode-Test-Documents/blob/master/UTF-8_sequence_separated/utf8_sequence_0-0x10ffff_assigned_including-unprintable-asis.txt
|
||||
pub fn is_invisible(c: char) -> bool {
|
||||
if c <= '\u{1f}' {
|
||||
c != '\t' && c != '\n' && c != '\r'
|
||||
} else if c >= '\u{7f}' {
|
||||
c <= '\u{9f}' || c.is_whitespace() || contains(c, &FORMAT) || contains(c, &OTHER)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn replacement(c: char) -> Option<&'static str> {
|
||||
if !is_invisible(c) {
|
||||
return None;
|
||||
}
|
||||
if c <= '\x7f' {
|
||||
REPLACEMENTS.get(&c).copied()
|
||||
} else if contains(c, &PRESERVE) {
|
||||
None
|
||||
} else {
|
||||
Some(" ")
|
||||
}
|
||||
}
|
||||
|
||||
const REPLACEMENTS: LazyLock<HashMap<char, &'static str>> = LazyLock::new(|| {
|
||||
[
|
||||
('\x00', "␀"),
|
||||
('\x01', "␁"),
|
||||
('\x02', "␂"),
|
||||
('\x03', "␃"),
|
||||
('\x04', "␄"),
|
||||
('\x05', "␅"),
|
||||
('\x06', "␆"),
|
||||
('\x07', "␇"),
|
||||
('\x08', "␈"),
|
||||
('\x0B', "␋"),
|
||||
('\x0C', "␌"),
|
||||
('\x0D', "␍"),
|
||||
('\x0E', "␎"),
|
||||
('\x0F', "␏"),
|
||||
('\x10', "␐"),
|
||||
('\x11', "␑"),
|
||||
('\x12', "␒"),
|
||||
('\x13', "␓"),
|
||||
('\x14', "␔"),
|
||||
('\x15', "␕"),
|
||||
('\x16', "␖"),
|
||||
('\x17', "␗"),
|
||||
('\x18', "␘"),
|
||||
('\x19', "␙"),
|
||||
('\x1A', "␚"),
|
||||
('\x1B', "␛"),
|
||||
('\x1C', "␜"),
|
||||
('\x1D', "␝"),
|
||||
('\x1E', "␞"),
|
||||
('\x1F', "␟"),
|
||||
('\u{007F}', "␡"),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
});
|
||||
|
||||
// generated using ucd-generate: ucd-generate general-category --include Format --chars ucd-16.0.0
|
||||
pub const FORMAT: &'static [(char, char)] = &[
|
||||
('\u{ad}', '\u{ad}'),
|
||||
('\u{600}', '\u{605}'),
|
||||
('\u{61c}', '\u{61c}'),
|
||||
('\u{6dd}', '\u{6dd}'),
|
||||
('\u{70f}', '\u{70f}'),
|
||||
('\u{890}', '\u{891}'),
|
||||
('\u{8e2}', '\u{8e2}'),
|
||||
('\u{180e}', '\u{180e}'),
|
||||
('\u{200b}', '\u{200f}'),
|
||||
('\u{202a}', '\u{202e}'),
|
||||
('\u{2060}', '\u{2064}'),
|
||||
('\u{2066}', '\u{206f}'),
|
||||
('\u{feff}', '\u{feff}'),
|
||||
('\u{fff9}', '\u{fffb}'),
|
||||
('\u{110bd}', '\u{110bd}'),
|
||||
('\u{110cd}', '\u{110cd}'),
|
||||
('\u{13430}', '\u{1343f}'),
|
||||
('\u{1bca0}', '\u{1bca3}'),
|
||||
('\u{1d173}', '\u{1d17a}'),
|
||||
('\u{e0001}', '\u{e0001}'),
|
||||
('\u{e0020}', '\u{e007f}'),
|
||||
];
|
||||
|
||||
// hand-made base on https://invisible-characters.com (Excluding Cf)
|
||||
pub const OTHER: &'static [(char, char)] = &[
|
||||
('\u{034f}', '\u{034f}'),
|
||||
('\u{115F}', '\u{1160}'),
|
||||
('\u{17b4}', '\u{17b5}'),
|
||||
('\u{180b}', '\u{180d}'),
|
||||
('\u{2800}', '\u{2800}'),
|
||||
('\u{3164}', '\u{3164}'),
|
||||
('\u{fe00}', '\u{fe0d}'),
|
||||
('\u{ffa0}', '\u{ffa0}'),
|
||||
('\u{fffc}', '\u{fffc}'),
|
||||
('\u{e0100}', '\u{e01ef}'),
|
||||
];
|
||||
|
||||
// a subset of FORMAT/OTHER that may appear within glyphs
|
||||
const PRESERVE: &'static [(char, char)] = &[
|
||||
('\u{034f}', '\u{034f}'),
|
||||
('\u{200d}', '\u{200d}'),
|
||||
('\u{17b4}', '\u{17b5}'),
|
||||
('\u{180b}', '\u{180d}'),
|
||||
('\u{e0061}', '\u{e007a}'),
|
||||
('\u{e007f}', '\u{e007f}'),
|
||||
];
|
||||
|
||||
fn contains(c: char, list: &[(char, char)]) -> bool {
|
||||
for (start, end) in list {
|
||||
if c < *start {
|
||||
return false;
|
||||
}
|
||||
if c <= *end {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::{
|
||||
fold_map::{self, FoldChunks, FoldEdit, FoldPoint, FoldSnapshot},
|
||||
invisibles::{is_invisible, replacement},
|
||||
Highlights,
|
||||
};
|
||||
use language::{Chunk, Point};
|
||||
@@ -10,14 +9,14 @@ use sum_tree::Bias;
|
||||
|
||||
const MAX_EXPANSION_COLUMN: u32 = 256;
|
||||
|
||||
/// Keeps track of hard tabs and non-printable characters in a text buffer.
|
||||
/// Keeps track of hard tabs in a text buffer.
|
||||
///
|
||||
/// See the [`display_map` module documentation](crate::display_map) for more information.
|
||||
pub struct CharMap(CharSnapshot);
|
||||
pub struct TabMap(TabSnapshot);
|
||||
|
||||
impl CharMap {
|
||||
pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, CharSnapshot) {
|
||||
let snapshot = CharSnapshot {
|
||||
impl TabMap {
|
||||
pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) {
|
||||
let snapshot = TabSnapshot {
|
||||
fold_snapshot,
|
||||
tab_size,
|
||||
max_expansion_column: MAX_EXPANSION_COLUMN,
|
||||
@@ -27,7 +26,7 @@ impl CharMap {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn set_max_expansion_column(&mut self, column: u32) -> CharSnapshot {
|
||||
pub fn set_max_expansion_column(&mut self, column: u32) -> TabSnapshot {
|
||||
self.0.max_expansion_column = column;
|
||||
self.0.clone()
|
||||
}
|
||||
@@ -37,9 +36,9 @@ impl CharMap {
|
||||
fold_snapshot: FoldSnapshot,
|
||||
mut fold_edits: Vec<FoldEdit>,
|
||||
tab_size: NonZeroU32,
|
||||
) -> (CharSnapshot, Vec<TabEdit>) {
|
||||
) -> (TabSnapshot, Vec<TabEdit>) {
|
||||
let old_snapshot = &mut self.0;
|
||||
let mut new_snapshot = CharSnapshot {
|
||||
let mut new_snapshot = TabSnapshot {
|
||||
fold_snapshot,
|
||||
tab_size,
|
||||
max_expansion_column: old_snapshot.max_expansion_column,
|
||||
@@ -138,15 +137,15 @@ impl CharMap {
|
||||
let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot);
|
||||
let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot);
|
||||
tab_edits.push(TabEdit {
|
||||
old: old_snapshot.to_char_point(old_start)..old_snapshot.to_char_point(old_end),
|
||||
new: new_snapshot.to_char_point(new_start)..new_snapshot.to_char_point(new_end),
|
||||
old: old_snapshot.to_tab_point(old_start)..old_snapshot.to_tab_point(old_end),
|
||||
new: new_snapshot.to_tab_point(new_start)..new_snapshot.to_tab_point(new_end),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
new_snapshot.version += 1;
|
||||
tab_edits.push(TabEdit {
|
||||
old: CharPoint::zero()..old_snapshot.max_point(),
|
||||
new: CharPoint::zero()..new_snapshot.max_point(),
|
||||
old: TabPoint::zero()..old_snapshot.max_point(),
|
||||
new: TabPoint::zero()..new_snapshot.max_point(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,14 +155,14 @@ impl CharMap {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CharSnapshot {
|
||||
pub struct TabSnapshot {
|
||||
pub fold_snapshot: FoldSnapshot,
|
||||
pub tab_size: NonZeroU32,
|
||||
pub max_expansion_column: u32,
|
||||
pub version: usize,
|
||||
}
|
||||
|
||||
impl CharSnapshot {
|
||||
impl TabSnapshot {
|
||||
pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
|
||||
&self.fold_snapshot.inlay_snapshot.buffer
|
||||
}
|
||||
@@ -171,7 +170,7 @@ impl CharSnapshot {
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
let max_point = self.max_point();
|
||||
if row < max_point.row() {
|
||||
self.to_char_point(FoldPoint::new(row, self.fold_snapshot.line_len(row)))
|
||||
self.to_tab_point(FoldPoint::new(row, self.fold_snapshot.line_len(row)))
|
||||
.0
|
||||
.column
|
||||
} else {
|
||||
@@ -180,10 +179,10 @@ impl CharSnapshot {
|
||||
}
|
||||
|
||||
pub fn text_summary(&self) -> TextSummary {
|
||||
self.text_summary_for_range(CharPoint::zero()..self.max_point())
|
||||
self.text_summary_for_range(TabPoint::zero()..self.max_point())
|
||||
}
|
||||
|
||||
pub fn text_summary_for_range(&self, range: Range<CharPoint>) -> TextSummary {
|
||||
pub fn text_summary_for_range(&self, range: Range<TabPoint>) -> TextSummary {
|
||||
let input_start = self.to_fold_point(range.start, Bias::Left).0;
|
||||
let input_end = self.to_fold_point(range.end, Bias::Right).0;
|
||||
let input_summary = self
|
||||
@@ -212,7 +211,7 @@ impl CharSnapshot {
|
||||
} else {
|
||||
for _ in self
|
||||
.chunks(
|
||||
CharPoint::new(range.end.row(), 0)..range.end,
|
||||
TabPoint::new(range.end.row(), 0)..range.end,
|
||||
false,
|
||||
Highlights::default(),
|
||||
)
|
||||
@@ -233,7 +232,7 @@ impl CharSnapshot {
|
||||
|
||||
pub fn chunks<'a>(
|
||||
&'a self,
|
||||
range: Range<CharPoint>,
|
||||
range: Range<TabPoint>,
|
||||
language_aware: bool,
|
||||
highlights: Highlights<'a>,
|
||||
) -> TabChunks<'a> {
|
||||
@@ -280,7 +279,7 @@ impl CharSnapshot {
|
||||
#[cfg(test)]
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks(
|
||||
CharPoint::zero()..self.max_point(),
|
||||
TabPoint::zero()..self.max_point(),
|
||||
false,
|
||||
Highlights::default(),
|
||||
)
|
||||
@@ -288,24 +287,24 @@ impl CharSnapshot {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn max_point(&self) -> CharPoint {
|
||||
self.to_char_point(self.fold_snapshot.max_point())
|
||||
pub fn max_point(&self) -> TabPoint {
|
||||
self.to_tab_point(self.fold_snapshot.max_point())
|
||||
}
|
||||
|
||||
pub fn clip_point(&self, point: CharPoint, bias: Bias) -> CharPoint {
|
||||
self.to_char_point(
|
||||
pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint {
|
||||
self.to_tab_point(
|
||||
self.fold_snapshot
|
||||
.clip_point(self.to_fold_point(point, bias).0, bias),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_char_point(&self, input: FoldPoint) -> CharPoint {
|
||||
pub fn to_tab_point(&self, input: FoldPoint) -> TabPoint {
|
||||
let chars = self.fold_snapshot.chars_at(FoldPoint::new(input.row(), 0));
|
||||
let expanded = self.expand_tabs(chars, input.column());
|
||||
CharPoint::new(input.row(), expanded)
|
||||
TabPoint::new(input.row(), expanded)
|
||||
}
|
||||
|
||||
pub fn to_fold_point(&self, output: CharPoint, bias: Bias) -> (FoldPoint, u32, u32) {
|
||||
pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, u32, u32) {
|
||||
let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0));
|
||||
let expanded = output.column();
|
||||
let (collapsed, expanded_char_column, to_next_stop) =
|
||||
@@ -317,13 +316,13 @@ impl CharSnapshot {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn make_char_point(&self, point: Point, bias: Bias) -> CharPoint {
|
||||
pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
|
||||
let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point);
|
||||
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
|
||||
self.to_char_point(fold_point)
|
||||
self.to_tab_point(fold_point)
|
||||
}
|
||||
|
||||
pub fn to_point(&self, point: CharPoint, bias: Bias) -> Point {
|
||||
pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
|
||||
let fold_point = self.to_fold_point(point, bias).0;
|
||||
let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
|
||||
self.fold_snapshot
|
||||
@@ -346,9 +345,6 @@ impl CharSnapshot {
|
||||
let tab_len = tab_size - expanded_chars % tab_size;
|
||||
expanded_bytes += tab_len;
|
||||
expanded_chars += tab_len;
|
||||
} else if let Some(replacement) = replacement(c) {
|
||||
expanded_chars += replacement.chars().count() as u32;
|
||||
expanded_bytes += replacement.len() as u32;
|
||||
} else {
|
||||
expanded_bytes += c.len_utf8() as u32;
|
||||
expanded_chars += 1;
|
||||
@@ -388,9 +384,6 @@ impl CharSnapshot {
|
||||
Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
|
||||
};
|
||||
}
|
||||
} else if let Some(replacement) = replacement(c) {
|
||||
expanded_chars += replacement.chars().count() as u32;
|
||||
expanded_bytes += replacement.len() as u32;
|
||||
} else {
|
||||
expanded_chars += 1;
|
||||
expanded_bytes += c.len_utf8() as u32;
|
||||
@@ -412,9 +405,9 @@ impl CharSnapshot {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
pub struct CharPoint(pub Point);
|
||||
pub struct TabPoint(pub Point);
|
||||
|
||||
impl CharPoint {
|
||||
impl TabPoint {
|
||||
pub fn new(row: u32, column: u32) -> Self {
|
||||
Self(Point::new(row, column))
|
||||
}
|
||||
@@ -432,13 +425,13 @@ impl CharPoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point> for CharPoint {
|
||||
impl From<Point> for TabPoint {
|
||||
fn from(point: Point) -> Self {
|
||||
Self(point)
|
||||
}
|
||||
}
|
||||
|
||||
pub type TabEdit = text::Edit<CharPoint>;
|
||||
pub type TabEdit = text::Edit<TabPoint>;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct TextSummary {
|
||||
@@ -493,7 +486,7 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
|
||||
const SPACES: &str = " ";
|
||||
|
||||
pub struct TabChunks<'a> {
|
||||
snapshot: &'a CharSnapshot,
|
||||
snapshot: &'a TabSnapshot,
|
||||
fold_chunks: FoldChunks<'a>,
|
||||
chunk: Chunk<'a>,
|
||||
column: u32,
|
||||
@@ -506,7 +499,7 @@ pub struct TabChunks<'a> {
|
||||
}
|
||||
|
||||
impl<'a> TabChunks<'a> {
|
||||
pub(crate) fn seek(&mut self, range: Range<CharPoint>) {
|
||||
pub(crate) fn seek(&mut self, range: Range<TabPoint>) {
|
||||
let (input_start, expanded_char_column, to_next_stop) =
|
||||
self.snapshot.to_fold_point(range.start, Bias::Left);
|
||||
let input_column = input_start.column();
|
||||
@@ -591,37 +584,6 @@ impl<'a> Iterator for TabChunks<'a> {
|
||||
self.input_column = 0;
|
||||
self.output_position += Point::new(1, 0);
|
||||
}
|
||||
_ if is_invisible(c) => {
|
||||
if ix > 0 {
|
||||
let (prefix, suffix) = self.chunk.text.split_at(ix);
|
||||
self.chunk.text = suffix;
|
||||
return Some(Chunk {
|
||||
text: prefix,
|
||||
is_invisible: false,
|
||||
..self.chunk.clone()
|
||||
});
|
||||
}
|
||||
let c_len = c.len_utf8();
|
||||
let replacement = replacement(c).unwrap_or(&self.chunk.text[..c_len]);
|
||||
if self.chunk.text.len() >= c_len {
|
||||
self.chunk.text = &self.chunk.text[c_len..];
|
||||
} else {
|
||||
self.chunk.text = "";
|
||||
}
|
||||
let len = replacement.chars().count() as u32;
|
||||
let next_output_position = cmp::min(
|
||||
self.output_position + Point::new(0, len),
|
||||
self.max_output_position,
|
||||
);
|
||||
self.column += len;
|
||||
self.input_column += 1;
|
||||
self.output_position = next_output_position;
|
||||
return Some(Chunk {
|
||||
text: replacement,
|
||||
is_invisible: true,
|
||||
..self.chunk.clone()
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
self.column += 1;
|
||||
if !self.inside_leading_tab {
|
||||
@@ -651,11 +613,11 @@ mod tests {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
assert_eq!(char_snapshot.expand_tabs("\t".chars(), 0), 0);
|
||||
assert_eq!(char_snapshot.expand_tabs("\t".chars(), 1), 4);
|
||||
assert_eq!(char_snapshot.expand_tabs("\ta".chars(), 2), 5);
|
||||
assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0);
|
||||
assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 1), 4);
|
||||
assert_eq!(tab_snapshot.expand_tabs("\ta".chars(), 2), 5);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -668,16 +630,16 @@ mod tests {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, mut char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
char_snapshot.max_expansion_column = max_expansion_column;
|
||||
assert_eq!(char_snapshot.text(), output);
|
||||
tab_snapshot.max_expansion_column = max_expansion_column;
|
||||
assert_eq!(tab_snapshot.text(), output);
|
||||
|
||||
for (ix, c) in input.char_indices() {
|
||||
assert_eq!(
|
||||
char_snapshot
|
||||
tab_snapshot
|
||||
.chunks(
|
||||
CharPoint::new(0, ix as u32)..char_snapshot.max_point(),
|
||||
TabPoint::new(0, ix as u32)..tab_snapshot.max_point(),
|
||||
false,
|
||||
Highlights::default(),
|
||||
)
|
||||
@@ -691,13 +653,13 @@ mod tests {
|
||||
let input_point = Point::new(0, ix as u32);
|
||||
let output_point = Point::new(0, output.find(c).unwrap() as u32);
|
||||
assert_eq!(
|
||||
char_snapshot.to_char_point(FoldPoint(input_point)),
|
||||
CharPoint(output_point),
|
||||
"to_char_point({input_point:?})"
|
||||
tab_snapshot.to_tab_point(FoldPoint(input_point)),
|
||||
TabPoint(output_point),
|
||||
"to_tab_point({input_point:?})"
|
||||
);
|
||||
assert_eq!(
|
||||
char_snapshot
|
||||
.to_fold_point(CharPoint(output_point), Bias::Left)
|
||||
tab_snapshot
|
||||
.to_fold_point(TabPoint(output_point), Bias::Left)
|
||||
.0,
|
||||
FoldPoint(input_point),
|
||||
"to_fold_point({output_point:?})"
|
||||
@@ -715,10 +677,10 @@ mod tests {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, mut char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
char_snapshot.max_expansion_column = max_expansion_column;
|
||||
assert_eq!(char_snapshot.text(), input);
|
||||
tab_snapshot.max_expansion_column = max_expansion_column;
|
||||
assert_eq!(tab_snapshot.text(), input);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -729,10 +691,10 @@ mod tests {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
assert_eq!(
|
||||
chunks(&char_snapshot, CharPoint::zero()),
|
||||
chunks(&tab_snapshot, TabPoint::zero()),
|
||||
vec![
|
||||
(" ".to_string(), true),
|
||||
(" ".to_string(), false),
|
||||
@@ -741,7 +703,7 @@ mod tests {
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
chunks(&char_snapshot, CharPoint::new(0, 2)),
|
||||
chunks(&tab_snapshot, TabPoint::new(0, 2)),
|
||||
vec![
|
||||
(" ".to_string(), true),
|
||||
(" ".to_string(), false),
|
||||
@@ -750,7 +712,7 @@ mod tests {
|
||||
]
|
||||
);
|
||||
|
||||
fn chunks(snapshot: &CharSnapshot, start: CharPoint) -> Vec<(String, bool)> {
|
||||
fn chunks(snapshot: &TabSnapshot, start: TabPoint) -> Vec<(String, bool)> {
|
||||
let mut chunks = Vec::new();
|
||||
let mut was_tab = false;
|
||||
let mut text = String::new();
|
||||
@@ -796,12 +758,12 @@ mod tests {
|
||||
let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng);
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
|
||||
let (mut char_map, _) = CharMap::new(fold_snapshot.clone(), tab_size);
|
||||
let tabs_snapshot = char_map.set_max_expansion_column(32);
|
||||
let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
|
||||
let tabs_snapshot = tab_map.set_max_expansion_column(32);
|
||||
|
||||
let text = text::Rope::from(tabs_snapshot.text().as_str());
|
||||
log::info!(
|
||||
"CharMap text (tab size: {}): {:?}",
|
||||
"TabMap text (tab size: {}): {:?}",
|
||||
tab_size,
|
||||
tabs_snapshot.text(),
|
||||
);
|
||||
@@ -809,11 +771,11 @@ mod tests {
|
||||
for _ in 0..5 {
|
||||
let end_row = rng.gen_range(0..=text.max_point().row);
|
||||
let end_column = rng.gen_range(0..=text.line_len(end_row));
|
||||
let mut end = CharPoint(text.clip_point(Point::new(end_row, end_column), Bias::Right));
|
||||
let mut end = TabPoint(text.clip_point(Point::new(end_row, end_column), Bias::Right));
|
||||
let start_row = rng.gen_range(0..=text.max_point().row);
|
||||
let start_column = rng.gen_range(0..=text.line_len(start_row));
|
||||
let mut start =
|
||||
CharPoint(text.clip_point(Point::new(start_row, start_column), Bias::Left));
|
||||
TabPoint(text.clip_point(Point::new(start_row, start_column), Bias::Left));
|
||||
if start > end {
|
||||
mem::swap(&mut start, &mut end);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
char_map::{self, CharPoint, CharSnapshot, TabEdit},
|
||||
fold_map::FoldBufferRows,
|
||||
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
|
||||
Highlights,
|
||||
};
|
||||
use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
|
||||
@@ -12,7 +12,7 @@ use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
|
||||
use sum_tree::{Bias, Cursor, SumTree};
|
||||
use text::Patch;
|
||||
|
||||
pub use super::char_map::TextSummary;
|
||||
pub use super::tab_map::TextSummary;
|
||||
pub type WrapEdit = text::Edit<u32>;
|
||||
|
||||
/// Handles soft wrapping of text.
|
||||
@@ -20,7 +20,7 @@ pub type WrapEdit = text::Edit<u32>;
|
||||
/// See the [`display_map` module documentation](crate::display_map) for more information.
|
||||
pub struct WrapMap {
|
||||
snapshot: WrapSnapshot,
|
||||
pending_edits: VecDeque<(CharSnapshot, Vec<TabEdit>)>,
|
||||
pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>,
|
||||
interpolated_edits: Patch<u32>,
|
||||
edits_since_sync: Patch<u32>,
|
||||
wrap_width: Option<Pixels>,
|
||||
@@ -30,7 +30,7 @@ pub struct WrapMap {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WrapSnapshot {
|
||||
char_snapshot: CharSnapshot,
|
||||
tab_snapshot: TabSnapshot,
|
||||
transforms: SumTree<Transform>,
|
||||
interpolated: bool,
|
||||
}
|
||||
@@ -51,11 +51,11 @@ struct TransformSummary {
|
||||
pub struct WrapPoint(pub Point);
|
||||
|
||||
pub struct WrapChunks<'a> {
|
||||
input_chunks: char_map::TabChunks<'a>,
|
||||
input_chunks: tab_map::TabChunks<'a>,
|
||||
input_chunk: Chunk<'a>,
|
||||
output_position: WrapPoint,
|
||||
max_output_row: u32,
|
||||
transforms: Cursor<'a, Transform, (WrapPoint, CharPoint)>,
|
||||
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
|
||||
snapshot: &'a WrapSnapshot,
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ pub struct WrapBufferRows<'a> {
|
||||
output_row: u32,
|
||||
soft_wrapped: bool,
|
||||
max_output_row: u32,
|
||||
transforms: Cursor<'a, Transform, (WrapPoint, CharPoint)>,
|
||||
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
|
||||
}
|
||||
|
||||
impl<'a> WrapBufferRows<'a> {
|
||||
@@ -86,7 +86,7 @@ impl<'a> WrapBufferRows<'a> {
|
||||
|
||||
impl WrapMap {
|
||||
pub fn new(
|
||||
char_snapshot: CharSnapshot,
|
||||
tab_snapshot: TabSnapshot,
|
||||
font: Font,
|
||||
font_size: Pixels,
|
||||
wrap_width: Option<Pixels>,
|
||||
@@ -99,7 +99,7 @@ impl WrapMap {
|
||||
pending_edits: Default::default(),
|
||||
interpolated_edits: Default::default(),
|
||||
edits_since_sync: Default::default(),
|
||||
snapshot: WrapSnapshot::new(char_snapshot),
|
||||
snapshot: WrapSnapshot::new(tab_snapshot),
|
||||
background_task: None,
|
||||
};
|
||||
this.set_wrap_width(wrap_width, cx);
|
||||
@@ -117,17 +117,17 @@ impl WrapMap {
|
||||
|
||||
pub fn sync(
|
||||
&mut self,
|
||||
char_snapshot: CharSnapshot,
|
||||
tab_snapshot: TabSnapshot,
|
||||
edits: Vec<TabEdit>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> (WrapSnapshot, Patch<u32>) {
|
||||
if self.wrap_width.is_some() {
|
||||
self.pending_edits.push_back((char_snapshot, edits));
|
||||
self.pending_edits.push_back((tab_snapshot, edits));
|
||||
self.flush_edits(cx);
|
||||
} else {
|
||||
self.edits_since_sync = self
|
||||
.edits_since_sync
|
||||
.compose(self.snapshot.interpolate(char_snapshot, &edits));
|
||||
.compose(self.snapshot.interpolate(tab_snapshot, &edits));
|
||||
self.snapshot.interpolated = false;
|
||||
}
|
||||
|
||||
@@ -177,11 +177,11 @@ impl WrapMap {
|
||||
let (font, font_size) = self.font_with_size.clone();
|
||||
let task = cx.background_executor().spawn(async move {
|
||||
let mut line_wrapper = text_system.line_wrapper(font, font_size);
|
||||
let char_snapshot = new_snapshot.char_snapshot.clone();
|
||||
let range = CharPoint::zero()..char_snapshot.max_point();
|
||||
let tab_snapshot = new_snapshot.tab_snapshot.clone();
|
||||
let range = TabPoint::zero()..tab_snapshot.max_point();
|
||||
let edits = new_snapshot
|
||||
.update(
|
||||
char_snapshot,
|
||||
tab_snapshot,
|
||||
&[TabEdit {
|
||||
old: range.clone(),
|
||||
new: range.clone(),
|
||||
@@ -221,7 +221,7 @@ impl WrapMap {
|
||||
} else {
|
||||
let old_rows = self.snapshot.transforms.summary().output.lines.row + 1;
|
||||
self.snapshot.transforms = SumTree::default();
|
||||
let summary = self.snapshot.char_snapshot.text_summary();
|
||||
let summary = self.snapshot.tab_snapshot.text_summary();
|
||||
if !summary.lines.is_zero() {
|
||||
self.snapshot
|
||||
.transforms
|
||||
@@ -239,8 +239,8 @@ impl WrapMap {
|
||||
fn flush_edits(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if !self.snapshot.interpolated {
|
||||
let mut to_remove_len = 0;
|
||||
for (char_snapshot, _) in &self.pending_edits {
|
||||
if char_snapshot.version <= self.snapshot.char_snapshot.version {
|
||||
for (tab_snapshot, _) in &self.pending_edits {
|
||||
if tab_snapshot.version <= self.snapshot.tab_snapshot.version {
|
||||
to_remove_len += 1;
|
||||
} else {
|
||||
break;
|
||||
@@ -262,9 +262,9 @@ impl WrapMap {
|
||||
let update_task = cx.background_executor().spawn(async move {
|
||||
let mut edits = Patch::default();
|
||||
let mut line_wrapper = text_system.line_wrapper(font, font_size);
|
||||
for (char_snapshot, tab_edits) in pending_edits {
|
||||
for (tab_snapshot, tab_edits) in pending_edits {
|
||||
let wrap_edits = snapshot
|
||||
.update(char_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
|
||||
.update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
|
||||
.await;
|
||||
edits = edits.compose(&wrap_edits);
|
||||
}
|
||||
@@ -301,11 +301,11 @@ impl WrapMap {
|
||||
|
||||
let was_interpolated = self.snapshot.interpolated;
|
||||
let mut to_remove_len = 0;
|
||||
for (char_snapshot, edits) in &self.pending_edits {
|
||||
if char_snapshot.version <= self.snapshot.char_snapshot.version {
|
||||
for (tab_snapshot, edits) in &self.pending_edits {
|
||||
if tab_snapshot.version <= self.snapshot.tab_snapshot.version {
|
||||
to_remove_len += 1;
|
||||
} else {
|
||||
let interpolated_edits = self.snapshot.interpolate(char_snapshot.clone(), edits);
|
||||
let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), edits);
|
||||
self.edits_since_sync = self.edits_since_sync.compose(&interpolated_edits);
|
||||
self.interpolated_edits = self.interpolated_edits.compose(&interpolated_edits);
|
||||
}
|
||||
@@ -318,49 +318,45 @@ impl WrapMap {
|
||||
}
|
||||
|
||||
impl WrapSnapshot {
|
||||
fn new(char_snapshot: CharSnapshot) -> Self {
|
||||
fn new(tab_snapshot: TabSnapshot) -> Self {
|
||||
let mut transforms = SumTree::default();
|
||||
let extent = char_snapshot.text_summary();
|
||||
let extent = tab_snapshot.text_summary();
|
||||
if !extent.lines.is_zero() {
|
||||
transforms.push(Transform::isomorphic(extent), &());
|
||||
}
|
||||
Self {
|
||||
transforms,
|
||||
char_snapshot,
|
||||
tab_snapshot,
|
||||
interpolated: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
|
||||
self.char_snapshot.buffer_snapshot()
|
||||
self.tab_snapshot.buffer_snapshot()
|
||||
}
|
||||
|
||||
fn interpolate(
|
||||
&mut self,
|
||||
new_char_snapshot: CharSnapshot,
|
||||
tab_edits: &[TabEdit],
|
||||
) -> Patch<u32> {
|
||||
fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch<u32> {
|
||||
let mut new_transforms;
|
||||
if tab_edits.is_empty() {
|
||||
new_transforms = self.transforms.clone();
|
||||
} else {
|
||||
let mut old_cursor = self.transforms.cursor::<CharPoint>(&());
|
||||
let mut old_cursor = self.transforms.cursor::<TabPoint>(&());
|
||||
|
||||
let mut tab_edits_iter = tab_edits.iter().peekable();
|
||||
new_transforms =
|
||||
old_cursor.slice(&tab_edits_iter.peek().unwrap().old.start, Bias::Right, &());
|
||||
|
||||
while let Some(edit) = tab_edits_iter.next() {
|
||||
if edit.new.start > CharPoint::from(new_transforms.summary().input.lines) {
|
||||
let summary = new_char_snapshot.text_summary_for_range(
|
||||
CharPoint::from(new_transforms.summary().input.lines)..edit.new.start,
|
||||
if edit.new.start > TabPoint::from(new_transforms.summary().input.lines) {
|
||||
let summary = new_tab_snapshot.text_summary_for_range(
|
||||
TabPoint::from(new_transforms.summary().input.lines)..edit.new.start,
|
||||
);
|
||||
new_transforms.push_or_extend(Transform::isomorphic(summary));
|
||||
}
|
||||
|
||||
if !edit.new.is_empty() {
|
||||
new_transforms.push_or_extend(Transform::isomorphic(
|
||||
new_char_snapshot.text_summary_for_range(edit.new.clone()),
|
||||
new_tab_snapshot.text_summary_for_range(edit.new.clone()),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -369,7 +365,7 @@ impl WrapSnapshot {
|
||||
if next_edit.old.start > old_cursor.end(&()) {
|
||||
if old_cursor.end(&()) > edit.old.end {
|
||||
let summary = self
|
||||
.char_snapshot
|
||||
.tab_snapshot
|
||||
.text_summary_for_range(edit.old.end..old_cursor.end(&()));
|
||||
new_transforms.push_or_extend(Transform::isomorphic(summary));
|
||||
}
|
||||
@@ -383,7 +379,7 @@ impl WrapSnapshot {
|
||||
} else {
|
||||
if old_cursor.end(&()) > edit.old.end {
|
||||
let summary = self
|
||||
.char_snapshot
|
||||
.tab_snapshot
|
||||
.text_summary_for_range(edit.old.end..old_cursor.end(&()));
|
||||
new_transforms.push_or_extend(Transform::isomorphic(summary));
|
||||
}
|
||||
@@ -396,7 +392,7 @@ impl WrapSnapshot {
|
||||
let old_snapshot = mem::replace(
|
||||
self,
|
||||
WrapSnapshot {
|
||||
char_snapshot: new_char_snapshot,
|
||||
tab_snapshot: new_tab_snapshot,
|
||||
transforms: new_transforms,
|
||||
interpolated: true,
|
||||
},
|
||||
@@ -407,7 +403,7 @@ impl WrapSnapshot {
|
||||
|
||||
async fn update(
|
||||
&mut self,
|
||||
new_char_snapshot: CharSnapshot,
|
||||
new_tab_snapshot: TabSnapshot,
|
||||
tab_edits: &[TabEdit],
|
||||
wrap_width: Pixels,
|
||||
line_wrapper: &mut LineWrapper,
|
||||
@@ -444,27 +440,27 @@ impl WrapSnapshot {
|
||||
new_transforms = self.transforms.clone();
|
||||
} else {
|
||||
let mut row_edits = row_edits.into_iter().peekable();
|
||||
let mut old_cursor = self.transforms.cursor::<CharPoint>(&());
|
||||
let mut old_cursor = self.transforms.cursor::<TabPoint>(&());
|
||||
|
||||
new_transforms = old_cursor.slice(
|
||||
&CharPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
|
||||
&TabPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
|
||||
Bias::Right,
|
||||
&(),
|
||||
);
|
||||
|
||||
while let Some(edit) = row_edits.next() {
|
||||
if edit.new_rows.start > new_transforms.summary().input.lines.row {
|
||||
let summary = new_char_snapshot.text_summary_for_range(
|
||||
CharPoint(new_transforms.summary().input.lines)
|
||||
..CharPoint::new(edit.new_rows.start, 0),
|
||||
let summary = new_tab_snapshot.text_summary_for_range(
|
||||
TabPoint(new_transforms.summary().input.lines)
|
||||
..TabPoint::new(edit.new_rows.start, 0),
|
||||
);
|
||||
new_transforms.push_or_extend(Transform::isomorphic(summary));
|
||||
}
|
||||
|
||||
let mut line = String::new();
|
||||
let mut remaining = None;
|
||||
let mut chunks = new_char_snapshot.chunks(
|
||||
CharPoint::new(edit.new_rows.start, 0)..new_char_snapshot.max_point(),
|
||||
let mut chunks = new_tab_snapshot.chunks(
|
||||
TabPoint::new(edit.new_rows.start, 0)..new_tab_snapshot.max_point(),
|
||||
false,
|
||||
Highlights::default(),
|
||||
);
|
||||
@@ -511,19 +507,19 @@ impl WrapSnapshot {
|
||||
}
|
||||
new_transforms.extend(edit_transforms, &());
|
||||
|
||||
old_cursor.seek_forward(&CharPoint::new(edit.old_rows.end, 0), Bias::Right, &());
|
||||
old_cursor.seek_forward(&TabPoint::new(edit.old_rows.end, 0), Bias::Right, &());
|
||||
if let Some(next_edit) = row_edits.peek() {
|
||||
if next_edit.old_rows.start > old_cursor.end(&()).row() {
|
||||
if old_cursor.end(&()) > CharPoint::new(edit.old_rows.end, 0) {
|
||||
let summary = self.char_snapshot.text_summary_for_range(
|
||||
CharPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()),
|
||||
if old_cursor.end(&()) > TabPoint::new(edit.old_rows.end, 0) {
|
||||
let summary = self.tab_snapshot.text_summary_for_range(
|
||||
TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()),
|
||||
);
|
||||
new_transforms.push_or_extend(Transform::isomorphic(summary));
|
||||
}
|
||||
old_cursor.next(&());
|
||||
new_transforms.append(
|
||||
old_cursor.slice(
|
||||
&CharPoint::new(next_edit.old_rows.start, 0),
|
||||
&TabPoint::new(next_edit.old_rows.start, 0),
|
||||
Bias::Right,
|
||||
&(),
|
||||
),
|
||||
@@ -531,9 +527,9 @@ impl WrapSnapshot {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if old_cursor.end(&()) > CharPoint::new(edit.old_rows.end, 0) {
|
||||
let summary = self.char_snapshot.text_summary_for_range(
|
||||
CharPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()),
|
||||
if old_cursor.end(&()) > TabPoint::new(edit.old_rows.end, 0) {
|
||||
let summary = self.tab_snapshot.text_summary_for_range(
|
||||
TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()),
|
||||
);
|
||||
new_transforms.push_or_extend(Transform::isomorphic(summary));
|
||||
}
|
||||
@@ -546,7 +542,7 @@ impl WrapSnapshot {
|
||||
let old_snapshot = mem::replace(
|
||||
self,
|
||||
WrapSnapshot {
|
||||
char_snapshot: new_char_snapshot,
|
||||
tab_snapshot: new_tab_snapshot,
|
||||
transforms: new_transforms,
|
||||
interpolated: false,
|
||||
},
|
||||
@@ -599,17 +595,17 @@ impl WrapSnapshot {
|
||||
) -> WrapChunks<'a> {
|
||||
let output_start = WrapPoint::new(rows.start, 0);
|
||||
let output_end = WrapPoint::new(rows.end, 0);
|
||||
let mut transforms = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
|
||||
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||
transforms.seek(&output_start, Bias::Right, &());
|
||||
let mut input_start = CharPoint(transforms.start().1 .0);
|
||||
let mut input_start = TabPoint(transforms.start().1 .0);
|
||||
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
|
||||
input_start.0 += output_start.0 - transforms.start().0 .0;
|
||||
}
|
||||
let input_end = self
|
||||
.to_char_point(output_end)
|
||||
.min(self.char_snapshot.max_point());
|
||||
.to_tab_point(output_end)
|
||||
.min(self.tab_snapshot.max_point());
|
||||
WrapChunks {
|
||||
input_chunks: self.char_snapshot.chunks(
|
||||
input_chunks: self.tab_snapshot.chunks(
|
||||
input_start..input_end,
|
||||
language_aware,
|
||||
highlights,
|
||||
@@ -627,7 +623,7 @@ impl WrapSnapshot {
|
||||
}
|
||||
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
let mut cursor = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
|
||||
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left, &());
|
||||
if cursor
|
||||
.item()
|
||||
@@ -635,7 +631,7 @@ impl WrapSnapshot {
|
||||
{
|
||||
let overshoot = row - cursor.start().0.row();
|
||||
let tab_row = cursor.start().1.row() + overshoot;
|
||||
let tab_line_len = self.char_snapshot.line_len(tab_row);
|
||||
let tab_line_len = self.tab_snapshot.line_len(tab_row);
|
||||
if overshoot == 0 {
|
||||
cursor.start().0.column() + (tab_line_len - cursor.start().1.column())
|
||||
} else {
|
||||
@@ -652,17 +648,15 @@ impl WrapSnapshot {
|
||||
let start = WrapPoint::new(rows.start, 0);
|
||||
let end = WrapPoint::new(rows.end, 0);
|
||||
|
||||
let mut cursor = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
|
||||
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||
cursor.seek(&start, Bias::Right, &());
|
||||
if let Some(transform) = cursor.item() {
|
||||
let start_in_transform = start.0 - cursor.start().0 .0;
|
||||
let end_in_transform = cmp::min(end, cursor.end(&()).0).0 - cursor.start().0 .0;
|
||||
if transform.is_isomorphic() {
|
||||
let char_start = CharPoint(cursor.start().1 .0 + start_in_transform);
|
||||
let char_end = CharPoint(cursor.start().1 .0 + end_in_transform);
|
||||
summary += &self
|
||||
.char_snapshot
|
||||
.text_summary_for_range(char_start..char_end);
|
||||
let tab_start = TabPoint(cursor.start().1 .0 + start_in_transform);
|
||||
let tab_end = TabPoint(cursor.start().1 .0 + end_in_transform);
|
||||
summary += &self.tab_snapshot.text_summary_for_range(tab_start..tab_end);
|
||||
} else {
|
||||
debug_assert_eq!(start_in_transform.row, end_in_transform.row);
|
||||
let indent_len = end_in_transform.column - start_in_transform.column;
|
||||
@@ -687,9 +681,9 @@ impl WrapSnapshot {
|
||||
let end_in_transform = end.0 - cursor.start().0 .0;
|
||||
if transform.is_isomorphic() {
|
||||
let char_start = cursor.start().1;
|
||||
let char_end = CharPoint(char_start.0 + end_in_transform);
|
||||
let char_end = TabPoint(char_start.0 + end_in_transform);
|
||||
summary += &self
|
||||
.char_snapshot
|
||||
.tab_snapshot
|
||||
.text_summary_for_range(char_start..char_end);
|
||||
} else {
|
||||
debug_assert_eq!(end_in_transform, Point::new(1, 0));
|
||||
@@ -724,14 +718,14 @@ impl WrapSnapshot {
|
||||
}
|
||||
|
||||
pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows {
|
||||
let mut transforms = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
|
||||
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
|
||||
let mut input_row = transforms.start().1.row();
|
||||
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
|
||||
input_row += start_row - transforms.start().0.row();
|
||||
}
|
||||
let soft_wrapped = transforms.item().map_or(false, |t| !t.is_isomorphic());
|
||||
let mut input_buffer_rows = self.char_snapshot.buffer_rows(input_row);
|
||||
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(input_row);
|
||||
let input_buffer_row = input_buffer_rows.next().unwrap();
|
||||
WrapBufferRows {
|
||||
transforms,
|
||||
@@ -743,26 +737,26 @@ impl WrapSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_char_point(&self, point: WrapPoint) -> CharPoint {
|
||||
let mut cursor = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
|
||||
pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint {
|
||||
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
let mut char_point = cursor.start().1 .0;
|
||||
let mut tab_point = cursor.start().1 .0;
|
||||
if cursor.item().map_or(false, |t| t.is_isomorphic()) {
|
||||
char_point += point.0 - cursor.start().0 .0;
|
||||
tab_point += point.0 - cursor.start().0 .0;
|
||||
}
|
||||
CharPoint(char_point)
|
||||
TabPoint(tab_point)
|
||||
}
|
||||
|
||||
pub fn to_point(&self, point: WrapPoint, bias: Bias) -> Point {
|
||||
self.char_snapshot.to_point(self.to_char_point(point), bias)
|
||||
self.tab_snapshot.to_point(self.to_tab_point(point), bias)
|
||||
}
|
||||
|
||||
pub fn make_wrap_point(&self, point: Point, bias: Bias) -> WrapPoint {
|
||||
self.char_point_to_wrap_point(self.char_snapshot.make_char_point(point, bias))
|
||||
self.tab_point_to_wrap_point(self.tab_snapshot.make_tab_point(point, bias))
|
||||
}
|
||||
|
||||
pub fn char_point_to_wrap_point(&self, point: CharPoint) -> WrapPoint {
|
||||
let mut cursor = self.transforms.cursor::<(CharPoint, WrapPoint)>(&());
|
||||
pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint {
|
||||
let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
WrapPoint(cursor.start().1 .0 + (point.0 - cursor.start().0 .0))
|
||||
}
|
||||
@@ -777,10 +771,7 @@ impl WrapSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
self.char_point_to_wrap_point(
|
||||
self.char_snapshot
|
||||
.clip_point(self.to_char_point(point), bias),
|
||||
)
|
||||
self.tab_point_to_wrap_point(self.tab_snapshot.clip_point(self.to_tab_point(point), bias))
|
||||
}
|
||||
|
||||
pub fn prev_row_boundary(&self, mut point: WrapPoint) -> u32 {
|
||||
@@ -790,7 +781,7 @@ impl WrapSnapshot {
|
||||
|
||||
*point.column_mut() = 0;
|
||||
|
||||
let mut cursor = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
|
||||
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev(&());
|
||||
@@ -810,7 +801,7 @@ impl WrapSnapshot {
|
||||
pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option<u32> {
|
||||
point.0 += Point::new(1, 0);
|
||||
|
||||
let mut cursor = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
|
||||
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
while let Some(transform) = cursor.item() {
|
||||
if transform.is_isomorphic() && cursor.start().1.column() == 0 {
|
||||
@@ -842,8 +833,8 @@ impl WrapSnapshot {
|
||||
#[cfg(test)]
|
||||
{
|
||||
assert_eq!(
|
||||
CharPoint::from(self.transforms.summary().input.lines),
|
||||
self.char_snapshot.max_point()
|
||||
TabPoint::from(self.transforms.summary().input.lines),
|
||||
self.tab_snapshot.max_point()
|
||||
);
|
||||
|
||||
{
|
||||
@@ -856,18 +847,18 @@ impl WrapSnapshot {
|
||||
}
|
||||
|
||||
let text = language::Rope::from(self.text().as_str());
|
||||
let mut input_buffer_rows = self.char_snapshot.buffer_rows(0);
|
||||
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
|
||||
let mut expected_buffer_rows = Vec::new();
|
||||
let mut prev_tab_row = 0;
|
||||
for display_row in 0..=self.max_point().row() {
|
||||
let char_point = self.to_char_point(WrapPoint::new(display_row, 0));
|
||||
if char_point.row() == prev_tab_row && display_row != 0 {
|
||||
let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0));
|
||||
if tab_point.row() == prev_tab_row && display_row != 0 {
|
||||
expected_buffer_rows.push(None);
|
||||
} else {
|
||||
expected_buffer_rows.push(input_buffer_rows.next().unwrap());
|
||||
}
|
||||
|
||||
prev_tab_row = char_point.row();
|
||||
prev_tab_row = tab_point.row();
|
||||
assert_eq!(self.line_len(display_row), text.line_len(display_row));
|
||||
}
|
||||
|
||||
@@ -889,14 +880,14 @@ impl<'a> WrapChunks<'a> {
|
||||
let output_start = WrapPoint::new(rows.start, 0);
|
||||
let output_end = WrapPoint::new(rows.end, 0);
|
||||
self.transforms.seek(&output_start, Bias::Right, &());
|
||||
let mut input_start = CharPoint(self.transforms.start().1 .0);
|
||||
let mut input_start = TabPoint(self.transforms.start().1 .0);
|
||||
if self.transforms.item().map_or(false, |t| t.is_isomorphic()) {
|
||||
input_start.0 += output_start.0 - self.transforms.start().0 .0;
|
||||
}
|
||||
let input_end = self
|
||||
.snapshot
|
||||
.to_char_point(output_end)
|
||||
.min(self.snapshot.char_snapshot.max_point());
|
||||
.to_tab_point(output_end)
|
||||
.min(self.snapshot.tab_snapshot.max_point());
|
||||
self.input_chunks.seek(input_start..input_end);
|
||||
self.input_chunk = Chunk::default();
|
||||
self.output_position = output_start;
|
||||
@@ -951,11 +942,13 @@ impl<'a> Iterator for WrapChunks<'a> {
|
||||
} else {
|
||||
*self.output_position.column_mut() += char_len as u32;
|
||||
}
|
||||
|
||||
if self.output_position >= transform_end {
|
||||
self.transforms.next(&());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let (prefix, suffix) = self.input_chunk.text.split_at(input_len);
|
||||
self.input_chunk.text = suffix;
|
||||
Some(Chunk {
|
||||
@@ -1110,7 +1103,7 @@ impl sum_tree::Summary for TransformSummary {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for CharPoint {
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
@@ -1120,7 +1113,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for CharPoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for CharPoint {
|
||||
impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for TabPoint {
|
||||
fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering {
|
||||
Ord::cmp(&self.0, &cursor_location.input.lines)
|
||||
}
|
||||
@@ -1168,7 +1161,7 @@ fn consolidate_wrap_edits(edits: Vec<WrapEdit>) -> Vec<WrapEdit> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{char_map::CharMap, fold_map::FoldMap, inlay_map::InlayMap},
|
||||
display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
|
||||
MultiBuffer,
|
||||
};
|
||||
use gpui::{font, px, test::observe};
|
||||
@@ -1220,9 +1213,9 @@ mod tests {
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
|
||||
log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
let (mut char_map, _) = CharMap::new(fold_snapshot.clone(), tab_size);
|
||||
let tabs_snapshot = char_map.set_max_expansion_column(32);
|
||||
log::info!("CharMap text: {:?}", tabs_snapshot.text());
|
||||
let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
|
||||
let tabs_snapshot = tab_map.set_max_expansion_column(32);
|
||||
log::info!("TabMap text: {:?}", tabs_snapshot.text());
|
||||
|
||||
let mut line_wrapper = text_system.line_wrapper(font.clone(), font_size);
|
||||
let unwrapped_text = tabs_snapshot.text();
|
||||
@@ -1268,7 +1261,7 @@ mod tests {
|
||||
20..=39 => {
|
||||
for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
|
||||
let (tabs_snapshot, tab_edits) =
|
||||
char_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (mut snapshot, wrap_edits) =
|
||||
wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
|
||||
snapshot.check_invariants();
|
||||
@@ -1281,7 +1274,7 @@ mod tests {
|
||||
inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tabs_snapshot, tab_edits) =
|
||||
char_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (mut snapshot, wrap_edits) =
|
||||
wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
|
||||
snapshot.check_invariants();
|
||||
@@ -1305,8 +1298,8 @@ mod tests {
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
let (tabs_snapshot, tab_edits) = char_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
log::info!("CharMap text: {:?}", tabs_snapshot.text());
|
||||
let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
log::info!("TabMap text: {:?}", tabs_snapshot.text());
|
||||
|
||||
let unwrapped_text = tabs_snapshot.text();
|
||||
let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
|
||||
@@ -1352,7 +1345,7 @@ mod tests {
|
||||
|
||||
if tab_size.get() == 1
|
||||
|| !wrapped_snapshot
|
||||
.char_snapshot
|
||||
.tab_snapshot
|
||||
.fold_snapshot
|
||||
.text()
|
||||
.contains('\t')
|
||||
|
||||
@@ -3218,7 +3218,7 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
let selections = self.selections.all_adjusted(cx);
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
let mut bracket_inserted = false;
|
||||
let mut edits = Vec::new();
|
||||
let mut linked_edits = HashMap::<_, Vec<_>>::default();
|
||||
@@ -3282,10 +3282,25 @@ impl Editor {
|
||||
&bracket_pair.start[..prefix_len],
|
||||
));
|
||||
|
||||
let is_closing_quote = if bracket_pair.end == bracket_pair.start
|
||||
&& bracket_pair.start.len() == 1
|
||||
{
|
||||
let target = bracket_pair.start.chars().next().unwrap();
|
||||
let current_line_count = snapshot
|
||||
.reversed_chars_at(selection.start)
|
||||
.take_while(|&c| c != '\n')
|
||||
.filter(|&c| c == target)
|
||||
.count();
|
||||
current_line_count % 2 == 1
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if autoclose
|
||||
&& bracket_pair.close
|
||||
&& following_text_allows_autoclose
|
||||
&& preceding_text_matches_prefix
|
||||
&& !is_closing_quote
|
||||
{
|
||||
let anchor = snapshot.anchor_before(selection.end);
|
||||
new_selections.push((selection.map(|_| anchor), text.len()));
|
||||
@@ -3716,7 +3731,7 @@ impl Editor {
|
||||
let mut edits = Vec::new();
|
||||
let mut rows = Vec::new();
|
||||
|
||||
for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
|
||||
for (rows_inserted, selection) in self.selections.all::<Point>(cx).into_iter().enumerate() {
|
||||
let cursor = selection.head();
|
||||
let row = cursor.row;
|
||||
|
||||
@@ -3774,7 +3789,7 @@ impl Editor {
|
||||
let mut rows = Vec::new();
|
||||
let mut rows_inserted = 0;
|
||||
|
||||
for selection in self.selections.all_adjusted(cx) {
|
||||
for selection in self.selections.all::<Point>(cx) {
|
||||
let cursor = selection.head();
|
||||
let row = cursor.row;
|
||||
|
||||
@@ -3845,7 +3860,7 @@ impl Editor {
|
||||
|
||||
let text: Arc<str> = text.into();
|
||||
self.transact(cx, |this, cx| {
|
||||
let old_selections = this.selections.all_adjusted(cx);
|
||||
let old_selections = this.selections.all::<Point>(cx);
|
||||
let selection_anchors = this.buffer.update(cx, |buffer, cx| {
|
||||
let anchors = {
|
||||
let snapshot = buffer.read(cx);
|
||||
@@ -5803,7 +5818,7 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut selections = self.selections.all_adjusted(cx);
|
||||
let mut selections = self.selections.all::<Point>(cx);
|
||||
let buffer = self.buffer.read(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
let rows_iter = selections.iter().map(|s| s.head().row);
|
||||
@@ -10377,7 +10392,7 @@ impl Editor {
|
||||
|
||||
let selections = self
|
||||
.selections
|
||||
.all_adjusted(cx)
|
||||
.all::<Point>(cx)
|
||||
.into_iter()
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect_vec();
|
||||
@@ -10686,7 +10701,7 @@ impl Editor {
|
||||
pub fn fold(&mut self, _: &actions::Fold, cx: &mut ViewContext<Self>) {
|
||||
let mut fold_ranges = Vec::new();
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let selections = self.selections.all_adjusted(cx);
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
|
||||
for selection in selections {
|
||||
let range = selection.range().sorted();
|
||||
@@ -10728,15 +10743,44 @@ impl Editor {
|
||||
self.fold_ranges(fold_ranges, true, cx);
|
||||
}
|
||||
|
||||
fn fold_at_level(&mut self, fold_at: &FoldAtLevel, cx: &mut ViewContext<Self>) {
|
||||
let fold_at_level = fold_at.level;
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut fold_ranges = Vec::new();
|
||||
let mut stack = vec![(0, snapshot.max_buffer_row().0, 1)];
|
||||
|
||||
while let Some((mut start_row, end_row, current_level)) = stack.pop() {
|
||||
while start_row < end_row {
|
||||
match self.snapshot(cx).foldable_range(MultiBufferRow(start_row)) {
|
||||
Some(foldable_range) => {
|
||||
let nested_start_row = foldable_range.0.start.row + 1;
|
||||
let nested_end_row = foldable_range.0.end.row;
|
||||
|
||||
if current_level == fold_at_level {
|
||||
fold_ranges.push(foldable_range);
|
||||
}
|
||||
|
||||
if current_level <= fold_at_level {
|
||||
stack.push((nested_start_row, nested_end_row, current_level + 1));
|
||||
}
|
||||
|
||||
start_row = nested_end_row + 1;
|
||||
}
|
||||
None => start_row += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.fold_ranges(fold_ranges, true, cx);
|
||||
}
|
||||
|
||||
pub fn fold_all(&mut self, _: &actions::FoldAll, cx: &mut ViewContext<Self>) {
|
||||
let mut fold_ranges = Vec::new();
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
|
||||
for row in 0..display_map.max_buffer_row().0 {
|
||||
if let Some((foldable_range, fold_text)) =
|
||||
display_map.foldable_range(MultiBufferRow(row))
|
||||
{
|
||||
fold_ranges.push((foldable_range, fold_text));
|
||||
for row in 0..snapshot.max_buffer_row().0 {
|
||||
if let Some(foldable_range) = self.snapshot(cx).foldable_range(MultiBufferRow(row)) {
|
||||
fold_ranges.push(foldable_range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10746,7 +10790,7 @@ impl Editor {
|
||||
pub fn fold_recursive(&mut self, _: &actions::FoldRecursive, cx: &mut ViewContext<Self>) {
|
||||
let mut fold_ranges = Vec::new();
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let selections = self.selections.all_adjusted(cx);
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
|
||||
for selection in selections {
|
||||
let range = selection.range().sorted();
|
||||
@@ -12328,9 +12372,10 @@ impl Editor {
|
||||
return;
|
||||
};
|
||||
|
||||
let selections = self.selections.all::<usize>(cx);
|
||||
let buffer = self.buffer.read(cx);
|
||||
let mut new_selections_by_buffer = HashMap::default();
|
||||
for selection in self.selections.all::<usize>(cx) {
|
||||
for selection in selections {
|
||||
for (buffer, range, _) in
|
||||
buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
|
||||
{
|
||||
@@ -12375,6 +12420,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn open_excerpts_common(&mut self, split: bool, cx: &mut ViewContext<Self>) {
|
||||
let selections = self.selections.all::<usize>(cx);
|
||||
let buffer = self.buffer.read(cx);
|
||||
if buffer.is_singleton() {
|
||||
cx.propagate();
|
||||
@@ -12387,7 +12433,7 @@ impl Editor {
|
||||
};
|
||||
|
||||
let mut new_selections_by_buffer = HashMap::default();
|
||||
for selection in self.selections.all::<usize>(cx) {
|
||||
for selection in selections {
|
||||
for (mut buffer_handle, mut range, _) in
|
||||
buffer.range_to_buffer_ranges(selection.range(), cx)
|
||||
{
|
||||
@@ -12503,7 +12549,7 @@ impl Editor {
|
||||
fn selection_replacement_ranges(
|
||||
&self,
|
||||
range: Range<OffsetUtf16>,
|
||||
cx: &AppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> Vec<Range<OffsetUtf16>> {
|
||||
let selections = self.selections.all::<OffsetUtf16>(cx);
|
||||
let newest_selection = selections
|
||||
|
||||
@@ -1080,6 +1080,112 @@ fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_fold_at_level(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let view = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(
|
||||
&"
|
||||
class Foo:
|
||||
# Hello!
|
||||
|
||||
def a():
|
||||
print(1)
|
||||
|
||||
def b():
|
||||
print(2)
|
||||
|
||||
|
||||
class Bar:
|
||||
# World!
|
||||
|
||||
def a():
|
||||
print(1)
|
||||
|
||||
def b():
|
||||
print(2)
|
||||
|
||||
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
build_editor(buffer.clone(), cx)
|
||||
});
|
||||
|
||||
_ = view.update(cx, |view, cx| {
|
||||
view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
|
||||
assert_eq!(
|
||||
view.display_text(cx),
|
||||
"
|
||||
class Foo:
|
||||
# Hello!
|
||||
|
||||
def a():⋯
|
||||
|
||||
def b():⋯
|
||||
|
||||
|
||||
class Bar:
|
||||
# World!
|
||||
|
||||
def a():⋯
|
||||
|
||||
def b():⋯
|
||||
|
||||
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
|
||||
assert_eq!(
|
||||
view.display_text(cx),
|
||||
"
|
||||
class Foo:⋯
|
||||
|
||||
|
||||
class Bar:⋯
|
||||
|
||||
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
view.unfold_all(&UnfoldAll, cx);
|
||||
view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
|
||||
assert_eq!(
|
||||
view.display_text(cx),
|
||||
"
|
||||
class Foo:
|
||||
# Hello!
|
||||
|
||||
def a():
|
||||
print(1)
|
||||
|
||||
def b():
|
||||
print(2)
|
||||
|
||||
|
||||
class Bar:
|
||||
# World!
|
||||
|
||||
def a():
|
||||
print(1)
|
||||
|
||||
def b():
|
||||
print(2)
|
||||
|
||||
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_cursor(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
@@ -68,7 +68,6 @@ use sum_tree::Bias;
|
||||
use theme::{ActiveTheme, Appearance, PlayerColor};
|
||||
use ui::prelude::*;
|
||||
use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use util::RangeExt;
|
||||
use util::ResultExt;
|
||||
use workspace::{item::Item, Workspace};
|
||||
@@ -337,6 +336,7 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::open_url);
|
||||
register_action(view, cx, Editor::open_file);
|
||||
register_action(view, cx, Editor::fold);
|
||||
register_action(view, cx, Editor::fold_at_level);
|
||||
register_action(view, cx, Editor::fold_all);
|
||||
register_action(view, cx, Editor::fold_at);
|
||||
register_action(view, cx, Editor::fold_recursive);
|
||||
@@ -445,6 +445,7 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::accept_inline_completion);
|
||||
register_action(view, cx, Editor::revert_file);
|
||||
register_action(view, cx, Editor::revert_selected_hunks);
|
||||
register_action(view, cx, Editor::apply_all_diff_hunks);
|
||||
register_action(view, cx, Editor::apply_selected_diff_hunks);
|
||||
register_action(view, cx, Editor::open_active_item_in_terminal);
|
||||
register_action(view, cx, Editor::reload_file)
|
||||
@@ -1026,21 +1027,23 @@ impl EditorElement {
|
||||
}
|
||||
let block_text = if let CursorShape::Block = selection.cursor_shape {
|
||||
snapshot
|
||||
.grapheme_at(cursor_position)
|
||||
.display_chars_at(cursor_position)
|
||||
.next()
|
||||
.or_else(|| {
|
||||
if cursor_column == 0 {
|
||||
snapshot.placeholder_text().and_then(|s| {
|
||||
s.graphemes(true).next().map(|s| s.to_owned())
|
||||
})
|
||||
snapshot
|
||||
.placeholder_text()
|
||||
.and_then(|s| s.chars().next())
|
||||
.map(|c| (c, cursor_position))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.and_then(|grapheme| {
|
||||
let text = if grapheme == "\n" {
|
||||
.and_then(|(character, _)| {
|
||||
let text = if character == '\n' {
|
||||
SharedString::from(" ")
|
||||
} else {
|
||||
SharedString::from(grapheme)
|
||||
SharedString::from(character.to_string())
|
||||
};
|
||||
let len = text.len();
|
||||
|
||||
|
||||
@@ -706,10 +706,11 @@ pub(crate) async fn find_file(
|
||||
) -> Option<ResolvedPath> {
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.resolve_existing_file_path(&candidate_file_path, buffer, cx)
|
||||
project.resolve_path_in_buffer(&candidate_file_path, buffer, cx)
|
||||
})
|
||||
.ok()?
|
||||
.await
|
||||
.filter(|s| s.is_file())
|
||||
}
|
||||
|
||||
if let Some(existing_path) = check_path(&candidate_file_path, &project, buffer, cx).await {
|
||||
@@ -1612,4 +1613,46 @@ mod tests {
|
||||
assert_eq!(file_path.to_str().unwrap(), "/root/dir/file2.rs");
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_hover_directories(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Insert a new file
|
||||
let fs = cx.update_workspace(|workspace, cx| workspace.project().read(cx).fs().clone());
|
||||
fs.as_fake()
|
||||
.insert_file("/root/dir/file2.rs", "This is file2.rs".as_bytes().to_vec())
|
||||
.await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
You can't open ../diˇr because it's a directory.
|
||||
"});
|
||||
|
||||
// File does not exist
|
||||
let screen_coord = cx.pixel_position(indoc! {"
|
||||
You can't open ../diˇr because it's a directory.
|
||||
"});
|
||||
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
|
||||
|
||||
// No highlight
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(editor
|
||||
.snapshot(cx)
|
||||
.text_highlight_ranges::<HoveredLinkState>()
|
||||
.unwrap_or_default()
|
||||
.1
|
||||
.is_empty());
|
||||
});
|
||||
|
||||
// Does not open the directory
|
||||
cx.simulate_click(screen_coord, Modifiers::secondary_key());
|
||||
cx.update_workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::{
|
||||
display_map::{InlayOffset, ToDisplayPoint},
|
||||
hover_links::{InlayHighlight, RangeInEditor},
|
||||
is_invisible,
|
||||
scroll::ScrollAmount,
|
||||
Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
|
||||
Hover, RangeToAnchorExt,
|
||||
@@ -12,7 +11,7 @@ use gpui::{
|
||||
StyleRefinement, Styled, Task, TextStyleRefinement, View, ViewContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{Diagnostic, DiagnosticEntry, Language, LanguageRegistry};
|
||||
use language::{DiagnosticEntry, Language, LanguageRegistry};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use markdown::{Markdown, MarkdownStyle};
|
||||
use multi_buffer::ToOffset;
|
||||
@@ -200,6 +199,7 @@ fn show_hover(
|
||||
if editor.pending_rename.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let snapshot = editor.snapshot(cx);
|
||||
|
||||
let (buffer, buffer_position) = editor
|
||||
@@ -259,7 +259,7 @@ fn show_hover(
|
||||
}
|
||||
|
||||
// If there's a diagnostic, assign it on the hover state and notify
|
||||
let mut local_diagnostic = snapshot
|
||||
let local_diagnostic = snapshot
|
||||
.buffer_snapshot
|
||||
.diagnostics_in_range::<_, usize>(anchor..anchor, false)
|
||||
// Find the entry with the most specific range
|
||||
@@ -281,42 +281,6 @@ fn show_hover(
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(invisible) = snapshot
|
||||
.buffer_snapshot
|
||||
.chars_at(anchor)
|
||||
.next()
|
||||
.filter(|&c| is_invisible(c))
|
||||
{
|
||||
let after = snapshot.buffer_snapshot.anchor_after(
|
||||
anchor.to_offset(&snapshot.buffer_snapshot) + invisible.len_utf8(),
|
||||
);
|
||||
local_diagnostic = Some(DiagnosticEntry {
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: format!("Unicode character U+{:02X}", invisible as u32),
|
||||
..Default::default()
|
||||
},
|
||||
range: anchor..after,
|
||||
})
|
||||
} else if let Some(invisible) = snapshot
|
||||
.buffer_snapshot
|
||||
.reversed_chars_at(anchor)
|
||||
.next()
|
||||
.filter(|&c| is_invisible(c))
|
||||
{
|
||||
let before = snapshot.buffer_snapshot.anchor_before(
|
||||
anchor.to_offset(&snapshot.buffer_snapshot) - invisible.len_utf8(),
|
||||
);
|
||||
local_diagnostic = Some(DiagnosticEntry {
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: format!("Unicode character U+{:02X}", invisible as u32),
|
||||
..Default::default()
|
||||
},
|
||||
range: before..anchor,
|
||||
})
|
||||
}
|
||||
|
||||
let diagnostic_popover = if let Some(local_diagnostic) = local_diagnostic {
|
||||
let text = match local_diagnostic.diagnostic.source {
|
||||
Some(ref source) => {
|
||||
@@ -324,6 +288,7 @@ fn show_hover(
|
||||
}
|
||||
None => local_diagnostic.diagnostic.message.clone(),
|
||||
};
|
||||
|
||||
let mut border_color: Option<Hsla> = None;
|
||||
let mut background_color: Option<Hsla> = None;
|
||||
|
||||
@@ -379,6 +344,7 @@ fn show_hover(
|
||||
Markdown::new_text(text, markdown_style.clone(), None, cx, None)
|
||||
})
|
||||
.ok();
|
||||
|
||||
Some(DiagnosticPopover {
|
||||
local_diagnostic,
|
||||
primary_diagnostic,
|
||||
@@ -466,6 +432,7 @@ fn show_hover(
|
||||
cx.notify();
|
||||
cx.refresh();
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err()
|
||||
|
||||
@@ -16,10 +16,10 @@ use util::RangeExt;
|
||||
use workspace::Item;
|
||||
|
||||
use crate::{
|
||||
editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyDiffHunk,
|
||||
BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, DisplayRow,
|
||||
DisplaySnapshot, Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, RevertFile,
|
||||
RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
|
||||
editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyAllDiffHunks,
|
||||
ApplyDiffHunk, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight,
|
||||
DisplayRow, DisplaySnapshot, Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk,
|
||||
RevertFile, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -352,7 +352,11 @@ impl Editor {
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn apply_all_diff_hunks(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub(crate) fn apply_all_diff_hunks(
|
||||
&mut self,
|
||||
_: &ApplyAllDiffHunks,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let buffers = self.buffer.read(cx).all_buffers();
|
||||
for branch_buffer in buffers {
|
||||
branch_buffer.update(cx, |branch_buffer, cx| {
|
||||
|
||||
@@ -41,9 +41,9 @@ pub(super) fn refresh_linked_ranges(this: &mut Editor, cx: &mut ViewContext<Edit
|
||||
return None;
|
||||
}
|
||||
let project = this.project.clone()?;
|
||||
let selections = this.selections.all::<usize>(cx);
|
||||
let buffer = this.buffer.read(cx);
|
||||
let mut applicable_selections = vec![];
|
||||
let selections = this.selections.all::<usize>(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
for selection in selections {
|
||||
let cursor_position = selection.head();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{Editor, EditorEvent, SemanticsProvider};
|
||||
use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SemanticsProvider};
|
||||
use collections::HashSet;
|
||||
use futures::{channel::mpsc, future::join_all};
|
||||
use gpui::{AppContext, EventEmitter, FocusableView, Model, Render, Subscription, Task, View};
|
||||
@@ -8,7 +8,7 @@ use project::Project;
|
||||
use smol::stream::StreamExt;
|
||||
use std::{any::TypeId, ops::Range, rc::Rc, time::Duration};
|
||||
use text::ToOffset;
|
||||
use ui::prelude::*;
|
||||
use ui::{prelude::*, ButtonLike, KeyBinding};
|
||||
use workspace::{
|
||||
searchable::SearchableItemHandle, Item, ItemHandle as _, ToolbarItemEvent, ToolbarItemLocation,
|
||||
ToolbarItemView, Workspace,
|
||||
@@ -232,7 +232,10 @@ impl ProposedChangesEditor {
|
||||
|
||||
impl Render for ProposedChangesEditor {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
self.editor.clone()
|
||||
div()
|
||||
.size_full()
|
||||
.key_context("ProposedChangesEditor")
|
||||
.child(self.editor.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,17 +334,21 @@ impl ProposedChangesEditorToolbar {
|
||||
}
|
||||
|
||||
impl Render for ProposedChangesEditorToolbar {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let editor = self.current_editor.clone();
|
||||
Button::new("apply-changes", "Apply All").on_click(move |_, cx| {
|
||||
if let Some(editor) = &editor {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.editor.update(cx, |editor, cx| {
|
||||
editor.apply_all_diff_hunks(cx);
|
||||
})
|
||||
});
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let button_like = ButtonLike::new("apply-changes").child(Label::new("Apply All"));
|
||||
|
||||
match &self.current_editor {
|
||||
Some(editor) => {
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
let keybinding = KeyBinding::for_action_in(&ApplyAllDiffHunks, &focus_handle, cx)
|
||||
.map(|binding| binding.into_any_element());
|
||||
|
||||
button_like.children(keybinding).on_click({
|
||||
move |_event, cx| focus_handle.dispatch_action(&ApplyAllDiffHunks, cx)
|
||||
})
|
||||
}
|
||||
})
|
||||
None => button_like.disabled(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,14 +8,15 @@ use std::{
|
||||
use collections::HashMap;
|
||||
use gpui::{AppContext, Model, Pixels};
|
||||
use itertools::Itertools;
|
||||
use language::{Bias, Point, Selection, SelectionGoal, TextDimension, ToPoint};
|
||||
use language::{Bias, Point, Selection, SelectionGoal, TextDimension};
|
||||
use multi_buffer::AnchorRangeExt as _;
|
||||
use util::post_inc;
|
||||
|
||||
use crate::{
|
||||
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
|
||||
movement::TextLayoutDetails,
|
||||
Anchor, DisplayPoint, DisplayRow, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode,
|
||||
ToOffset,
|
||||
ToOffset, ToPoint,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -96,28 +97,68 @@ impl SelectionsCollection {
|
||||
|
||||
pub fn pending<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||
&self,
|
||||
cx: &AppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<Selection<D>> {
|
||||
self.pending_anchor()
|
||||
.as_ref()
|
||||
.map(|pending| pending.map(|p| p.summary::<D>(&self.buffer(cx))))
|
||||
let selection = self.pending_anchor()?;
|
||||
if self.line_mode {
|
||||
let map = self.display_map(cx);
|
||||
let range = selection.range().to_point(&map.buffer_snapshot);
|
||||
let expanded_range = map.expand_to_line(range);
|
||||
let mut endpoints = map
|
||||
.buffer_snapshot
|
||||
.dimensions_from_points::<D>([expanded_range.start, expanded_range.end]);
|
||||
let start = endpoints.next().unwrap();
|
||||
let end = endpoints.next().unwrap();
|
||||
Some(Selection {
|
||||
id: selection.id,
|
||||
start,
|
||||
end,
|
||||
reversed: selection.reversed,
|
||||
goal: selection.goal,
|
||||
})
|
||||
} else {
|
||||
Some(selection.map(|p| p.summary::<D>(&self.buffer(cx))))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pending_mode(&self) -> Option<SelectMode> {
|
||||
self.pending.as_ref().map(|pending| pending.mode.clone())
|
||||
}
|
||||
|
||||
pub fn all<'a, D>(&self, cx: &AppContext) -> Vec<Selection<D>>
|
||||
pub fn all<'a, D>(&self, cx: &mut AppContext) -> Vec<Selection<D>>
|
||||
where
|
||||
D: 'a + TextDimension + Ord + Sub<D, Output = D>,
|
||||
{
|
||||
// todo!()
|
||||
// let mut selections = self.all::<Point>(cx);
|
||||
// if self.line_mode {
|
||||
// let map = self.display_map(cx);
|
||||
// for selection in &mut selections {
|
||||
// let new_range = map.expand_to_line(selection.range());
|
||||
// selection.start = new_range.start;
|
||||
// selection.end = new_range.end;
|
||||
// }
|
||||
// }
|
||||
let map = self.display_map(cx);
|
||||
let disjoint_anchors = &self.disjoint;
|
||||
let mut disjoint =
|
||||
resolve_multiple::<D, _>(disjoint_anchors.iter(), &self.buffer(cx)).peekable();
|
||||
let mut disjoint = resolve_multiple::<Point, _>(disjoint_anchors.iter(), &self.buffer(cx))
|
||||
.map(|mut selection| {
|
||||
if self.line_mode {
|
||||
let new_range = map.expand_to_line(selection.range());
|
||||
selection.start = new_range.start;
|
||||
selection.end = new_range.end;
|
||||
}
|
||||
// todo!(expand selection to encompass folds and blocks)
|
||||
// let start = map.display_point_to_point(map.point_to_display_point(selection.start, Bias::Left), Bias::Left);
|
||||
// let end = map.display_point_to_point(map.point_to_display_point(selection.end, Bias::Right), Bias::Right);
|
||||
selection
|
||||
})
|
||||
.peekable();
|
||||
|
||||
let mut pending_opt = self.pending::<D>(cx);
|
||||
let mut pending_opt = self.pending::<Point>(cx);
|
||||
|
||||
iter::from_fn(move || {
|
||||
// todo!(return merged selections)
|
||||
if let Some(pending) = pending_opt.as_mut() {
|
||||
while let Some(next_selection) = disjoint.peek() {
|
||||
if pending.start <= next_selection.end && pending.end >= next_selection.start {
|
||||
@@ -140,23 +181,10 @@ impl SelectionsCollection {
|
||||
disjoint.next()
|
||||
}
|
||||
})
|
||||
// todo!("convert selections to D in a batched way")
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns all of the selections, adjusted to take into account the selection line_mode
|
||||
pub fn all_adjusted(&self, cx: &mut AppContext) -> Vec<Selection<Point>> {
|
||||
let mut selections = self.all::<Point>(cx);
|
||||
if self.line_mode {
|
||||
let map = self.display_map(cx);
|
||||
for selection in &mut selections {
|
||||
let new_range = map.expand_to_line(selection.range());
|
||||
selection.start = new_range.start;
|
||||
selection.end = new_range.end;
|
||||
}
|
||||
}
|
||||
selections
|
||||
}
|
||||
|
||||
/// Returns the newest selection, adjusted to take into account the selection line_mode
|
||||
pub fn newest_adjusted(&self, cx: &mut AppContext) -> Selection<Point> {
|
||||
let mut selection = self.newest::<Point>(cx);
|
||||
@@ -276,14 +304,14 @@ impl SelectionsCollection {
|
||||
|
||||
pub fn first<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||
&self,
|
||||
cx: &AppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> Selection<D> {
|
||||
self.all(cx).first().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn last<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||
&self,
|
||||
cx: &AppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> Selection<D> {
|
||||
self.all(cx).last().unwrap().clone()
|
||||
}
|
||||
@@ -298,7 +326,7 @@ impl SelectionsCollection {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn ranges<D: TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug>(
|
||||
&self,
|
||||
cx: &AppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> Vec<Range<D>> {
|
||||
self.all::<D>(cx)
|
||||
.iter()
|
||||
@@ -475,7 +503,7 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
where
|
||||
T: 'a + ToOffset + ToPoint + TextDimension + Ord + Sub<T, Output = T> + std::marker::Copy,
|
||||
{
|
||||
let mut selections = self.all(self.cx);
|
||||
let mut selections = self.collection.all(self.cx);
|
||||
let mut start = range.start.to_offset(&self.buffer());
|
||||
let mut end = range.end.to_offset(&self.buffer());
|
||||
let reversed = if start > end {
|
||||
@@ -649,6 +677,7 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
let mut changed = false;
|
||||
let display_map = self.display_map();
|
||||
let selections = self
|
||||
.collection
|
||||
.all::<Point>(self.cx)
|
||||
.into_iter()
|
||||
.map(|selection| {
|
||||
@@ -676,6 +705,7 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
let mut changed = false;
|
||||
let snapshot = self.buffer().clone();
|
||||
let selections = self
|
||||
.collection
|
||||
.all::<usize>(self.cx)
|
||||
.into_iter()
|
||||
.map(|selection| {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
ops::{Deref, DerefMut, Range},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
@@ -66,10 +67,12 @@ impl EditorLspTestContext {
|
||||
);
|
||||
language_registry.add(Arc::new(language));
|
||||
|
||||
let root = Self::root_path();
|
||||
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
|
||||
.insert_tree(root, json!({ "dir": { file_name.clone(): "" }}))
|
||||
.await;
|
||||
|
||||
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
@@ -79,7 +82,7 @@ impl EditorLspTestContext {
|
||||
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||
project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.find_or_create_worktree("/root", true, cx)
|
||||
project.find_or_create_worktree(root, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -108,7 +111,7 @@ impl EditorLspTestContext {
|
||||
},
|
||||
lsp,
|
||||
workspace,
|
||||
buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
|
||||
buffer_lsp_url: lsp::Url::from_file_path(root.join("dir").join(file_name)).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +126,7 @@ impl EditorLspTestContext {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
@@ -309,6 +313,16 @@ impl EditorLspTestContext {
|
||||
pub fn notify<T: notification::Notification>(&self, params: T::Params) {
|
||||
self.lsp.notify::<T>(params);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn root_path() -> &'static Path {
|
||||
Path::new("C:\\root")
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn root_path() -> &'static Path {
|
||||
Path::new("/root")
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for EditorLspTestContext {
|
||||
|
||||
@@ -17,6 +17,7 @@ use project::{FakeFs, Project};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
ops::{Deref, DerefMut, Range},
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
@@ -42,17 +43,18 @@ impl EditorTestContext {
|
||||
pub async fn new(cx: &mut gpui::TestAppContext) -> EditorTestContext {
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
// fs.insert_file("/file", "".to_owned()).await;
|
||||
let root = Self::root_path();
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
root,
|
||||
serde_json::json!({
|
||||
"file": "",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/root".as_ref()], cx).await;
|
||||
let project = Project::test(fs, [root], cx).await;
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer("/root/file", cx)
|
||||
project.open_local_buffer(root.join("file"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -71,6 +73,16 @@ impl EditorTestContext {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn root_path() -> &'static Path {
|
||||
Path::new("C:\\root")
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn root_path() -> &'static Path {
|
||||
Path::new("/root")
|
||||
}
|
||||
|
||||
pub async fn for_editor(editor: WindowHandle<Editor>, cx: &mut gpui::TestAppContext) -> Self {
|
||||
let editor_view = editor.root_view(cx).unwrap();
|
||||
Self {
|
||||
|
||||
@@ -8,7 +8,8 @@ use collections::HashMap;
|
||||
use futures::{Future, FutureExt};
|
||||
use gpui::AsyncAppContext;
|
||||
use language::{
|
||||
CodeLabel, HighlightId, Language, LanguageServerName, LspAdapter, LspAdapterDelegate,
|
||||
CodeLabel, HighlightId, Language, LanguageServerName, LanguageToolchainStore, LspAdapter,
|
||||
LspAdapterDelegate,
|
||||
};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions};
|
||||
use serde::Serialize;
|
||||
@@ -194,6 +195,7 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Arc<dyn LanguageToolchainStore>,
|
||||
_cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
let delegate = delegate.clone();
|
||||
|
||||
@@ -37,7 +37,7 @@ use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use indexed_docs::{IndexedDocsRegistry, ProviderId};
|
||||
use language::{
|
||||
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LanguageRegistry,
|
||||
QUERY_FILENAME_PREFIXES,
|
||||
LoadedLanguage, QUERY_FILENAME_PREFIXES,
|
||||
};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::ContextProviderWithTasks;
|
||||
@@ -1102,14 +1102,21 @@ impl ExtensionStore {
|
||||
let config = std::fs::read_to_string(language_path.join("config.toml"))?;
|
||||
let config: LanguageConfig = ::toml::from_str(&config)?;
|
||||
let queries = load_plugin_queries(&language_path);
|
||||
let tasks = std::fs::read_to_string(language_path.join("tasks.json"))
|
||||
.ok()
|
||||
.and_then(|contents| {
|
||||
let definitions = serde_json_lenient::from_str(&contents).log_err()?;
|
||||
Some(Arc::new(ContextProviderWithTasks::new(definitions)) as Arc<_>)
|
||||
});
|
||||
let context_provider =
|
||||
std::fs::read_to_string(language_path.join("tasks.json"))
|
||||
.ok()
|
||||
.and_then(|contents| {
|
||||
let definitions =
|
||||
serde_json_lenient::from_str(&contents).log_err()?;
|
||||
Some(Arc::new(ContextProviderWithTasks::new(definitions)) as Arc<_>)
|
||||
});
|
||||
|
||||
Ok((config, queries, tasks))
|
||||
Ok(LoadedLanguage {
|
||||
config,
|
||||
queries,
|
||||
context_provider,
|
||||
toolchain_provider: None,
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -790,9 +790,9 @@ impl FileFinderDelegate {
|
||||
let mut path_matches = Vec::new();
|
||||
|
||||
let abs_file_exists = if let Ok(task) = project.update(&mut cx, |this, cx| {
|
||||
this.abs_file_path_exists(query.path_query(), cx)
|
||||
this.resolve_abs_file_path(query.path_query(), cx)
|
||||
}) {
|
||||
task.await
|
||||
task.await.is_some()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
@@ -242,7 +242,7 @@ async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) {
|
||||
cx.executor().advance_clock(Duration::from_secs(2));
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
let all_selections = editor.selections.all_adjusted(cx);
|
||||
let all_selections = editor.selections.all::<Point>(cx);
|
||||
assert_eq!(
|
||||
all_selections.len(),
|
||||
1,
|
||||
@@ -317,7 +317,7 @@ async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) {
|
||||
cx.executor().advance_clock(Duration::from_secs(2));
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
let all_selections = editor.selections.all_adjusted(cx);
|
||||
let all_selections = editor.selections.all::<Point>(cx);
|
||||
assert_eq!(
|
||||
all_selections.len(),
|
||||
1,
|
||||
|
||||
@@ -813,6 +813,7 @@ struct FakeFsState {
|
||||
root: Arc<Mutex<FakeFsEntry>>,
|
||||
next_inode: u64,
|
||||
next_mtime: SystemTime,
|
||||
git_event_tx: smol::channel::Sender<PathBuf>,
|
||||
event_txs: Vec<smol::channel::Sender<Vec<PathEvent>>>,
|
||||
events_paused: bool,
|
||||
buffered_events: Vec<PathEvent>,
|
||||
@@ -865,14 +866,22 @@ impl FakeFsState {
|
||||
let mut entry_stack = Vec::new();
|
||||
'outer: loop {
|
||||
let mut path_components = path.components().peekable();
|
||||
let mut prefix = None;
|
||||
while let Some(component) = path_components.next() {
|
||||
match component {
|
||||
Component::Prefix(_) => panic!("prefix paths aren't supported"),
|
||||
Component::Prefix(prefix_component) => prefix = Some(prefix_component),
|
||||
Component::RootDir => {
|
||||
entry_stack.clear();
|
||||
entry_stack.push(self.root.clone());
|
||||
canonical_path.clear();
|
||||
canonical_path.push("/");
|
||||
match prefix {
|
||||
Some(prefix_component) => {
|
||||
canonical_path = PathBuf::from(prefix_component.as_os_str());
|
||||
// Prefixes like `C:\\` are represented without their trailing slash, so we have to re-add it.
|
||||
canonical_path.push(std::path::MAIN_SEPARATOR_STR);
|
||||
}
|
||||
None => canonical_path = PathBuf::from(std::path::MAIN_SEPARATOR_STR),
|
||||
}
|
||||
}
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => {
|
||||
@@ -894,7 +903,7 @@ impl FakeFsState {
|
||||
}
|
||||
}
|
||||
entry_stack.push(entry.clone());
|
||||
canonical_path.push(name);
|
||||
canonical_path = canonical_path.join(name);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
@@ -956,9 +965,15 @@ pub static FS_DOT_GIT: std::sync::LazyLock<&'static OsStr> =
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl FakeFs {
|
||||
/// We need to use something large enough for Windows and Unix to consider this a new file.
|
||||
/// https://doc.rust-lang.org/nightly/std/time/struct.SystemTime.html#platform-specific-behavior
|
||||
const SYSTEMTIME_INTERVAL: u64 = 100;
|
||||
|
||||
pub fn new(executor: gpui::BackgroundExecutor) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
executor,
|
||||
let (tx, mut rx) = smol::channel::bounded::<PathBuf>(10);
|
||||
|
||||
let this = Arc::new(Self {
|
||||
executor: executor.clone(),
|
||||
state: Mutex::new(FakeFsState {
|
||||
root: Arc::new(Mutex::new(FakeFsEntry::Dir {
|
||||
inode: 0,
|
||||
@@ -967,6 +982,7 @@ impl FakeFs {
|
||||
entries: Default::default(),
|
||||
git_repo_state: None,
|
||||
})),
|
||||
git_event_tx: tx,
|
||||
next_mtime: SystemTime::UNIX_EPOCH,
|
||||
next_inode: 1,
|
||||
event_txs: Default::default(),
|
||||
@@ -975,7 +991,22 @@ impl FakeFs {
|
||||
read_dir_call_count: 0,
|
||||
metadata_call_count: 0,
|
||||
}),
|
||||
})
|
||||
});
|
||||
|
||||
executor.spawn({
|
||||
let this = this.clone();
|
||||
async move {
|
||||
while let Some(git_event) = rx.next().await {
|
||||
if let Some(mut state) = this.state.try_lock() {
|
||||
state.emit_event([(git_event, None)]);
|
||||
} else {
|
||||
panic!("Failed to lock file system state, this execution would have caused a test hang");
|
||||
}
|
||||
}
|
||||
}
|
||||
}).detach();
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub fn set_next_mtime(&self, next_mtime: SystemTime) {
|
||||
@@ -989,7 +1020,7 @@ impl FakeFs {
|
||||
let new_mtime = state.next_mtime;
|
||||
let new_inode = state.next_inode;
|
||||
state.next_inode += 1;
|
||||
state.next_mtime += Duration::from_nanos(1);
|
||||
state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL);
|
||||
state
|
||||
.write_path(path, move |entry| {
|
||||
match entry {
|
||||
@@ -1042,7 +1073,7 @@ impl FakeFs {
|
||||
let inode = state.next_inode;
|
||||
let mtime = state.next_mtime;
|
||||
state.next_inode += 1;
|
||||
state.next_mtime += Duration::from_nanos(1);
|
||||
state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL);
|
||||
let file = Arc::new(Mutex::new(FakeFsEntry::File {
|
||||
inode,
|
||||
mtime,
|
||||
@@ -1169,7 +1200,12 @@ impl FakeFs {
|
||||
let mut entry = entry.lock();
|
||||
|
||||
if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
|
||||
let repo_state = git_repo_state.get_or_insert_with(Default::default);
|
||||
let repo_state = git_repo_state.get_or_insert_with(|| {
|
||||
Arc::new(Mutex::new(FakeGitRepositoryState::new(
|
||||
dot_git.to_path_buf(),
|
||||
state.git_event_tx.clone(),
|
||||
)))
|
||||
});
|
||||
let mut repo_state = repo_state.lock();
|
||||
|
||||
f(&mut repo_state);
|
||||
@@ -1184,7 +1220,22 @@ impl FakeFs {
|
||||
|
||||
pub fn set_branch_name(&self, dot_git: &Path, branch: Option<impl Into<String>>) {
|
||||
self.with_git_state(dot_git, true, |state| {
|
||||
state.branch_name = branch.map(Into::into)
|
||||
let branch = branch.map(Into::into);
|
||||
state.branches.extend(branch.clone());
|
||||
state.current_branch_name = branch.map(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert_branches(&self, dot_git: &Path, branches: &[&str]) {
|
||||
self.with_git_state(dot_git, true, |state| {
|
||||
if let Some(first) = branches.first() {
|
||||
if state.current_branch_name.is_none() {
|
||||
state.current_branch_name = Some(first.to_string())
|
||||
}
|
||||
}
|
||||
state
|
||||
.branches
|
||||
.extend(branches.iter().map(ToString::to_string));
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1384,15 +1435,16 @@ impl Fs for FakeFs {
|
||||
let mut created_dirs = Vec::new();
|
||||
let mut cur_path = PathBuf::new();
|
||||
for component in path.components() {
|
||||
let mut state = self.state.lock();
|
||||
let should_skip = matches!(component, Component::Prefix(..) | Component::RootDir);
|
||||
cur_path.push(component);
|
||||
if cur_path == Path::new("/") {
|
||||
if should_skip {
|
||||
continue;
|
||||
}
|
||||
let mut state = self.state.lock();
|
||||
|
||||
let inode = state.next_inode;
|
||||
let mtime = state.next_mtime;
|
||||
state.next_mtime += Duration::from_nanos(1);
|
||||
state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL);
|
||||
state.next_inode += 1;
|
||||
state.write_path(&cur_path, |entry| {
|
||||
entry.or_insert_with(|| {
|
||||
@@ -1418,7 +1470,7 @@ impl Fs for FakeFs {
|
||||
let mut state = self.state.lock();
|
||||
let inode = state.next_inode;
|
||||
let mtime = state.next_mtime;
|
||||
state.next_mtime += Duration::from_nanos(1);
|
||||
state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL);
|
||||
state.next_inode += 1;
|
||||
let file = Arc::new(Mutex::new(FakeFsEntry::File {
|
||||
inode,
|
||||
@@ -1553,7 +1605,7 @@ impl Fs for FakeFs {
|
||||
let mut state = self.state.lock();
|
||||
let mtime = state.next_mtime;
|
||||
let inode = util::post_inc(&mut state.next_inode);
|
||||
state.next_mtime += Duration::from_nanos(1);
|
||||
state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL);
|
||||
let source_entry = state.read_path(&source)?;
|
||||
let content = source_entry.lock().file_content(&source)?.clone();
|
||||
let mut kind = Some(PathEventKind::Created);
|
||||
@@ -1823,7 +1875,12 @@ impl Fs for FakeFs {
|
||||
let mut entry = entry.lock();
|
||||
if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
|
||||
let state = git_repo_state
|
||||
.get_or_insert_with(|| Arc::new(Mutex::new(FakeGitRepositoryState::default())))
|
||||
.get_or_insert_with(|| {
|
||||
Arc::new(Mutex::new(FakeGitRepositoryState::new(
|
||||
abs_dot_git.to_path_buf(),
|
||||
state.git_event_tx.clone(),
|
||||
)))
|
||||
})
|
||||
.clone();
|
||||
Some(git::repository::FakeGitRepository::open(state))
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::GitHostingProviderRegistry;
|
||||
use crate::{blame::Blame, status::GitStatus};
|
||||
use anyhow::{Context, Result};
|
||||
use collections::HashMap;
|
||||
use collections::{HashMap, HashSet};
|
||||
use git2::BranchType;
|
||||
use gpui::SharedString;
|
||||
use parking_lot::Mutex;
|
||||
use rope::Rope;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -17,7 +18,7 @@ use util::ResultExt;
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
pub struct Branch {
|
||||
pub is_head: bool,
|
||||
pub name: Box<str>,
|
||||
pub name: SharedString,
|
||||
/// Timestamp of most recent commit, normalized to Unix Epoch format.
|
||||
pub unix_timestamp: Option<i64>,
|
||||
}
|
||||
@@ -41,6 +42,7 @@ pub trait GitRepository: Send + Sync {
|
||||
fn branches(&self) -> Result<Vec<Branch>>;
|
||||
fn change_branch(&self, _: &str) -> Result<()>;
|
||||
fn create_branch(&self, _: &str) -> Result<()>;
|
||||
fn branch_exits(&self, _: &str) -> Result<bool>;
|
||||
|
||||
fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame>;
|
||||
}
|
||||
@@ -132,6 +134,18 @@ impl GitRepository for RealGitRepository {
|
||||
GitStatus::new(&self.git_binary_path, &working_directory, path_prefixes)
|
||||
}
|
||||
|
||||
fn branch_exits(&self, name: &str) -> Result<bool> {
|
||||
let repo = self.repository.lock();
|
||||
let branch = repo.find_branch(name, BranchType::Local);
|
||||
match branch {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) => match e.code() {
|
||||
git2::ErrorCode::NotFound => Ok(false),
|
||||
_ => Err(anyhow::anyhow!(e)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn branches(&self) -> Result<Vec<Branch>> {
|
||||
let repo = self.repository.lock();
|
||||
let local_branches = repo.branches(Some(BranchType::Local))?;
|
||||
@@ -139,7 +153,11 @@ impl GitRepository for RealGitRepository {
|
||||
.filter_map(|branch| {
|
||||
branch.ok().and_then(|(branch, _)| {
|
||||
let is_head = branch.is_head();
|
||||
let name = branch.name().ok().flatten().map(Box::from)?;
|
||||
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();
|
||||
@@ -201,17 +219,20 @@ impl GitRepository for RealGitRepository {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FakeGitRepository {
|
||||
state: Arc<Mutex<FakeGitRepositoryState>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FakeGitRepositoryState {
|
||||
pub path: PathBuf,
|
||||
pub event_emitter: smol::channel::Sender<PathBuf>,
|
||||
pub index_contents: HashMap<PathBuf, String>,
|
||||
pub blames: HashMap<PathBuf, Blame>,
|
||||
pub worktree_statuses: HashMap<RepoPath, GitFileStatus>,
|
||||
pub branch_name: Option<String>,
|
||||
pub current_branch_name: Option<String>,
|
||||
pub branches: HashSet<String>,
|
||||
}
|
||||
|
||||
impl FakeGitRepository {
|
||||
@@ -220,6 +241,20 @@ impl FakeGitRepository {
|
||||
}
|
||||
}
|
||||
|
||||
impl FakeGitRepositoryState {
|
||||
pub fn new(path: PathBuf, event_emitter: smol::channel::Sender<PathBuf>) -> Self {
|
||||
FakeGitRepositoryState {
|
||||
path,
|
||||
event_emitter,
|
||||
index_contents: Default::default(),
|
||||
blames: Default::default(),
|
||||
worktree_statuses: Default::default(),
|
||||
current_branch_name: Default::default(),
|
||||
branches: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GitRepository for FakeGitRepository {
|
||||
fn reload_index(&self) {}
|
||||
|
||||
@@ -234,7 +269,7 @@ impl GitRepository for FakeGitRepository {
|
||||
|
||||
fn branch_name(&self) -> Option<String> {
|
||||
let state = self.state.lock();
|
||||
state.branch_name.clone()
|
||||
state.current_branch_name.clone()
|
||||
}
|
||||
|
||||
fn head_sha(&self) -> Option<String> {
|
||||
@@ -264,18 +299,41 @@ impl GitRepository for FakeGitRepository {
|
||||
}
|
||||
|
||||
fn branches(&self) -> Result<Vec<Branch>> {
|
||||
Ok(vec![])
|
||||
let state = self.state.lock();
|
||||
let current_branch = &state.current_branch_name;
|
||||
Ok(state
|
||||
.branches
|
||||
.iter()
|
||||
.map(|branch_name| Branch {
|
||||
is_head: Some(branch_name) == current_branch.as_ref(),
|
||||
name: branch_name.into(),
|
||||
unix_timestamp: None,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn branch_exits(&self, name: &str) -> Result<bool> {
|
||||
let state = self.state.lock();
|
||||
Ok(state.branches.contains(name))
|
||||
}
|
||||
|
||||
fn change_branch(&self, name: &str) -> Result<()> {
|
||||
let mut state = self.state.lock();
|
||||
state.branch_name = Some(name.to_owned());
|
||||
state.current_branch_name = Some(name.to_owned());
|
||||
state
|
||||
.event_emitter
|
||||
.try_send(state.path.clone())
|
||||
.expect("Dropped repo change event");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_branch(&self, name: &str) -> Result<()> {
|
||||
let mut state = self.state.lock();
|
||||
state.branch_name = Some(name.to_owned());
|
||||
state.branches.insert(name.to_owned());
|
||||
state
|
||||
.event_emitter
|
||||
.try_send(state.path.clone())
|
||||
.expect("Dropped repo change event");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -37,34 +37,34 @@ impl CursorPosition {
|
||||
}
|
||||
|
||||
fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
|
||||
let editor = editor.read(cx);
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
self.selected_count = Default::default();
|
||||
self.selected_count.selections = editor.selections.count();
|
||||
let mut last_selection: Option<Selection<usize>> = None;
|
||||
for selection in editor.selections.all::<usize>(cx) {
|
||||
self.selected_count.characters += buffer
|
||||
.text_for_range(selection.start..selection.end)
|
||||
.map(|t| t.chars().count())
|
||||
.sum::<usize>();
|
||||
if last_selection
|
||||
.as_ref()
|
||||
.map_or(true, |last_selection| selection.id > last_selection.id)
|
||||
{
|
||||
last_selection = Some(selection);
|
||||
}
|
||||
}
|
||||
for selection in editor.selections.all::<Point>(cx) {
|
||||
if selection.end != selection.start {
|
||||
self.selected_count.lines += (selection.end.row - selection.start.row) as usize;
|
||||
if selection.end.column != 0 {
|
||||
self.selected_count.lines += 1;
|
||||
self.selected_count = Default::default();
|
||||
self.selected_count.selections = editor.selections.count();
|
||||
let mut last_selection: Option<Selection<usize>> = None;
|
||||
for selection in editor.selections.all::<usize>(cx) {
|
||||
self.selected_count.characters += buffer
|
||||
.text_for_range(selection.start..selection.end)
|
||||
.map(|t| t.chars().count())
|
||||
.sum::<usize>();
|
||||
if last_selection
|
||||
.as_ref()
|
||||
.map_or(true, |last_selection| selection.id > last_selection.id)
|
||||
{
|
||||
last_selection = Some(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.position = last_selection.map(|s| s.head().to_point(&buffer));
|
||||
|
||||
for selection in editor.selections.all::<Point>(cx) {
|
||||
if selection.end != selection.start {
|
||||
self.selected_count.lines += (selection.end.row - selection.start.row) as usize;
|
||||
if selection.end.column != 0 {
|
||||
self.selected_count.lines += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.position = last_selection.map(|s| s.head().to_point(&buffer));
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,8 @@ impl GoToLine {
|
||||
}
|
||||
|
||||
pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let editor = active_editor.read(cx);
|
||||
let cursor = editor.selections.last::<Point>(cx).head();
|
||||
let cursor =
|
||||
active_editor.update(cx, |editor, cx| editor.selections.last::<Point>(cx).head());
|
||||
|
||||
let line = cursor.row + 1;
|
||||
let column = cursor.column + 1;
|
||||
|
||||
@@ -256,6 +256,9 @@ pub struct AppContext {
|
||||
pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
|
||||
pub(crate) propagate_event: bool,
|
||||
pub(crate) prompt_builder: Option<PromptBuilder>,
|
||||
|
||||
#[cfg(any(test, feature = "test-support", debug_assertions))]
|
||||
pub(crate) name: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl AppContext {
|
||||
@@ -309,6 +312,9 @@ impl AppContext {
|
||||
layout_id_buffer: Default::default(),
|
||||
propagate_event: true,
|
||||
prompt_builder: Some(PromptBuilder::Default),
|
||||
|
||||
#[cfg(any(test, feature = "test-support", debug_assertions))]
|
||||
name: None,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -988,6 +994,7 @@ impl AppContext {
|
||||
}
|
||||
|
||||
/// Move the global of the given type to the stack.
|
||||
#[track_caller]
|
||||
pub(crate) fn lease_global<G: Global>(&mut self) -> GlobalLease<G> {
|
||||
GlobalLease::new(
|
||||
self.globals_by_type
|
||||
@@ -1319,6 +1326,12 @@ impl AppContext {
|
||||
|
||||
(task, is_first)
|
||||
}
|
||||
|
||||
/// Get the name for this App.
|
||||
#[cfg(any(test, feature = "test-support", debug_assertions))]
|
||||
pub fn get_name(&self) -> &'static str {
|
||||
self.name.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AppContext {
|
||||
|
||||
@@ -536,6 +536,15 @@ impl AnyWeakModel {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AnyWeakModel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct(type_name::<Self>())
|
||||
.field("entity_id", &self.entity_id)
|
||||
.field("entity_type", &self.entity_type)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<WeakModel<T>> for AnyWeakModel {
|
||||
fn from(model: WeakModel<T>) -> Self {
|
||||
model.any_model
|
||||
|
||||
@@ -478,6 +478,12 @@ impl TestAppContext {
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Set a name for this App.
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn set_name(&mut self, name: &'static str) {
|
||||
self.update(|cx| cx.name = Some(name))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Model<T> {
|
||||
|
||||
@@ -340,6 +340,7 @@ impl Element for UniformList {
|
||||
visible_range.clone(),
|
||||
bounds,
|
||||
item_height,
|
||||
self.item_count,
|
||||
cx,
|
||||
);
|
||||
let available_space = size(
|
||||
@@ -396,6 +397,7 @@ pub trait UniformListDecoration {
|
||||
visible_range: Range<usize>,
|
||||
bounds: Bounds<Pixels>,
|
||||
item_height: Pixels,
|
||||
item_count: usize,
|
||||
cx: &mut WindowContext,
|
||||
) -> AnyElement;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ pub trait UpdateGlobal {
|
||||
}
|
||||
|
||||
impl<T: Global> UpdateGlobal for T {
|
||||
#[track_caller]
|
||||
fn update_global<C, F, R>(cx: &mut C, update: F) -> R
|
||||
where
|
||||
C: BorrowAppContext,
|
||||
|
||||
@@ -306,6 +306,7 @@ where
|
||||
self.borrow_mut().set_global(global)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
|
||||
where
|
||||
G: Global,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::{
|
||||
black, fill, point, px, size, Bounds, Half, Hsla, LineLayout, Pixels, Point, Result,
|
||||
SharedString, StrikethroughStyle, UnderlineStyle, WindowContext, WrapBoundary,
|
||||
WrappedLineLayout,
|
||||
black, fill, point, px, size, Bounds, Hsla, LineLayout, Pixels, Point, Result, SharedString,
|
||||
StrikethroughStyle, UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use smallvec::SmallVec;
|
||||
@@ -130,9 +129,8 @@ fn paint_line(
|
||||
let text_system = cx.text_system().clone();
|
||||
let mut glyph_origin = origin;
|
||||
let mut prev_glyph_position = Point::default();
|
||||
let mut max_glyph_size = size(px(0.), px(0.));
|
||||
for (run_ix, run) in layout.runs.iter().enumerate() {
|
||||
max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
|
||||
let max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
|
||||
|
||||
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
|
||||
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
|
||||
@@ -141,9 +139,6 @@ fn paint_line(
|
||||
wraps.next();
|
||||
if let Some((background_origin, background_color)) = current_background.as_mut()
|
||||
{
|
||||
if glyph_origin.x == background_origin.x {
|
||||
background_origin.x -= max_glyph_size.width.half()
|
||||
}
|
||||
cx.paint_quad(fill(
|
||||
Bounds {
|
||||
origin: *background_origin,
|
||||
@@ -155,9 +150,6 @@ fn paint_line(
|
||||
background_origin.y += line_height;
|
||||
}
|
||||
if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
|
||||
if glyph_origin.x == underline_origin.x {
|
||||
underline_origin.x -= max_glyph_size.width.half();
|
||||
};
|
||||
cx.paint_underline(
|
||||
*underline_origin,
|
||||
glyph_origin.x - underline_origin.x,
|
||||
@@ -169,9 +161,6 @@ fn paint_line(
|
||||
if let Some((strikethrough_origin, strikethrough_style)) =
|
||||
current_strikethrough.as_mut()
|
||||
{
|
||||
if glyph_origin.x == strikethrough_origin.x {
|
||||
strikethrough_origin.x -= max_glyph_size.width.half();
|
||||
};
|
||||
cx.paint_strikethrough(
|
||||
*strikethrough_origin,
|
||||
glyph_origin.x - strikethrough_origin.x,
|
||||
@@ -190,18 +179,7 @@ fn paint_line(
|
||||
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||
let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
|
||||
if glyph.index >= run_end {
|
||||
let mut style_run = decoration_runs.next();
|
||||
|
||||
// ignore style runs that apply to a partial glyph
|
||||
while let Some(run) = style_run {
|
||||
if glyph.index < run_end + (run.len as usize) {
|
||||
break;
|
||||
}
|
||||
run_end += run.len as usize;
|
||||
style_run = decoration_runs.next();
|
||||
}
|
||||
|
||||
if let Some(style_run) = style_run {
|
||||
if let Some(style_run) = decoration_runs.next() {
|
||||
if let Some((_, background_color)) = &mut current_background {
|
||||
if style_run.background_color.as_ref() != Some(background_color) {
|
||||
finished_background = current_background.take();
|
||||
@@ -262,14 +240,10 @@ fn paint_line(
|
||||
}
|
||||
|
||||
if let Some((background_origin, background_color)) = finished_background {
|
||||
let mut width = glyph_origin.x - background_origin.x;
|
||||
if width == px(0.) {
|
||||
width = px(5.)
|
||||
};
|
||||
cx.paint_quad(fill(
|
||||
Bounds {
|
||||
origin: background_origin,
|
||||
size: size(width, line_height),
|
||||
size: size(glyph_origin.x - background_origin.x, line_height),
|
||||
},
|
||||
background_color,
|
||||
));
|
||||
@@ -325,10 +299,7 @@ fn paint_line(
|
||||
last_line_end_x -= glyph.position.x;
|
||||
}
|
||||
|
||||
if let Some((mut background_origin, background_color)) = current_background.take() {
|
||||
if last_line_end_x == background_origin.x {
|
||||
background_origin.x -= max_glyph_size.width.half()
|
||||
};
|
||||
if let Some((background_origin, background_color)) = current_background.take() {
|
||||
cx.paint_quad(fill(
|
||||
Bounds {
|
||||
origin: background_origin,
|
||||
@@ -338,10 +309,7 @@ fn paint_line(
|
||||
));
|
||||
}
|
||||
|
||||
if let Some((mut underline_start, underline_style)) = current_underline.take() {
|
||||
if last_line_end_x == underline_start.x {
|
||||
underline_start.x -= max_glyph_size.width.half()
|
||||
};
|
||||
if let Some((underline_start, underline_style)) = current_underline.take() {
|
||||
cx.paint_underline(
|
||||
underline_start,
|
||||
last_line_end_x - underline_start.x,
|
||||
@@ -349,10 +317,7 @@ fn paint_line(
|
||||
);
|
||||
}
|
||||
|
||||
if let Some((mut strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
|
||||
if last_line_end_x == strikethrough_start.x {
|
||||
strikethrough_start.x -= max_glyph_size.width.half()
|
||||
};
|
||||
if let Some((strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
|
||||
cx.paint_strikethrough(
|
||||
strikethrough_start,
|
||||
last_line_end_x - strikethrough_start.x,
|
||||
|
||||
@@ -501,8 +501,6 @@ pub struct Chunk<'a> {
|
||||
pub is_unnecessary: bool,
|
||||
/// Whether this chunk of text was originally a tab character.
|
||||
pub is_tab: bool,
|
||||
/// Whether this chunk of text is an invisible character.
|
||||
pub is_invisible: bool,
|
||||
/// An optional recipe for how the chunk should be presented.
|
||||
pub renderer: Option<ChunkRenderer>,
|
||||
}
|
||||
@@ -1969,18 +1967,27 @@ impl Buffer {
|
||||
let new_text_length = new_text.len();
|
||||
let old_start = range.start.to_point(&before_edit);
|
||||
let new_start = (delta + range.start as isize) as usize;
|
||||
delta += new_text_length as isize - (range.end as isize - range.start as isize);
|
||||
let range_len = range.end - range.start;
|
||||
delta += new_text_length as isize - range_len as isize;
|
||||
|
||||
// Decide what range of the insertion to auto-indent, and whether
|
||||
// the first line of the insertion should be considered a newly-inserted line
|
||||
// or an edit to an existing line.
|
||||
let mut range_of_insertion_to_indent = 0..new_text_length;
|
||||
let mut first_line_is_new = false;
|
||||
let mut original_indent_column = None;
|
||||
let mut first_line_is_new = true;
|
||||
|
||||
// When inserting an entire line at the beginning of an existing line,
|
||||
// treat the insertion as new.
|
||||
if new_text.contains('\n')
|
||||
&& old_start.column <= before_edit.indent_size_for_line(old_start.row).len
|
||||
let old_line_start = before_edit.indent_size_for_line(old_start.row).len;
|
||||
let old_line_end = before_edit.line_len(old_start.row);
|
||||
|
||||
if old_start.column > old_line_start {
|
||||
first_line_is_new = false;
|
||||
}
|
||||
|
||||
if !new_text.contains('\n')
|
||||
&& (old_start.column + (range_len as u32) < old_line_end
|
||||
|| old_line_end == old_line_start)
|
||||
{
|
||||
first_line_is_new = true;
|
||||
first_line_is_new = false;
|
||||
}
|
||||
|
||||
// When inserting text starting with a newline, avoid auto-indenting the
|
||||
@@ -1990,7 +1997,7 @@ impl Buffer {
|
||||
first_line_is_new = true;
|
||||
}
|
||||
|
||||
// Avoid auto-indenting after the insertion.
|
||||
let mut original_indent_column = None;
|
||||
if let AutoindentMode::Block {
|
||||
original_indent_columns,
|
||||
} = &mode
|
||||
@@ -2002,6 +2009,8 @@ impl Buffer {
|
||||
)
|
||||
.len
|
||||
}));
|
||||
|
||||
// Avoid auto-indenting the line after the edit.
|
||||
if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
|
||||
range_of_insertion_to_indent.end -= 1;
|
||||
}
|
||||
@@ -4037,7 +4046,7 @@ impl<'a> BufferChunks<'a> {
|
||||
let old_range = std::mem::replace(&mut self.range, range.clone());
|
||||
self.chunks.set_range(self.range.clone());
|
||||
if let Some(highlights) = self.highlights.as_mut() {
|
||||
if old_range.start >= self.range.start && old_range.end <= self.range.end {
|
||||
if old_range.start <= self.range.start && old_range.end >= self.range.end {
|
||||
// Reuse existing highlights stack, as the new range is a subrange of the old one.
|
||||
highlights
|
||||
.stack
|
||||
@@ -4213,6 +4222,7 @@ impl<'a> Iterator for BufferChunks<'a> {
|
||||
if self.range.start == self.chunks.offset() + chunk.len() {
|
||||
self.chunks.next().unwrap();
|
||||
}
|
||||
|
||||
Some(Chunk {
|
||||
text: slice,
|
||||
syntax_highlight_id: highlight_id,
|
||||
|
||||
@@ -1241,7 +1241,6 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
|
||||
Some(AutoindentMode::EachLine),
|
||||
cx,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"
|
||||
@@ -1256,6 +1255,74 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
|
||||
"
|
||||
.unindent()
|
||||
);
|
||||
|
||||
// Insert a newline after the open brace. It is auto-indented
|
||||
buffer.edit_via_marked_text(
|
||||
&"
|
||||
fn a() {«
|
||||
»
|
||||
c
|
||||
.f
|
||||
.g();
|
||||
d
|
||||
.f
|
||||
.g();
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
Some(AutoindentMode::EachLine),
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"
|
||||
fn a() {
|
||||
ˇ
|
||||
c
|
||||
.f
|
||||
.g();
|
||||
d
|
||||
.f
|
||||
.g();
|
||||
}
|
||||
"
|
||||
.unindent()
|
||||
.replace("ˇ", "")
|
||||
);
|
||||
|
||||
// Manually outdent the line. It stays outdented.
|
||||
buffer.edit_via_marked_text(
|
||||
&"
|
||||
fn a() {
|
||||
«»
|
||||
c
|
||||
.f
|
||||
.g();
|
||||
d
|
||||
.f
|
||||
.g();
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
Some(AutoindentMode::EachLine),
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"
|
||||
fn a() {
|
||||
|
||||
c
|
||||
.f
|
||||
.g();
|
||||
d
|
||||
.f
|
||||
.g();
|
||||
}
|
||||
"
|
||||
.unindent()
|
||||
);
|
||||
|
||||
buffer
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ mod outline;
|
||||
pub mod proto;
|
||||
mod syntax_map;
|
||||
mod task_context;
|
||||
mod toolchain;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod buffer_tests;
|
||||
@@ -28,7 +29,7 @@ use futures::Future;
|
||||
use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task};
|
||||
pub use highlight_map::HighlightMap;
|
||||
use http_client::HttpClient;
|
||||
pub use language_registry::LanguageName;
|
||||
pub use language_registry::{LanguageName, LoadedLanguage};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions};
|
||||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
@@ -61,6 +62,7 @@ use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
|
||||
use task::RunnableTag;
|
||||
pub use task_context::{ContextProvider, RunnableRange};
|
||||
use theme::SyntaxTheme;
|
||||
pub use toolchain::{LanguageToolchainStore, Toolchain, ToolchainList, ToolchainLister};
|
||||
use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore};
|
||||
use util::serde::default_true;
|
||||
|
||||
@@ -502,6 +504,7 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Arc<dyn LanguageToolchainStore>,
|
||||
_cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
Ok(serde_json::json!({}))
|
||||
@@ -855,6 +858,7 @@ pub struct Language {
|
||||
pub(crate) config: LanguageConfig,
|
||||
pub(crate) grammar: Option<Arc<Grammar>>,
|
||||
pub(crate) context_provider: Option<Arc<dyn ContextProvider>>,
|
||||
pub(crate) toolchain: Option<Arc<dyn ToolchainLister>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
@@ -983,6 +987,7 @@ impl Language {
|
||||
})
|
||||
}),
|
||||
context_provider: None,
|
||||
toolchain: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -991,6 +996,11 @@ impl Language {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_toolchain_lister(mut self, provider: Option<Arc<dyn ToolchainLister>>) -> Self {
|
||||
self.toolchain = provider;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> {
|
||||
if let Some(query) = queries.highlights {
|
||||
self = self
|
||||
@@ -1361,6 +1371,10 @@ impl Language {
|
||||
self.context_provider.clone()
|
||||
}
|
||||
|
||||
pub fn toolchain_lister(&self) -> Option<Arc<dyn ToolchainLister>> {
|
||||
self.toolchain.clone()
|
||||
}
|
||||
|
||||
pub fn highlight_text<'a>(
|
||||
self: &'a Arc<Self>,
|
||||
text: &'a Rope,
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
},
|
||||
task_context::ContextProvider,
|
||||
with_parser, CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher,
|
||||
LanguageServerName, LspAdapter, PLAIN_TEXT,
|
||||
LanguageServerName, LspAdapter, ToolchainLister, PLAIN_TEXT,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
@@ -75,6 +75,13 @@ impl<'a> From<&'a str> for LanguageName {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LanguageName> for String {
|
||||
fn from(value: LanguageName) -> Self {
|
||||
let value: &str = &value.0;
|
||||
Self::from(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LanguageRegistry {
|
||||
state: RwLock<LanguageRegistryState>,
|
||||
language_server_download_dir: Option<Arc<Path>>,
|
||||
@@ -123,16 +130,7 @@ pub struct AvailableLanguage {
|
||||
name: LanguageName,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
load: Arc<
|
||||
dyn Fn() -> Result<(
|
||||
LanguageConfig,
|
||||
LanguageQueries,
|
||||
Option<Arc<dyn ContextProvider>>,
|
||||
)>
|
||||
+ 'static
|
||||
+ Send
|
||||
+ Sync,
|
||||
>,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
|
||||
loaded: bool,
|
||||
}
|
||||
|
||||
@@ -200,6 +198,13 @@ struct LspBinaryStatusSender {
|
||||
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(LanguageServerName, LanguageServerBinaryStatus)>>>>,
|
||||
}
|
||||
|
||||
pub struct LoadedLanguage {
|
||||
pub config: LanguageConfig,
|
||||
pub queries: LanguageQueries,
|
||||
pub context_provider: Option<Arc<dyn ContextProvider>>,
|
||||
pub toolchain_provider: Option<Arc<dyn ToolchainLister>>,
|
||||
}
|
||||
|
||||
impl LanguageRegistry {
|
||||
pub fn new(executor: BackgroundExecutor) -> Self {
|
||||
let this = Self {
|
||||
@@ -283,7 +288,14 @@ impl LanguageRegistry {
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
move || Ok((config.clone(), Default::default(), None)),
|
||||
move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
queries: Default::default(),
|
||||
toolchain_provider: None,
|
||||
context_provider: None,
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -424,14 +436,7 @@ impl LanguageRegistry {
|
||||
name: LanguageName,
|
||||
grammar_name: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
load: impl Fn() -> Result<(
|
||||
LanguageConfig,
|
||||
LanguageQueries,
|
||||
Option<Arc<dyn ContextProvider>>,
|
||||
)>
|
||||
+ 'static
|
||||
+ Send
|
||||
+ Sync,
|
||||
load: impl Fn() -> Result<LoadedLanguage> + 'static + Send + Sync,
|
||||
) {
|
||||
let load = Arc::new(load);
|
||||
let state = &mut *self.state.write();
|
||||
@@ -726,16 +731,18 @@ impl LanguageRegistry {
|
||||
self.executor
|
||||
.spawn(async move {
|
||||
let language = async {
|
||||
let (config, queries, provider) = (language_load)()?;
|
||||
|
||||
if let Some(grammar) = config.grammar.clone() {
|
||||
let loaded_language = (language_load)()?;
|
||||
if let Some(grammar) = loaded_language.config.grammar.clone() {
|
||||
let grammar = Some(this.get_or_load_grammar(grammar).await?);
|
||||
Language::new_with_id(id, config, grammar)
|
||||
.with_context_provider(provider)
|
||||
.with_queries(queries)
|
||||
|
||||
Language::new_with_id(id, loaded_language.config, grammar)
|
||||
.with_context_provider(loaded_language.context_provider)
|
||||
.with_toolchain_lister(loaded_language.toolchain_provider)
|
||||
.with_queries(loaded_language.queries)
|
||||
} else {
|
||||
Ok(Language::new_with_id(id, config, None)
|
||||
.with_context_provider(provider))
|
||||
Ok(Language::new_with_id(id, loaded_language.config, None)
|
||||
.with_context_provider(loaded_language.context_provider)
|
||||
.with_toolchain_lister(loaded_language.toolchain_provider))
|
||||
}
|
||||
}
|
||||
.await;
|
||||
|
||||
65
crates/language/src/toolchain.rs
Normal file
65
crates/language/src/toolchain.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
//! Provides support for language toolchains.
|
||||
//!
|
||||
//! A language can have associated toolchains,
|
||||
//! which is a set of tools used to interact with the projects written in said language.
|
||||
//! For example, a Python project can have an associated virtual environment; a Rust project can have a toolchain override.
|
||||
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use gpui::{AsyncAppContext, SharedString};
|
||||
use settings::WorktreeId;
|
||||
|
||||
use crate::LanguageName;
|
||||
|
||||
/// Represents a single toolchain.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Toolchain {
|
||||
/// User-facing label
|
||||
pub name: SharedString,
|
||||
pub path: SharedString,
|
||||
pub language_name: LanguageName,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait ToolchainLister: Send + Sync {
|
||||
async fn list(&self, _: PathBuf) -> ToolchainList;
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait LanguageToolchainStore {
|
||||
async fn active_toolchain(
|
||||
self: Arc<Self>,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<Toolchain>;
|
||||
}
|
||||
|
||||
type DefaultIndex = usize;
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ToolchainList {
|
||||
pub toolchains: Vec<Toolchain>,
|
||||
pub default: Option<DefaultIndex>,
|
||||
pub groups: Box<[(usize, SharedString)]>,
|
||||
}
|
||||
|
||||
impl ToolchainList {
|
||||
pub fn toolchains(&self) -> &[Toolchain] {
|
||||
&self.toolchains
|
||||
}
|
||||
pub fn default_toolchain(&self) -> Option<Toolchain> {
|
||||
self.default.and_then(|ix| self.toolchains.get(ix)).cloned()
|
||||
}
|
||||
pub fn group_for_index(&self, index: usize) -> Option<(usize, SharedString)> {
|
||||
if index >= self.toolchains.len() {
|
||||
return None;
|
||||
}
|
||||
let first_equal_or_greater = self
|
||||
.groups
|
||||
.partition_point(|(group_lower_bound, _)| group_lower_bound <= &index);
|
||||
self.groups
|
||||
.get(first_equal_or_greater.checked_sub(1)?)
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
@@ -505,10 +505,14 @@ pub fn map_to_language_model_completion_events(
|
||||
LanguageModelToolUse {
|
||||
id: tool_use.id,
|
||||
name: tool_use.name,
|
||||
input: serde_json::Value::from_str(
|
||||
&tool_use.input_json,
|
||||
)
|
||||
.map_err(|err| anyhow!(err))?,
|
||||
input: if tool_use.input_json.is_empty() {
|
||||
serde_json::Value::Null
|
||||
} else {
|
||||
serde_json::Value::from_str(
|
||||
&tool_use.input_json,
|
||||
)
|
||||
.map_err(|err| anyhow!(err))?
|
||||
},
|
||||
},
|
||||
))
|
||||
})),
|
||||
|
||||
@@ -54,6 +54,7 @@ pub struct OllamaLanguageModelProvider {
|
||||
pub struct State {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
available_models: Vec<ollama::Model>,
|
||||
fetch_model_task: Option<Task<Result<()>>>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
@@ -89,6 +90,11 @@ impl State {
|
||||
})
|
||||
}
|
||||
|
||||
fn restart_fetch_models_task(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let task = self.fetch_models(cx);
|
||||
self.fetch_model_task.replace(task);
|
||||
}
|
||||
|
||||
fn authenticate(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.is_authenticated() {
|
||||
Task::ready(Ok(()))
|
||||
@@ -102,17 +108,29 @@ impl OllamaLanguageModelProvider {
|
||||
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Self {
|
||||
let this = Self {
|
||||
http_client: http_client.clone(),
|
||||
state: cx.new_model(|cx| State {
|
||||
http_client,
|
||||
available_models: Default::default(),
|
||||
_subscription: cx.observe_global::<SettingsStore>(|this: &mut State, cx| {
|
||||
this.fetch_models(cx).detach();
|
||||
cx.notify();
|
||||
}),
|
||||
state: cx.new_model(|cx| {
|
||||
let subscription = cx.observe_global::<SettingsStore>({
|
||||
let mut settings = AllLanguageModelSettings::get_global(cx).ollama.clone();
|
||||
move |this: &mut State, cx| {
|
||||
let new_settings = &AllLanguageModelSettings::get_global(cx).ollama;
|
||||
if &settings != new_settings {
|
||||
settings = new_settings.clone();
|
||||
this.restart_fetch_models_task(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
State {
|
||||
http_client,
|
||||
available_models: Default::default(),
|
||||
fetch_model_task: None,
|
||||
_subscription: subscription,
|
||||
}
|
||||
}),
|
||||
};
|
||||
this.state
|
||||
.update(cx, |state, cx| state.fetch_models(cx).detach());
|
||||
.update(cx, |state, cx| state.restart_fetch_models_task(cx));
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,12 +128,16 @@ impl SyntaxTreeView {
|
||||
fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext<Self>) -> Option<()> {
|
||||
// Find which excerpt the cursor is in, and the position within that excerpted buffer.
|
||||
let editor_state = self.editor.as_mut()?;
|
||||
let editor = &editor_state.editor.read(cx);
|
||||
let selection_range = editor.selections.last::<usize>(cx).range();
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
let (buffer, range, excerpt_id) = multibuffer
|
||||
.range_to_buffer_ranges(selection_range, cx)
|
||||
.pop()?;
|
||||
let (buffer, range, excerpt_id) = editor_state.editor.update(cx, |editor, cx| {
|
||||
let selection_range = editor.selections.last::<usize>(cx).range();
|
||||
Some(
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.range_to_buffer_ranges(selection_range, cx)
|
||||
.pop()?,
|
||||
)
|
||||
})?;
|
||||
|
||||
// If the cursor has moved into a different excerpt, retrieve a new syntax layer
|
||||
// from that buffer.
|
||||
|
||||
@@ -10,7 +10,7 @@ workspace = true
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"tree-sitter"
|
||||
"load-grammars"
|
||||
]
|
||||
load-grammars = [
|
||||
"tree-sitter-bash",
|
||||
@@ -47,6 +47,11 @@ log.workspace = true
|
||||
lsp.workspace = true
|
||||
node_runtime.workspace = true
|
||||
paths.workspace = true
|
||||
pet.workspace = true
|
||||
pet-core.workspace = true
|
||||
pet-conda.workspace = true
|
||||
pet-poetry.workspace = true
|
||||
pet-reporter.workspace = true
|
||||
project.workspace = true
|
||||
regex.workspace = true
|
||||
rope.workspace = true
|
||||
@@ -82,3 +87,8 @@ text.workspace = true
|
||||
theme = { workspace = true, features = ["test-support"] }
|
||||
unindent.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
tree-sitter-typescript.workspace = true
|
||||
tree-sitter-python.workspace = true
|
||||
tree-sitter-go.workspace = true
|
||||
tree-sitter-c.workspace = true
|
||||
tree-sitter-css.workspace = true
|
||||
|
||||
@@ -7,7 +7,9 @@ use feature_flags::FeatureFlagAppExt;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, AsyncAppContext};
|
||||
use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
|
||||
use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use language::{
|
||||
LanguageRegistry, LanguageServerName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
|
||||
};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::ContextProviderWithTasks;
|
||||
@@ -198,6 +200,7 @@ impl LspAdapter for JsonLspAdapter {
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Arc<dyn LanguageToolchainStore>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
cx.update(|cx| {
|
||||
|
||||
@@ -3,7 +3,7 @@ use gpui::{AppContext, UpdateGlobal};
|
||||
use json::json_task_context;
|
||||
pub use language::*;
|
||||
use node_runtime::NodeRuntime;
|
||||
use python::PythonContextProvider;
|
||||
use python::{PythonContextProvider, PythonToolchainProvider};
|
||||
use rust_embed::RustEmbed;
|
||||
use settings::SettingsStore;
|
||||
use smol::stream::StreamExt;
|
||||
@@ -61,7 +61,14 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
move || Ok((config.clone(), load_queries($name), None)),
|
||||
move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
queries: load_queries($name),
|
||||
context_provider: None,
|
||||
toolchain_provider: None,
|
||||
})
|
||||
},
|
||||
);
|
||||
};
|
||||
($name:literal, $adapters:expr) => {
|
||||
@@ -75,7 +82,14 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
move || Ok((config.clone(), load_queries($name), None)),
|
||||
move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
queries: load_queries($name),
|
||||
context_provider: None,
|
||||
toolchain_provider: None,
|
||||
})
|
||||
},
|
||||
);
|
||||
};
|
||||
($name:literal, $adapters:expr, $context_provider:expr) => {
|
||||
@@ -90,11 +104,33 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
move || {
|
||||
Ok((
|
||||
config.clone(),
|
||||
load_queries($name),
|
||||
Some(Arc::new($context_provider)),
|
||||
))
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
queries: load_queries($name),
|
||||
context_provider: Some(Arc::new($context_provider)),
|
||||
toolchain_provider: None,
|
||||
})
|
||||
},
|
||||
);
|
||||
};
|
||||
($name:literal, $adapters:expr, $context_provider:expr, $toolchain_provider:expr) => {
|
||||
let config = load_config($name);
|
||||
// typeck helper
|
||||
let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
|
||||
for adapter in adapters {
|
||||
languages.register_lsp_adapter(config.name.clone(), adapter);
|
||||
}
|
||||
languages.register_language(
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
queries: load_queries($name),
|
||||
context_provider: Some(Arc::new($context_provider)),
|
||||
toolchain_provider: Some($toolchain_provider),
|
||||
})
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -141,7 +177,8 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
|
||||
vec![Arc::new(python::PythonLspAdapter::new(
|
||||
node_runtime.clone(),
|
||||
))],
|
||||
PythonContextProvider
|
||||
PythonContextProvider,
|
||||
Arc::new(PythonToolchainProvider::default()) as Arc<dyn ToolchainLister>
|
||||
);
|
||||
language!(
|
||||
"rust",
|
||||
|
||||
@@ -3,9 +3,16 @@ use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use gpui::AppContext;
|
||||
use gpui::AsyncAppContext;
|
||||
use language::LanguageName;
|
||||
use language::LanguageToolchainStore;
|
||||
use language::Toolchain;
|
||||
use language::ToolchainList;
|
||||
use language::ToolchainLister;
|
||||
use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use pet_core::python_environment::PythonEnvironmentKind;
|
||||
use pet_core::Configuration;
|
||||
use project::lsp_store::language_server_settings;
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -200,12 +207,35 @@ impl LspAdapter for PythonLspAdapter {
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
adapter: &Arc<dyn LspAdapterDelegate>,
|
||||
toolchains: Arc<dyn LanguageToolchainStore>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
cx.update(|cx| {
|
||||
language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
|
||||
.and_then(|s| s.settings.clone())
|
||||
.unwrap_or_default()
|
||||
let toolchain = toolchains
|
||||
.active_toolchain(adapter.worktree_id(), LanguageName::new("Python"), cx)
|
||||
.await;
|
||||
cx.update(move |cx| {
|
||||
let mut user_settings =
|
||||
language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
|
||||
.and_then(|s| s.settings.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
// If python.pythonPath is not set in user config, do so using our toolchain picker.
|
||||
if let Some(toolchain) = toolchain {
|
||||
if user_settings.is_null() {
|
||||
user_settings = Value::Object(serde_json::Map::default());
|
||||
}
|
||||
let object = user_settings.as_object_mut().unwrap();
|
||||
if let Some(python) = object
|
||||
.entry("python")
|
||||
.or_insert(Value::Object(serde_json::Map::default()))
|
||||
.as_object_mut()
|
||||
{
|
||||
python
|
||||
.entry("pythonPath")
|
||||
.or_insert(Value::String(toolchain.path.into()));
|
||||
}
|
||||
}
|
||||
user_settings
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -320,6 +350,83 @@ fn python_module_name_from_relative_path(relative_path: &str) -> String {
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct PythonToolchainProvider {}
|
||||
|
||||
static ENV_PRIORITY_LIST: &'static [PythonEnvironmentKind] = &[
|
||||
// Prioritize non-Conda environments.
|
||||
PythonEnvironmentKind::Poetry,
|
||||
PythonEnvironmentKind::Pipenv,
|
||||
PythonEnvironmentKind::VirtualEnvWrapper,
|
||||
PythonEnvironmentKind::Venv,
|
||||
PythonEnvironmentKind::VirtualEnv,
|
||||
PythonEnvironmentKind::Conda,
|
||||
PythonEnvironmentKind::Pyenv,
|
||||
PythonEnvironmentKind::GlobalPaths,
|
||||
PythonEnvironmentKind::Homebrew,
|
||||
];
|
||||
|
||||
fn env_priority(kind: Option<PythonEnvironmentKind>) -> usize {
|
||||
if let Some(kind) = kind {
|
||||
ENV_PRIORITY_LIST
|
||||
.iter()
|
||||
.position(|blessed_env| blessed_env == &kind)
|
||||
.unwrap_or(ENV_PRIORITY_LIST.len())
|
||||
} else {
|
||||
// Unknown toolchains are less useful than non-blessed ones.
|
||||
ENV_PRIORITY_LIST.len() + 1
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl ToolchainLister for PythonToolchainProvider {
|
||||
async fn list(&self, worktree_root: PathBuf) -> ToolchainList {
|
||||
let environment = pet_core::os_environment::EnvironmentApi::new();
|
||||
let locators = pet::locators::create_locators(
|
||||
Arc::new(pet_conda::Conda::from(&environment)),
|
||||
Arc::new(pet_poetry::Poetry::from(&environment)),
|
||||
&environment,
|
||||
);
|
||||
let mut config = Configuration::default();
|
||||
config.workspace_directories = Some(vec![worktree_root]);
|
||||
let reporter = pet_reporter::collect::create_reporter();
|
||||
pet::find::find_and_report_envs(&reporter, config, &locators, &environment, None);
|
||||
|
||||
let mut toolchains = reporter
|
||||
.environments
|
||||
.lock()
|
||||
.ok()
|
||||
.map_or(Vec::new(), |mut guard| std::mem::take(&mut guard));
|
||||
toolchains.sort_by(|lhs, rhs| {
|
||||
env_priority(lhs.kind)
|
||||
.cmp(&env_priority(rhs.kind))
|
||||
.then_with(|| lhs.executable.cmp(&rhs.executable))
|
||||
});
|
||||
let mut toolchains: Vec<_> = toolchains
|
||||
.into_iter()
|
||||
.filter_map(|toolchain| {
|
||||
let name = if let Some(version) = &toolchain.version {
|
||||
format!("Python {version} ({:?})", toolchain.kind?)
|
||||
} else {
|
||||
format!("{:?}", toolchain.kind?)
|
||||
}
|
||||
.into();
|
||||
Some(Toolchain {
|
||||
name,
|
||||
path: toolchain.executable?.to_str()?.to_owned().into(),
|
||||
language_name: LanguageName::new("Python"),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
toolchains.dedup();
|
||||
ToolchainList {
|
||||
toolchains,
|
||||
default: None,
|
||||
groups: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::{BorrowAppContext, Context, ModelContext, TestAppContext};
|
||||
|
||||
@@ -5,6 +5,9 @@ line_comments = ["// ", "/// ", "//! "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "r#\"", end = "\"#", close = true, newline = true },
|
||||
{ start = "r##\"", end = "\"##", close = true, newline = true },
|
||||
{ start = "r###\"", end = "\"###", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "<", end = ">", close = false, newline = true, not_in = ["string", "comment"] },
|
||||
|
||||
@@ -3,7 +3,7 @@ use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use futures::StreamExt;
|
||||
use gpui::AsyncAppContext;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use language::{LanguageServerName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::lsp_store::language_server_settings;
|
||||
@@ -111,6 +111,7 @@ impl LspAdapter for TailwindLspAdapter {
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Arc<dyn LanguageToolchainStore>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
let tailwind_user_settings = cx.update(|cx| {
|
||||
|
||||
@@ -5,7 +5,7 @@ use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use gpui::AsyncAppContext;
|
||||
use http_client::github::{build_asset_url, AssetKind, GitHubLspBinaryVersion};
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use language::{LanguageServerName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::lsp_store::language_server_settings;
|
||||
@@ -230,6 +230,7 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Arc<dyn LanguageToolchainStore>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
let override_options = cx.update(|cx| {
|
||||
@@ -325,6 +326,7 @@ impl LspAdapter for EsLintLspAdapter {
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Arc<dyn LanguageToolchainStore>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
let workspace_root = delegate.worktree_root_path();
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use gpui::AsyncAppContext;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use language::{LanguageServerName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::lsp_store::language_server_settings;
|
||||
@@ -183,6 +183,7 @@ impl LspAdapter for VtslsLspAdapter {
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Arc<dyn LanguageToolchainStore>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
let tsdk_path = Self::tsdk_path(delegate).await;
|
||||
|
||||
@@ -3,7 +3,8 @@ use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use gpui::AsyncAppContext;
|
||||
use language::{
|
||||
language_settings::AllLanguageSettings, LanguageServerName, LspAdapter, LspAdapterDelegate,
|
||||
language_settings::AllLanguageSettings, LanguageServerName, LanguageToolchainStore, LspAdapter,
|
||||
LspAdapterDelegate,
|
||||
};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
@@ -92,6 +93,7 @@ impl LspAdapter for YamlLspAdapter {
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Arc<dyn LanguageToolchainStore>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
let location = SettingsLocation {
|
||||
|
||||
@@ -1177,6 +1177,8 @@ impl FakeLanguageServer {
|
||||
let (stdout_writer, stdout_reader) = async_pipe::pipe();
|
||||
let (notifications_tx, notifications_rx) = channel::unbounded();
|
||||
|
||||
let root = Self::root_path();
|
||||
|
||||
let mut server = LanguageServer::new_internal(
|
||||
server_id,
|
||||
stdin_writer,
|
||||
@@ -1184,8 +1186,8 @@ impl FakeLanguageServer {
|
||||
None::<async_pipe::PipeReader>,
|
||||
Arc::new(Mutex::new(None)),
|
||||
None,
|
||||
Path::new("/"),
|
||||
Path::new("/"),
|
||||
root,
|
||||
root,
|
||||
None,
|
||||
cx.clone(),
|
||||
|_| {},
|
||||
@@ -1201,8 +1203,8 @@ impl FakeLanguageServer {
|
||||
None::<async_pipe::PipeReader>,
|
||||
Arc::new(Mutex::new(None)),
|
||||
None,
|
||||
Path::new("/"),
|
||||
Path::new("/"),
|
||||
root,
|
||||
root,
|
||||
None,
|
||||
cx,
|
||||
move |msg| {
|
||||
@@ -1238,6 +1240,16 @@ impl FakeLanguageServer {
|
||||
|
||||
(server, fake)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn root_path() -> &'static Path {
|
||||
Path::new("C:\\")
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn root_path() -> &'static Path {
|
||||
Path::new("/")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
||||
@@ -234,6 +234,10 @@ impl<'a> MarkdownParser<'a> {
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
// We want to ignore any inline HTML tags in the text but keep
|
||||
// the text between them
|
||||
Event::InlineHtml(_) => {}
|
||||
|
||||
Event::Text(t) => {
|
||||
text.push_str(t.as_ref());
|
||||
|
||||
@@ -626,6 +630,8 @@ impl<'a> MarkdownParser<'a> {
|
||||
// Otherwise we need to insert the block after all the nested items
|
||||
// that have been parsed so far
|
||||
items.extend(block);
|
||||
} else {
|
||||
self.cursor += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -847,6 +853,16 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_text_with_inline_html() {
|
||||
let parsed = parse("This is a paragraph with an inline HTML <sometag>tag</sometag>.").await;
|
||||
|
||||
assert_eq!(
|
||||
parsed.children,
|
||||
vec![p("This is a paragraph with an inline HTML tag.", 0..63),],
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_raw_links_detection() {
|
||||
let parsed = parse("Checkout this https://zed.dev link").await;
|
||||
@@ -1090,6 +1106,26 @@ Some other content
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_list_item_with_inline_html() {
|
||||
let parsed = parse(
|
||||
"\
|
||||
* This is a list item with an inline HTML <sometag>tag</sometag>.
|
||||
",
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
parsed.children,
|
||||
vec![list_item(
|
||||
0..67,
|
||||
1,
|
||||
Unordered,
|
||||
vec![p("This is a list item with an inline HTML tag.", 4..44),],
|
||||
),],
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_nested_list_with_paragraph_inside() {
|
||||
let parsed = parse(
|
||||
|
||||
@@ -301,8 +301,8 @@ impl MarkdownPreviewView {
|
||||
this.parse_markdown_from_active_editor(true, cx);
|
||||
}
|
||||
EditorEvent::SelectionsChanged { .. } => {
|
||||
let editor = editor.read(cx);
|
||||
let selection_range = editor.selections.last::<usize>(cx).range();
|
||||
let selection_range =
|
||||
editor.update(cx, |editor, cx| editor.selections.last::<usize>(cx).range());
|
||||
this.selected_block = this.get_block_index_under_cursor(selection_range);
|
||||
this.list_state.scroll_to_reveal_item(this.selected_block);
|
||||
cx.notify();
|
||||
|
||||
@@ -2862,6 +2862,30 @@ impl MultiBufferSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn indent_and_comment_for_line(&self, row: MultiBufferRow, cx: &AppContext) -> String {
|
||||
let mut indent = self.indent_size_for_line(row).chars().collect::<String>();
|
||||
|
||||
if self.settings_at(0, cx).extend_comment_on_newline {
|
||||
if let Some(language_scope) = self.language_scope_at(Point::new(row.0, 0)) {
|
||||
let delimiters = language_scope.line_comment_prefixes();
|
||||
for delimiter in delimiters {
|
||||
if *self
|
||||
.chars_at(Point::new(row.0, indent.len() as u32))
|
||||
.take(delimiter.chars().count())
|
||||
.collect::<String>()
|
||||
.as_str()
|
||||
== **delimiter
|
||||
{
|
||||
indent.push_str(&delimiter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indent
|
||||
}
|
||||
|
||||
pub fn prev_non_blank_row(&self, mut row: MultiBufferRow) -> Option<MultiBufferRow> {
|
||||
while row.0 > 0 {
|
||||
row.0 -= 1;
|
||||
@@ -2913,6 +2937,58 @@ impl MultiBufferSnapshot {
|
||||
self.excerpts.summary().text.clone()
|
||||
}
|
||||
|
||||
pub fn dimensions_from_points<'a, D>(
|
||||
&'a self,
|
||||
points: impl 'a + IntoIterator<Item = Point>,
|
||||
) -> impl 'a + Iterator<Item = D>
|
||||
where
|
||||
D: TextDimension,
|
||||
{
|
||||
let mut cursor = self.excerpts.cursor::<TextSummary>(&());
|
||||
let mut memoized_source_start: Option<Point> = None;
|
||||
let mut points = points.into_iter();
|
||||
std::iter::from_fn(move || {
|
||||
let point = points.next()?;
|
||||
|
||||
// Clear the memoized source start if the point is in a different excerpt than previous.
|
||||
if memoized_source_start.map_or(false, |_| point >= cursor.end(&()).lines) {
|
||||
memoized_source_start = None;
|
||||
}
|
||||
|
||||
// Now determine where the excerpt containing the point starts in its source buffer.
|
||||
// We'll use this value to calculate overshoot next.
|
||||
let source_start = if let Some(source_start) = memoized_source_start {
|
||||
source_start
|
||||
} else {
|
||||
cursor.seek_forward(&point, Bias::Right, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
let source_start = excerpt.range.context.start.to_point(&excerpt.buffer);
|
||||
memoized_source_start = Some(source_start);
|
||||
source_start
|
||||
} else {
|
||||
return Some(D::from_text_summary(cursor.start()));
|
||||
}
|
||||
};
|
||||
|
||||
// First, assume the output dimension is at least the start of the excerpt containing the point
|
||||
let mut output = D::from_text_summary(cursor.start());
|
||||
|
||||
// If the point lands within its excerpt, calculate and add the overshoot in dimension D.
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
let overshoot = point - cursor.start().lines;
|
||||
if !overshoot.is_zero() {
|
||||
let end_in_excerpt = source_start + overshoot;
|
||||
output.add_assign(
|
||||
&excerpt
|
||||
.buffer
|
||||
.text_summary_for_range::<D, _>(source_start..end_in_excerpt),
|
||||
);
|
||||
}
|
||||
}
|
||||
Some(output)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn text_summary_for_range<D, O>(&self, range: Range<O>) -> D
|
||||
where
|
||||
D: TextDimension,
|
||||
@@ -4682,6 +4758,12 @@ impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, TextSummary> for Point {
|
||||
fn cmp(&self, cursor_location: &TextSummary, _: &()) -> cmp::Ordering {
|
||||
Ord::cmp(self, &cursor_location.lines)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, Option<&'a Locator>> for Locator {
|
||||
fn cmp(&self, cursor_location: &Option<&'a Locator>, _: &()) -> cmp::Ordering {
|
||||
Ord::cmp(&Some(self), cursor_location)
|
||||
|
||||
@@ -30,8 +30,10 @@ search.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
worktree.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -24,12 +24,12 @@ use editor::{
|
||||
use file_icons::FileIcons;
|
||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, anchored, deferred, div, impl_actions, px, uniform_list, Action, AnyElement,
|
||||
AppContext, AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, ElementId,
|
||||
EventEmitter, FocusHandle, FocusableView, HighlightStyle, InteractiveElement, IntoElement,
|
||||
KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render,
|
||||
SharedString, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext,
|
||||
VisualContext, WeakView, WindowContext,
|
||||
actions, anchored, deferred, div, impl_actions, point, px, size, uniform_list, Action,
|
||||
AnyElement, AppContext, AssetSource, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent,
|
||||
Div, ElementId, EventEmitter, FocusHandle, FocusableView, HighlightStyle, InteractiveElement,
|
||||
IntoElement, KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point,
|
||||
Render, SharedString, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View,
|
||||
ViewContext, VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{BufferId, BufferSnapshot, OffsetRangeExt, OutlineItem};
|
||||
@@ -41,7 +41,8 @@ use search::{BufferSearchBar, ProjectSearchView};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use smol::channel;
|
||||
use theme::SyntaxTheme;
|
||||
use theme::{SyntaxTheme, ThemeSettings};
|
||||
use ui::{IndentGuideColors, IndentGuideLayout};
|
||||
use util::{debug_panic, RangeExt, ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
@@ -254,14 +255,14 @@ impl SearchState {
|
||||
#[derive(Debug)]
|
||||
enum SelectedEntry {
|
||||
Invalidated(Option<PanelEntry>),
|
||||
Valid(PanelEntry),
|
||||
Valid(PanelEntry, usize),
|
||||
None,
|
||||
}
|
||||
|
||||
impl SelectedEntry {
|
||||
fn invalidate(&mut self) {
|
||||
match std::mem::replace(self, SelectedEntry::None) {
|
||||
Self::Valid(entry) => *self = Self::Invalidated(Some(entry)),
|
||||
Self::Valid(entry, _) => *self = Self::Invalidated(Some(entry)),
|
||||
Self::None => *self = Self::Invalidated(None),
|
||||
other => *self = other,
|
||||
}
|
||||
@@ -653,13 +654,25 @@ impl OutlinePanel {
|
||||
});
|
||||
|
||||
let mut outline_panel_settings = *OutlinePanelSettings::get_global(cx);
|
||||
let settings_subscription = cx.observe_global::<SettingsStore>(move |_, cx| {
|
||||
let new_settings = *OutlinePanelSettings::get_global(cx);
|
||||
if outline_panel_settings != new_settings {
|
||||
outline_panel_settings = new_settings;
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
let mut current_theme = ThemeSettings::get_global(cx).clone();
|
||||
let settings_subscription =
|
||||
cx.observe_global::<SettingsStore>(move |outline_panel, cx| {
|
||||
let new_settings = OutlinePanelSettings::get_global(cx);
|
||||
let new_theme = ThemeSettings::get_global(cx);
|
||||
if ¤t_theme != new_theme {
|
||||
outline_panel_settings = *new_settings;
|
||||
current_theme = new_theme.clone();
|
||||
for excerpts in outline_panel.excerpts.values_mut() {
|
||||
for excerpt in excerpts.values_mut() {
|
||||
excerpt.invalidate_outlines();
|
||||
}
|
||||
}
|
||||
outline_panel.update_non_fs_items(cx);
|
||||
} else if &outline_panel_settings != new_settings {
|
||||
outline_panel_settings = *new_settings;
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
|
||||
let mut outline_panel = Self {
|
||||
mode: ItemsDisplayMode::Outline,
|
||||
@@ -2813,7 +2826,6 @@ impl OutlinePanel {
|
||||
cx.spawn(|outline_panel, mut cx| async move {
|
||||
let mut entries = Vec::new();
|
||||
let mut match_candidates = Vec::new();
|
||||
let mut added_contexts = HashSet::default();
|
||||
|
||||
let Ok(()) = outline_panel.update(&mut cx, |outline_panel, cx| {
|
||||
let auto_fold_dirs = OutlinePanelSettings::get_global(cx).auto_fold_dirs;
|
||||
@@ -2935,7 +2947,6 @@ impl OutlinePanel {
|
||||
outline_panel.push_entry(
|
||||
&mut entries,
|
||||
&mut match_candidates,
|
||||
&mut added_contexts,
|
||||
track_matches,
|
||||
new_folded_dirs,
|
||||
folded_depth,
|
||||
@@ -2974,7 +2985,6 @@ impl OutlinePanel {
|
||||
outline_panel.push_entry(
|
||||
&mut entries,
|
||||
&mut match_candidates,
|
||||
&mut added_contexts,
|
||||
track_matches,
|
||||
PanelEntry::FoldedDirs(worktree_id, folded_dirs),
|
||||
folded_depth,
|
||||
@@ -3000,7 +3010,6 @@ impl OutlinePanel {
|
||||
outline_panel.push_entry(
|
||||
&mut entries,
|
||||
&mut match_candidates,
|
||||
&mut added_contexts,
|
||||
track_matches,
|
||||
PanelEntry::FoldedDirs(worktree_id, folded_dirs),
|
||||
folded_depth,
|
||||
@@ -3037,7 +3046,6 @@ impl OutlinePanel {
|
||||
outline_panel.push_entry(
|
||||
&mut entries,
|
||||
&mut match_candidates,
|
||||
&mut added_contexts,
|
||||
track_matches,
|
||||
PanelEntry::Fs(entry.clone()),
|
||||
depth,
|
||||
@@ -3051,7 +3059,6 @@ impl OutlinePanel {
|
||||
outline_panel.add_search_entries(
|
||||
&mut entries,
|
||||
&mut match_candidates,
|
||||
&mut added_contexts,
|
||||
entry.clone(),
|
||||
depth,
|
||||
query.clone(),
|
||||
@@ -3085,7 +3092,6 @@ impl OutlinePanel {
|
||||
query.as_deref(),
|
||||
&mut entries,
|
||||
&mut match_candidates,
|
||||
&mut added_contexts,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
@@ -3101,7 +3107,6 @@ impl OutlinePanel {
|
||||
outline_panel.push_entry(
|
||||
&mut entries,
|
||||
&mut match_candidates,
|
||||
&mut added_contexts,
|
||||
track_matches,
|
||||
PanelEntry::Fs(entry.clone()),
|
||||
0,
|
||||
@@ -3120,7 +3125,6 @@ impl OutlinePanel {
|
||||
outline_panel.push_entry(
|
||||
&mut entries,
|
||||
&mut match_candidates,
|
||||
&mut added_contexts,
|
||||
track_matches,
|
||||
PanelEntry::FoldedDirs(worktree_id, folded_dirs),
|
||||
folded_depth,
|
||||
@@ -3132,22 +3136,10 @@ impl OutlinePanel {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
outline_panel
|
||||
.update(&mut cx, |outline_panel, _| {
|
||||
if matches!(outline_panel.mode, ItemsDisplayMode::Search(_)) {
|
||||
cleanup_fs_entries_without_search_children(
|
||||
&outline_panel.collapsed_entries,
|
||||
&mut entries,
|
||||
&mut match_candidates,
|
||||
&mut added_contexts,
|
||||
);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
let Some(query) = query else {
|
||||
return entries;
|
||||
};
|
||||
|
||||
let mut matched_ids = match_strings(
|
||||
&match_candidates,
|
||||
&query,
|
||||
@@ -3183,7 +3175,6 @@ impl OutlinePanel {
|
||||
&self,
|
||||
entries: &mut Vec<CachedEntry>,
|
||||
match_candidates: &mut Vec<StringMatchCandidate>,
|
||||
added_contexts: &mut HashSet<String>,
|
||||
track_matches: bool,
|
||||
entry: PanelEntry,
|
||||
depth: usize,
|
||||
@@ -3209,47 +3200,39 @@ impl OutlinePanel {
|
||||
if let Some(file_name) =
|
||||
self.relative_path(fs_entry, cx).as_deref().map(file_name)
|
||||
{
|
||||
if added_contexts.insert(file_name.clone()) {
|
||||
match_candidates.push(StringMatchCandidate {
|
||||
id,
|
||||
string: file_name.to_string(),
|
||||
char_bag: file_name.chars().collect(),
|
||||
});
|
||||
}
|
||||
match_candidates.push(StringMatchCandidate {
|
||||
id,
|
||||
string: file_name.to_string(),
|
||||
char_bag: file_name.chars().collect(),
|
||||
});
|
||||
}
|
||||
}
|
||||
PanelEntry::FoldedDirs(worktree_id, entries) => {
|
||||
let dir_names = self.dir_names_string(entries, *worktree_id, cx);
|
||||
{
|
||||
if added_contexts.insert(dir_names.clone()) {
|
||||
match_candidates.push(StringMatchCandidate {
|
||||
id,
|
||||
string: dir_names.clone(),
|
||||
char_bag: dir_names.chars().collect(),
|
||||
});
|
||||
}
|
||||
match_candidates.push(StringMatchCandidate {
|
||||
id,
|
||||
string: dir_names.clone(),
|
||||
char_bag: dir_names.chars().collect(),
|
||||
});
|
||||
}
|
||||
}
|
||||
PanelEntry::Outline(outline_entry) => match outline_entry {
|
||||
OutlineEntry::Outline(_, _, outline) => {
|
||||
if added_contexts.insert(outline.text.clone()) {
|
||||
match_candidates.push(StringMatchCandidate {
|
||||
id,
|
||||
string: outline.text.clone(),
|
||||
char_bag: outline.text.chars().collect(),
|
||||
});
|
||||
}
|
||||
match_candidates.push(StringMatchCandidate {
|
||||
id,
|
||||
string: outline.text.clone(),
|
||||
char_bag: outline.text.chars().collect(),
|
||||
});
|
||||
}
|
||||
OutlineEntry::Excerpt(..) => {}
|
||||
},
|
||||
PanelEntry::Search(new_search_entry) => {
|
||||
if added_contexts.insert(new_search_entry.render_data.context_text.clone()) {
|
||||
match_candidates.push(StringMatchCandidate {
|
||||
id,
|
||||
char_bag: new_search_entry.render_data.context_text.chars().collect(),
|
||||
string: new_search_entry.render_data.context_text.clone(),
|
||||
});
|
||||
}
|
||||
match_candidates.push(StringMatchCandidate {
|
||||
id,
|
||||
char_bag: new_search_entry.render_data.context_text.chars().collect(),
|
||||
string: new_search_entry.render_data.context_text.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3396,7 +3379,6 @@ impl OutlinePanel {
|
||||
query: Option<&str>,
|
||||
entries: &mut Vec<CachedEntry>,
|
||||
match_candidates: &mut Vec<StringMatchCandidate>,
|
||||
added_contexts: &mut HashSet<String>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let Some(excerpts) = self.excerpts.get(&buffer_id) {
|
||||
@@ -3408,7 +3390,6 @@ impl OutlinePanel {
|
||||
self.push_entry(
|
||||
entries,
|
||||
match_candidates,
|
||||
added_contexts,
|
||||
track_matches,
|
||||
PanelEntry::Outline(OutlineEntry::Excerpt(
|
||||
buffer_id,
|
||||
@@ -3436,7 +3417,6 @@ impl OutlinePanel {
|
||||
self.push_entry(
|
||||
entries,
|
||||
match_candidates,
|
||||
added_contexts,
|
||||
track_matches,
|
||||
PanelEntry::Outline(OutlineEntry::Outline(
|
||||
buffer_id,
|
||||
@@ -3456,7 +3436,6 @@ impl OutlinePanel {
|
||||
&mut self,
|
||||
entries: &mut Vec<CachedEntry>,
|
||||
match_candidates: &mut Vec<StringMatchCandidate>,
|
||||
added_contexts: &mut HashSet<String>,
|
||||
parent_entry: FsEntry,
|
||||
parent_depth: usize,
|
||||
filter_query: Option<String>,
|
||||
@@ -3544,7 +3523,6 @@ impl OutlinePanel {
|
||||
self.push_entry(
|
||||
entries,
|
||||
match_candidates,
|
||||
added_contexts,
|
||||
filter_query.is_some(),
|
||||
PanelEntry::Search(new_search_entry),
|
||||
depth,
|
||||
@@ -3591,7 +3569,7 @@ impl OutlinePanel {
|
||||
fn selected_entry(&self) -> Option<&PanelEntry> {
|
||||
match &self.selected_entry {
|
||||
SelectedEntry::Invalidated(entry) => entry.as_ref(),
|
||||
SelectedEntry::Valid(entry) => Some(entry),
|
||||
SelectedEntry::Valid(entry, _) => Some(entry),
|
||||
SelectedEntry::None => None,
|
||||
}
|
||||
}
|
||||
@@ -3600,137 +3578,21 @@ impl OutlinePanel {
|
||||
if focus {
|
||||
self.focus_handle.focus(cx);
|
||||
}
|
||||
self.selected_entry = SelectedEntry::Valid(entry);
|
||||
let ix = self
|
||||
.cached_entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, cached_entry)| &cached_entry.entry == &entry)
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or_default();
|
||||
|
||||
self.selected_entry = SelectedEntry::Valid(entry, ix);
|
||||
|
||||
self.autoscroll(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup_fs_entries_without_search_children(
|
||||
collapsed_entries: &HashSet<CollapsedEntry>,
|
||||
entries: &mut Vec<CachedEntry>,
|
||||
string_match_candidates: &mut Vec<StringMatchCandidate>,
|
||||
added_contexts: &mut HashSet<String>,
|
||||
) {
|
||||
let mut match_ids_to_remove = BTreeSet::new();
|
||||
let mut previous_entry = None::<&PanelEntry>;
|
||||
for (id, entry) in entries.iter().enumerate().rev() {
|
||||
let has_search_items = match (previous_entry, &entry.entry) {
|
||||
(Some(PanelEntry::Outline(_)), _) => unreachable!(),
|
||||
(_, PanelEntry::Outline(_)) => false,
|
||||
(_, PanelEntry::Search(_)) => true,
|
||||
(None, PanelEntry::FoldedDirs(_, _) | PanelEntry::Fs(_)) => false,
|
||||
(
|
||||
Some(PanelEntry::Search(_)),
|
||||
PanelEntry::FoldedDirs(_, _) | PanelEntry::Fs(FsEntry::Directory(..)),
|
||||
) => false,
|
||||
(Some(PanelEntry::FoldedDirs(..)), PanelEntry::FoldedDirs(..)) => true,
|
||||
(
|
||||
Some(PanelEntry::Search(_)),
|
||||
PanelEntry::Fs(FsEntry::File(..) | FsEntry::ExternalFile(..)),
|
||||
) => true,
|
||||
(
|
||||
Some(PanelEntry::Fs(previous_fs)),
|
||||
PanelEntry::FoldedDirs(folded_worktree, folded_dirs),
|
||||
) => {
|
||||
let expected_parent = folded_dirs.last().map(|dir_entry| dir_entry.path.as_ref());
|
||||
match previous_fs {
|
||||
FsEntry::ExternalFile(..) => false,
|
||||
FsEntry::File(file_worktree, file_entry, ..) => {
|
||||
file_worktree == folded_worktree
|
||||
&& file_entry.path.parent() == expected_parent
|
||||
}
|
||||
FsEntry::Directory(directory_wortree, directory_entry) => {
|
||||
directory_wortree == folded_worktree
|
||||
&& directory_entry.path.parent() == expected_parent
|
||||
}
|
||||
}
|
||||
}
|
||||
(
|
||||
Some(PanelEntry::FoldedDirs(folded_worktree, folded_dirs)),
|
||||
PanelEntry::Fs(fs_entry),
|
||||
) => match fs_entry {
|
||||
FsEntry::File(..) | FsEntry::ExternalFile(..) => false,
|
||||
FsEntry::Directory(directory_wortree, maybe_parent_directory) => {
|
||||
directory_wortree == folded_worktree
|
||||
&& Some(maybe_parent_directory.path.as_ref())
|
||||
== folded_dirs
|
||||
.first()
|
||||
.and_then(|dir_entry| dir_entry.path.parent())
|
||||
}
|
||||
},
|
||||
(Some(PanelEntry::Fs(previous_entry)), PanelEntry::Fs(maybe_parent_entry)) => {
|
||||
match (previous_entry, maybe_parent_entry) {
|
||||
(FsEntry::ExternalFile(..), _) | (_, FsEntry::ExternalFile(..)) => false,
|
||||
(FsEntry::Directory(..) | FsEntry::File(..), FsEntry::File(..)) => false,
|
||||
(
|
||||
FsEntry::Directory(previous_worktree, previous_directory),
|
||||
FsEntry::Directory(new_worktree, maybe_parent_directory),
|
||||
) => {
|
||||
previous_worktree == new_worktree
|
||||
&& previous_directory.path.parent()
|
||||
== Some(maybe_parent_directory.path.as_ref())
|
||||
}
|
||||
(
|
||||
FsEntry::File(previous_worktree, previous_file, ..),
|
||||
FsEntry::Directory(new_worktree, maybe_parent_directory),
|
||||
) => {
|
||||
previous_worktree == new_worktree
|
||||
&& previous_file.path.parent()
|
||||
== Some(maybe_parent_directory.path.as_ref())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if has_search_items {
|
||||
previous_entry = Some(&entry.entry);
|
||||
} else {
|
||||
let collapsed_entries_to_check = match &entry.entry {
|
||||
PanelEntry::FoldedDirs(worktree_id, entries) => entries
|
||||
.iter()
|
||||
.map(|entry| CollapsedEntry::Dir(*worktree_id, entry.id))
|
||||
.collect(),
|
||||
PanelEntry::Fs(FsEntry::Directory(worktree_id, entry)) => {
|
||||
vec![CollapsedEntry::Dir(*worktree_id, entry.id)]
|
||||
}
|
||||
PanelEntry::Fs(FsEntry::ExternalFile(buffer_id, _)) => {
|
||||
vec![CollapsedEntry::ExternalFile(*buffer_id)]
|
||||
}
|
||||
PanelEntry::Fs(FsEntry::File(worktree_id, _, buffer_id, _)) => {
|
||||
vec![CollapsedEntry::File(*worktree_id, *buffer_id)]
|
||||
}
|
||||
PanelEntry::Search(_) | PanelEntry::Outline(_) => Vec::new(),
|
||||
};
|
||||
if !collapsed_entries_to_check.is_empty()
|
||||
&& collapsed_entries_to_check
|
||||
.iter()
|
||||
.any(|collapsed_entry| collapsed_entries.contains(collapsed_entry))
|
||||
{
|
||||
previous_entry = Some(&entry.entry);
|
||||
continue;
|
||||
}
|
||||
match_ids_to_remove.insert(id);
|
||||
previous_entry = None;
|
||||
}
|
||||
}
|
||||
|
||||
if match_ids_to_remove.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
string_match_candidates.retain(|candidate| {
|
||||
let retain = !match_ids_to_remove.contains(&candidate.id);
|
||||
if !retain {
|
||||
added_contexts.remove(&candidate.string);
|
||||
}
|
||||
retain
|
||||
});
|
||||
match_ids_to_remove.into_iter().rev().for_each(|id| {
|
||||
entries.remove(id);
|
||||
});
|
||||
}
|
||||
|
||||
fn workspace_active_editor(
|
||||
workspace: &Workspace,
|
||||
cx: &AppContext,
|
||||
@@ -3884,6 +3746,9 @@ impl Render for OutlinePanel {
|
||||
let project = self.project.read(cx);
|
||||
let query = self.query(cx);
|
||||
let pinned = self.pinned;
|
||||
let settings = OutlinePanelSettings::get_global(cx);
|
||||
let indent_size = settings.indent_size;
|
||||
let show_indent_guides = settings.indent_guides;
|
||||
|
||||
let outline_panel = v_flex()
|
||||
.id("outline-panel")
|
||||
@@ -4049,6 +3914,61 @@ impl Render for OutlinePanel {
|
||||
})
|
||||
.size_full()
|
||||
.track_scroll(self.scroll_handle.clone())
|
||||
.when(show_indent_guides, |list| {
|
||||
list.with_decoration(
|
||||
ui::indent_guides(
|
||||
cx.view().clone(),
|
||||
px(indent_size),
|
||||
IndentGuideColors::panel(cx),
|
||||
|outline_panel, range, _| {
|
||||
let entries = outline_panel.cached_entries.get(range);
|
||||
if let Some(entries) = entries {
|
||||
entries.into_iter().map(|item| item.depth).collect()
|
||||
} else {
|
||||
smallvec::SmallVec::new()
|
||||
}
|
||||
},
|
||||
)
|
||||
.with_render_fn(
|
||||
cx.view().clone(),
|
||||
move |outline_panel, params, _| {
|
||||
const LEFT_OFFSET: f32 = 14.;
|
||||
|
||||
let indent_size = params.indent_size;
|
||||
let item_height = params.item_height;
|
||||
let active_indent_guide_ix = find_active_indent_guide_ix(
|
||||
outline_panel,
|
||||
¶ms.indent_guides,
|
||||
);
|
||||
|
||||
params
|
||||
.indent_guides
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, layout)| {
|
||||
let bounds = Bounds::new(
|
||||
point(
|
||||
px(layout.offset.x as f32) * indent_size
|
||||
+ px(LEFT_OFFSET),
|
||||
px(layout.offset.y as f32) * item_height,
|
||||
),
|
||||
size(
|
||||
px(1.),
|
||||
px(layout.length as f32) * item_height,
|
||||
),
|
||||
);
|
||||
ui::RenderedIndentGuide {
|
||||
bounds,
|
||||
layout,
|
||||
is_active: active_indent_guide_ix == Some(ix),
|
||||
hitbox: None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
|
||||
@@ -4093,6 +4013,40 @@ impl Render for OutlinePanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn find_active_indent_guide_ix(
|
||||
outline_panel: &OutlinePanel,
|
||||
candidates: &[IndentGuideLayout],
|
||||
) -> Option<usize> {
|
||||
let SelectedEntry::Valid(_, target_ix) = &outline_panel.selected_entry else {
|
||||
return None;
|
||||
};
|
||||
let target_depth = outline_panel
|
||||
.cached_entries
|
||||
.get(*target_ix)
|
||||
.map(|cached_entry| cached_entry.depth)?;
|
||||
|
||||
let (target_ix, target_depth) = if let Some(target_depth) = outline_panel
|
||||
.cached_entries
|
||||
.get(target_ix + 1)
|
||||
.filter(|cached_entry| cached_entry.depth > target_depth)
|
||||
.map(|entry| entry.depth)
|
||||
{
|
||||
(target_ix + 1, target_depth.saturating_sub(1))
|
||||
} else {
|
||||
(*target_ix, target_depth.saturating_sub(1))
|
||||
};
|
||||
|
||||
candidates
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, guide)| {
|
||||
guide.offset.y <= target_ix
|
||||
&& target_ix < guide.offset.y + guide.length
|
||||
&& guide.offset.x == target_depth
|
||||
})
|
||||
.map(|(ix, _)| ix)
|
||||
}
|
||||
|
||||
fn subscribe_for_editor_events(
|
||||
editor: &View<Editor>,
|
||||
cx: &mut ViewContext<OutlinePanel>,
|
||||
@@ -4362,6 +4316,117 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_item_filtering(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
populate_with_test_ra_project(&fs, "/rust-analyzer").await;
|
||||
let project = Project::test(fs.clone(), ["/rust-analyzer".as_ref()], cx).await;
|
||||
project.read_with(cx, |project, _| {
|
||||
project.languages().add(Arc::new(rust_lang()))
|
||||
});
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
outline_panel.update(cx, |outline_panel, cx| outline_panel.set_active(true, cx));
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch::default(), cx)
|
||||
})
|
||||
.unwrap();
|
||||
let search_view = workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let query = "param_names_for_lifetime_elision_hints";
|
||||
perform_project_search(&search_view, query, cx);
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
search_view
|
||||
.results_editor()
|
||||
.update(cx, |results_editor, cx| {
|
||||
assert_eq!(
|
||||
results_editor.display_text(cx).match_indices(query).count(),
|
||||
9
|
||||
);
|
||||
});
|
||||
});
|
||||
let all_matches = r#"/
|
||||
crates/
|
||||
ide/src/
|
||||
inlay_hints/
|
||||
fn_lifetime_fn.rs
|
||||
search: match config.param_names_for_lifetime_elision_hints {
|
||||
search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {
|
||||
search: Some(it) if config.param_names_for_lifetime_elision_hints => {
|
||||
search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },
|
||||
inlay_hints.rs
|
||||
search: pub param_names_for_lifetime_elision_hints: bool,
|
||||
search: param_names_for_lifetime_elision_hints: self
|
||||
static_index.rs
|
||||
search: param_names_for_lifetime_elision_hints: false,
|
||||
rust-analyzer/src/
|
||||
cli/
|
||||
analysis_stats.rs
|
||||
search: param_names_for_lifetime_elision_hints: true,
|
||||
config.rs
|
||||
search: param_names_for_lifetime_elision_hints: self"#;
|
||||
|
||||
cx.executor()
|
||||
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
|
||||
cx.run_until_parked();
|
||||
outline_panel.update(cx, |outline_panel, _| {
|
||||
assert_eq!(
|
||||
display_entries(&outline_panel.cached_entries, None,),
|
||||
all_matches,
|
||||
);
|
||||
});
|
||||
|
||||
let filter_text = "a";
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
outline_panel.filter_editor.update(cx, |filter_editor, cx| {
|
||||
filter_editor.set_text(filter_text, cx);
|
||||
});
|
||||
});
|
||||
cx.executor()
|
||||
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
|
||||
cx.run_until_parked();
|
||||
|
||||
outline_panel.update(cx, |outline_panel, _| {
|
||||
assert_eq!(
|
||||
display_entries(&outline_panel.cached_entries, None),
|
||||
all_matches
|
||||
.lines()
|
||||
.filter(|item| item.contains(filter_text))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
);
|
||||
});
|
||||
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
outline_panel.filter_editor.update(cx, |filter_editor, cx| {
|
||||
filter_editor.set_text("", cx);
|
||||
});
|
||||
});
|
||||
cx.executor()
|
||||
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
|
||||
cx.run_until_parked();
|
||||
outline_panel.update(cx, |outline_panel, _| {
|
||||
assert_eq!(
|
||||
display_entries(&outline_panel.cached_entries, None,),
|
||||
all_matches,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_frontend_repo_structure(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -19,6 +19,7 @@ pub struct OutlinePanelSettings {
|
||||
pub folder_icons: bool,
|
||||
pub git_status: bool,
|
||||
pub indent_size: f32,
|
||||
pub indent_guides: bool,
|
||||
pub auto_reveal_entries: bool,
|
||||
pub auto_fold_dirs: bool,
|
||||
}
|
||||
@@ -53,6 +54,10 @@ pub struct OutlinePanelSettingsContent {
|
||||
///
|
||||
/// Default: 20
|
||||
pub indent_size: Option<f32>,
|
||||
/// Whether to show indent guides in the outline panel.
|
||||
///
|
||||
/// Default: true
|
||||
pub indent_guides: Option<bool>,
|
||||
/// Whether to reveal it in the outline panel automatically,
|
||||
/// when a corresponding project entry becomes active.
|
||||
/// Gitignored entries are never auto revealed.
|
||||
|
||||
@@ -7,10 +7,11 @@ use crate::{
|
||||
prettier_store::{self, PrettierStore, PrettierStoreEvent},
|
||||
project_settings::{LspSettings, ProjectSettings},
|
||||
relativize_path, resolve_path,
|
||||
toolchain_store::{EmptyToolchainStore, ToolchainStoreEvent},
|
||||
worktree_store::{WorktreeStore, WorktreeStoreEvent},
|
||||
yarn::YarnPathStore,
|
||||
CodeAction, Completion, CoreCompletion, Hover, InlayHint, Item as _, ProjectPath,
|
||||
ProjectTransaction, ResolveState, Symbol,
|
||||
ProjectTransaction, ResolveState, Symbol, ToolchainStore,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use async_trait::async_trait;
|
||||
@@ -36,9 +37,9 @@ use language::{
|
||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
|
||||
DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageName,
|
||||
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName, LocalFile, LspAdapter,
|
||||
LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
|
||||
Unclipped,
|
||||
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName, LanguageToolchainStore,
|
||||
LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset,
|
||||
ToPointUtf16, Transaction, Unclipped,
|
||||
};
|
||||
use lsp::{
|
||||
CodeActionKind, CompletionContext, DiagnosticSeverity, DiagnosticTag,
|
||||
@@ -707,12 +708,13 @@ pub struct LspStore {
|
||||
nonce: u128,
|
||||
buffer_store: Model<BufferStore>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
toolchain_store: Option<Model<ToolchainStore>>,
|
||||
buffer_snapshots: HashMap<BufferId, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
|
||||
pub languages: Arc<LanguageRegistry>,
|
||||
language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
|
||||
pub language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
|
||||
active_entry: Option<ProjectEntryId>,
|
||||
_maintain_workspace_config: Task<Result<()>>,
|
||||
_maintain_workspace_config: (Task<Result<()>>, watch::Sender<()>),
|
||||
_maintain_buffer_languages: Task<()>,
|
||||
next_diagnostic_group_id: usize,
|
||||
diagnostic_summaries:
|
||||
@@ -871,6 +873,7 @@ impl LspStore {
|
||||
buffer_store: Model<BufferStore>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
prettier_store: Model<PrettierStore>,
|
||||
toolchain_store: Model<ToolchainStore>,
|
||||
environment: Model<ProjectEnvironment>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
@@ -884,9 +887,15 @@ impl LspStore {
|
||||
.detach();
|
||||
cx.subscribe(&prettier_store, Self::on_prettier_store_event)
|
||||
.detach();
|
||||
cx.subscribe(&toolchain_store, Self::on_toolchain_store_event)
|
||||
.detach();
|
||||
cx.observe_global::<SettingsStore>(Self::on_settings_changed)
|
||||
.detach();
|
||||
|
||||
let _maintain_workspace_config = {
|
||||
let (sender, receiver) = watch::channel();
|
||||
(Self::maintain_workspace_config(receiver, cx), sender)
|
||||
};
|
||||
Self {
|
||||
mode: LspStoreMode::Local(LocalLspStore {
|
||||
supplementary_language_servers: Default::default(),
|
||||
@@ -909,6 +918,7 @@ impl LspStore {
|
||||
downstream_client: None,
|
||||
buffer_store,
|
||||
worktree_store,
|
||||
toolchain_store: Some(toolchain_store),
|
||||
languages: languages.clone(),
|
||||
language_server_ids: Default::default(),
|
||||
language_server_statuses: Default::default(),
|
||||
@@ -919,7 +929,7 @@ impl LspStore {
|
||||
diagnostics: Default::default(),
|
||||
active_entry: None,
|
||||
|
||||
_maintain_workspace_config: Self::maintain_workspace_config(cx),
|
||||
_maintain_workspace_config,
|
||||
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
|
||||
}
|
||||
}
|
||||
@@ -942,9 +952,10 @@ impl LspStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_remote(
|
||||
pub(super) fn new_remote(
|
||||
buffer_store: Model<BufferStore>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
toolchain_store: Option<Model<ToolchainStore>>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
upstream_client: AnyProtoClient,
|
||||
project_id: u64,
|
||||
@@ -954,7 +965,10 @@ impl LspStore {
|
||||
.detach();
|
||||
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
||||
.detach();
|
||||
|
||||
let _maintain_workspace_config = {
|
||||
let (sender, receiver) = watch::channel();
|
||||
(Self::maintain_workspace_config(receiver, cx), sender)
|
||||
};
|
||||
Self {
|
||||
mode: LspStoreMode::Remote(RemoteLspStore {
|
||||
upstream_client: Some(upstream_client),
|
||||
@@ -972,7 +986,8 @@ impl LspStore {
|
||||
diagnostic_summaries: Default::default(),
|
||||
diagnostics: Default::default(),
|
||||
active_entry: None,
|
||||
_maintain_workspace_config: Self::maintain_workspace_config(cx),
|
||||
toolchain_store,
|
||||
_maintain_workspace_config,
|
||||
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
|
||||
}
|
||||
}
|
||||
@@ -1063,6 +1078,22 @@ impl LspStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_toolchain_store_event(
|
||||
&mut self,
|
||||
_: Model<ToolchainStore>,
|
||||
event: &ToolchainStoreEvent,
|
||||
_: &mut ModelContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
ToolchainStoreEvent::ToolchainActivated { .. } => {
|
||||
self.request_workspace_config_refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn request_workspace_config_refresh(&mut self) {
|
||||
*self._maintain_workspace_config.1.borrow_mut() = ();
|
||||
}
|
||||
// todo!
|
||||
pub fn prettier_store(&self) -> Option<Model<PrettierStore>> {
|
||||
self.as_local().map(|local| local.prettier_store.clone())
|
||||
@@ -3029,17 +3060,13 @@ impl LspStore {
|
||||
None
|
||||
}
|
||||
|
||||
fn maintain_workspace_config(cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel();
|
||||
let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx);
|
||||
|
||||
let settings_observation = cx.observe_global::<SettingsStore>(move |_, _| {
|
||||
*settings_changed_tx.borrow_mut() = ();
|
||||
});
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
while let Some(()) = settings_changed_rx.next().await {
|
||||
let servers = this.update(&mut cx, |this, cx| {
|
||||
pub(crate) async fn refresh_workspace_configurations(
|
||||
this: &WeakModel<Self>,
|
||||
mut cx: AsyncAppContext,
|
||||
) {
|
||||
maybe!(async move {
|
||||
let servers = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.language_server_ids
|
||||
.iter()
|
||||
.filter_map(|((worktree_id, _), server_id)| {
|
||||
@@ -3061,17 +3088,52 @@ impl LspStore {
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
for (adapter, server, delegate) in servers {
|
||||
let settings = adapter.workspace_configuration(&delegate, &mut cx).await?;
|
||||
let toolchain_store = this
|
||||
.update(&mut cx, |this, cx| this.toolchain_store(cx))
|
||||
.ok()?;
|
||||
for (adapter, server, delegate) in servers {
|
||||
let settings = adapter
|
||||
.workspace_configuration(&delegate, toolchain_store.clone(), &mut cx)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
server
|
||||
.notify::<lsp::notification::DidChangeConfiguration>(
|
||||
lsp::DidChangeConfigurationParams { settings },
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
server
|
||||
.notify::<lsp::notification::DidChangeConfiguration>(
|
||||
lsp::DidChangeConfigurationParams { settings },
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
Some(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
fn toolchain_store(&self, cx: &AppContext) -> Arc<dyn LanguageToolchainStore> {
|
||||
if let Some(toolchain_store) = self.toolchain_store.as_ref() {
|
||||
toolchain_store.read(cx).as_language_toolchain_store()
|
||||
} else {
|
||||
Arc::new(EmptyToolchainStore)
|
||||
}
|
||||
}
|
||||
fn maintain_workspace_config(
|
||||
external_refresh_requests: watch::Receiver<()>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel();
|
||||
let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx);
|
||||
|
||||
let settings_observation = cx.observe_global::<SettingsStore>(move |_, _| {
|
||||
*settings_changed_tx.borrow_mut() = ();
|
||||
});
|
||||
|
||||
let mut joint_future =
|
||||
futures::stream::select(settings_changed_rx, external_refresh_requests);
|
||||
cx.spawn(move |this, cx| async move {
|
||||
while let Some(()) = joint_future.next().await {
|
||||
Self::refresh_workspace_configurations(&this, cx.clone()).await;
|
||||
}
|
||||
|
||||
drop(settings_observation);
|
||||
@@ -5517,6 +5579,9 @@ impl LspStore {
|
||||
let delegate = delegate.clone();
|
||||
let adapter = adapter.clone();
|
||||
let this = this.clone();
|
||||
let toolchains = this
|
||||
.update(&mut cx, |this, cx| this.toolchain_store(cx))
|
||||
.ok()?;
|
||||
let mut cx = cx.clone();
|
||||
async move {
|
||||
let language_server = pending_server.await?;
|
||||
@@ -5524,7 +5589,7 @@ impl LspStore {
|
||||
let workspace_config = adapter
|
||||
.adapter
|
||||
.clone()
|
||||
.workspace_configuration(&delegate, &mut cx)
|
||||
.workspace_configuration(&delegate, toolchains.clone(), &mut cx)
|
||||
.await?;
|
||||
|
||||
let mut initialization_options = adapter
|
||||
@@ -5864,17 +5929,21 @@ impl LspStore {
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
language_server
|
||||
.on_request::<lsp::request::WorkspaceConfiguration, _, _>({
|
||||
let adapter = adapter.adapter.clone();
|
||||
let delegate = delegate.clone();
|
||||
let this = this.clone();
|
||||
move |params, mut cx| {
|
||||
let adapter = adapter.clone();
|
||||
let delegate = delegate.clone();
|
||||
let this = this.clone();
|
||||
async move {
|
||||
let workspace_config =
|
||||
adapter.workspace_configuration(&delegate, &mut cx).await?;
|
||||
let toolchains =
|
||||
this.update(&mut cx, |this, cx| this.toolchain_store(cx))?;
|
||||
let workspace_config = adapter
|
||||
.workspace_configuration(&delegate, toolchains, &mut cx)
|
||||
.await?;
|
||||
Ok(params
|
||||
.items
|
||||
.into_iter()
|
||||
|
||||
@@ -11,6 +11,7 @@ pub mod search;
|
||||
mod task_inventory;
|
||||
pub mod task_store;
|
||||
pub mod terminals;
|
||||
pub mod toolchain_store;
|
||||
pub mod worktree_store;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -24,9 +25,7 @@ mod yarn;
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use buffer_store::{BufferStore, BufferStoreEvent};
|
||||
use client::{
|
||||
proto, Client, Collaborator, PendingEntitySubscription, ProjectId, TypedEnvelope, UserStore,
|
||||
};
|
||||
use client::{proto, Client, Collaborator, PendingEntitySubscription, TypedEnvelope, UserStore};
|
||||
use clock::ReplicaId;
|
||||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
use debounced_delay::DebouncedDelay;
|
||||
@@ -46,8 +45,8 @@ use itertools::Itertools;
|
||||
use language::{
|
||||
language_settings::InlayHintKind, proto::split_operations, Buffer, BufferEvent,
|
||||
CachedLspAdapter, Capability, CodeLabel, DiagnosticEntry, Documentation, File as _, Language,
|
||||
LanguageRegistry, LanguageServerName, PointUtf16, ToOffset, ToPointUtf16, Transaction,
|
||||
Unclipped,
|
||||
LanguageName, LanguageRegistry, LanguageServerName, PointUtf16, ToOffset, ToPointUtf16,
|
||||
Toolchain, ToolchainList, Transaction, Unclipped,
|
||||
};
|
||||
use lsp::{
|
||||
CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServer, LanguageServerId,
|
||||
@@ -103,7 +102,7 @@ pub use lsp_store::{
|
||||
LanguageServerStatus, LanguageServerToQuery, LspStore, LspStoreEvent,
|
||||
SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||
};
|
||||
|
||||
pub use toolchain_store::ToolchainStore;
|
||||
const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
|
||||
const MAX_SEARCH_RESULT_FILES: usize = 5_000;
|
||||
const MAX_SEARCH_RESULT_RANGES: usize = 10_000;
|
||||
@@ -154,13 +153,13 @@ pub struct Project {
|
||||
remotely_created_models: Arc<Mutex<RemotelyCreatedModels>>,
|
||||
terminals: Terminals,
|
||||
node: Option<NodeRuntime>,
|
||||
hosted_project_id: Option<ProjectId>,
|
||||
search_history: SearchHistory,
|
||||
search_included_history: SearchHistory,
|
||||
search_excluded_history: SearchHistory,
|
||||
snippets: Model<SnippetProvider>,
|
||||
environment: Model<ProjectEnvironment>,
|
||||
settings_observer: Model<SettingsObserver>,
|
||||
toolchain_store: Option<Model<ToolchainStore>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -288,6 +287,13 @@ impl ProjectPath {
|
||||
path: self.path.to_string_lossy().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_path(worktree_id: WorktreeId) -> Self {
|
||||
Self {
|
||||
worktree_id,
|
||||
path: Path::new("").into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -575,6 +581,7 @@ impl Project {
|
||||
LspStore::init(&client);
|
||||
SettingsObserver::init(&client);
|
||||
TaskStore::init(Some(&client));
|
||||
ToolchainStore::init(&client);
|
||||
}
|
||||
|
||||
pub fn local(
|
||||
@@ -631,12 +638,15 @@ impl Project {
|
||||
});
|
||||
cx.subscribe(&settings_observer, Self::on_settings_observer_event)
|
||||
.detach();
|
||||
|
||||
let toolchain_store = cx.new_model(|cx| {
|
||||
ToolchainStore::local(languages.clone(), worktree_store.clone(), cx)
|
||||
});
|
||||
let lsp_store = cx.new_model(|cx| {
|
||||
LspStore::new_local(
|
||||
buffer_store.clone(),
|
||||
worktree_store.clone(),
|
||||
prettier_store.clone(),
|
||||
toolchain_store.clone(),
|
||||
environment.clone(),
|
||||
languages.clone(),
|
||||
client.http_client(),
|
||||
@@ -671,13 +681,14 @@ impl Project {
|
||||
local_handles: Vec::new(),
|
||||
},
|
||||
node: Some(node),
|
||||
hosted_project_id: None,
|
||||
search_history: Self::new_search_history(),
|
||||
environment,
|
||||
remotely_created_models: Default::default(),
|
||||
|
||||
search_included_history: Self::new_search_history(),
|
||||
search_excluded_history: Self::new_search_history(),
|
||||
|
||||
toolchain_store: Some(toolchain_store),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -701,7 +712,7 @@ impl Project {
|
||||
|
||||
let ssh_proto = ssh.read(cx).proto_client();
|
||||
let worktree_store =
|
||||
cx.new_model(|_| WorktreeStore::remote(false, ssh_proto.clone(), 0));
|
||||
cx.new_model(|_| WorktreeStore::remote(false, ssh_proto.clone(), SSH_PROJECT_ID));
|
||||
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
||||
.detach();
|
||||
|
||||
@@ -734,10 +745,14 @@ impl Project {
|
||||
.detach();
|
||||
|
||||
let environment = ProjectEnvironment::new(&worktree_store, None, cx);
|
||||
let toolchain_store = Some(cx.new_model(|cx| {
|
||||
ToolchainStore::remote(SSH_PROJECT_ID, ssh.read(cx).proto_client(), cx)
|
||||
}));
|
||||
let lsp_store = cx.new_model(|cx| {
|
||||
LspStore::new_remote(
|
||||
buffer_store.clone(),
|
||||
worktree_store.clone(),
|
||||
toolchain_store.clone(),
|
||||
languages.clone(),
|
||||
ssh_proto.clone(),
|
||||
SSH_PROJECT_ID,
|
||||
@@ -789,13 +804,14 @@ impl Project {
|
||||
local_handles: Vec::new(),
|
||||
},
|
||||
node: Some(node),
|
||||
hosted_project_id: None,
|
||||
search_history: Self::new_search_history(),
|
||||
environment,
|
||||
remotely_created_models: Default::default(),
|
||||
|
||||
search_included_history: Self::new_search_history(),
|
||||
search_excluded_history: Self::new_search_history(),
|
||||
|
||||
toolchain_store,
|
||||
};
|
||||
|
||||
let ssh = ssh.read(cx);
|
||||
@@ -816,6 +832,7 @@ impl Project {
|
||||
LspStore::init(&ssh_proto);
|
||||
SettingsObserver::init(&ssh_proto);
|
||||
TaskStore::init(Some(&ssh_proto));
|
||||
ToolchainStore::init(&ssh_proto);
|
||||
|
||||
this
|
||||
})
|
||||
@@ -903,6 +920,7 @@ impl Project {
|
||||
let mut lsp_store = LspStore::new_remote(
|
||||
buffer_store.clone(),
|
||||
worktree_store.clone(),
|
||||
None,
|
||||
languages.clone(),
|
||||
client.clone().into(),
|
||||
remote_id,
|
||||
@@ -986,12 +1004,12 @@ impl Project {
|
||||
local_handles: Vec::new(),
|
||||
},
|
||||
node: None,
|
||||
hosted_project_id: None,
|
||||
search_history: Self::new_search_history(),
|
||||
search_included_history: Self::new_search_history(),
|
||||
search_excluded_history: Self::new_search_history(),
|
||||
environment: ProjectEnvironment::new(&worktree_store, None, cx),
|
||||
remotely_created_models: Arc::new(Mutex::new(RemotelyCreatedModels::default())),
|
||||
toolchain_store: None,
|
||||
};
|
||||
this.set_role(role, cx);
|
||||
for worktree in worktrees {
|
||||
@@ -1038,47 +1056,6 @@ impl Project {
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
pub async fn hosted(
|
||||
remote_id: ProjectId,
|
||||
user_store: Model<UserStore>,
|
||||
client: Arc<Client>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<Model<Self>> {
|
||||
client.authenticate_and_connect(true, &cx).await?;
|
||||
|
||||
let subscriptions = [
|
||||
EntitySubscription::Project(client.subscribe_to_entity::<Self>(remote_id.0)?),
|
||||
EntitySubscription::BufferStore(
|
||||
client.subscribe_to_entity::<BufferStore>(remote_id.0)?,
|
||||
),
|
||||
EntitySubscription::WorktreeStore(
|
||||
client.subscribe_to_entity::<WorktreeStore>(remote_id.0)?,
|
||||
),
|
||||
EntitySubscription::LspStore(client.subscribe_to_entity::<LspStore>(remote_id.0)?),
|
||||
EntitySubscription::SettingsObserver(
|
||||
client.subscribe_to_entity::<SettingsObserver>(remote_id.0)?,
|
||||
),
|
||||
];
|
||||
let response = client
|
||||
.request_envelope(proto::JoinHostedProject {
|
||||
project_id: remote_id.0,
|
||||
})
|
||||
.await?;
|
||||
Self::from_join_project_response(
|
||||
response,
|
||||
subscriptions,
|
||||
client,
|
||||
true,
|
||||
user_store,
|
||||
languages,
|
||||
fs,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn new_search_history() -> SearchHistory {
|
||||
SearchHistory::new(
|
||||
Some(MAX_PROJECT_SEARCH_HISTORY_SIZE),
|
||||
@@ -1283,10 +1260,6 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hosted_project_id(&self) -> Option<ProjectId> {
|
||||
self.hosted_project_id
|
||||
}
|
||||
|
||||
pub fn supports_terminal(&self, _cx: &AppContext) -> bool {
|
||||
if self.is_local() {
|
||||
return true;
|
||||
@@ -2390,6 +2363,46 @@ impl Project {
|
||||
.map_err(|e| anyhow!(e))
|
||||
}
|
||||
|
||||
pub fn available_toolchains(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &AppContext,
|
||||
) -> Task<Option<ToolchainList>> {
|
||||
if let Some(toolchain_store) = self.toolchain_store.as_ref() {
|
||||
toolchain_store
|
||||
.read(cx)
|
||||
.list_toolchains(worktree_id, language_name, cx)
|
||||
} else {
|
||||
Task::ready(None)
|
||||
}
|
||||
}
|
||||
pub fn activate_toolchain(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
toolchain: Toolchain,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Option<()>> {
|
||||
let Some(toolchain_store) = self.toolchain_store.clone() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
toolchain_store.update(cx, |this, cx| {
|
||||
this.activate_toolchain(worktree_id, toolchain, cx)
|
||||
})
|
||||
}
|
||||
pub fn active_toolchain(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &AppContext,
|
||||
) -> Task<Option<Toolchain>> {
|
||||
let Some(toolchain_store) = self.toolchain_store.clone() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
toolchain_store
|
||||
.read(cx)
|
||||
.active_toolchain(worktree_id, language_name, cx)
|
||||
}
|
||||
pub fn language_server_statuses<'a>(
|
||||
&'a self,
|
||||
cx: &'a AppContext,
|
||||
@@ -3081,7 +3094,7 @@ impl Project {
|
||||
}
|
||||
|
||||
/// Returns the resolved version of `path`, that was found in `buffer`, if it exists.
|
||||
pub fn resolve_existing_file_path(
|
||||
pub fn resolve_path_in_buffer(
|
||||
&self,
|
||||
path: &str,
|
||||
buffer: &Model<Buffer>,
|
||||
@@ -3089,47 +3102,56 @@ impl Project {
|
||||
) -> Task<Option<ResolvedPath>> {
|
||||
let path_buf = PathBuf::from(path);
|
||||
if path_buf.is_absolute() || path.starts_with("~") {
|
||||
self.resolve_abs_file_path(path, cx)
|
||||
self.resolve_abs_path(path, cx)
|
||||
} else {
|
||||
self.resolve_path_in_worktrees(path_buf, buffer, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn abs_file_path_exists(&self, path: &str, cx: &mut ModelContext<Self>) -> Task<bool> {
|
||||
let resolve_task = self.resolve_abs_file_path(path, cx);
|
||||
pub fn resolve_abs_file_path(
|
||||
&self,
|
||||
path: &str,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Option<ResolvedPath>> {
|
||||
let resolve_task = self.resolve_abs_path(path, cx);
|
||||
cx.background_executor().spawn(async move {
|
||||
let resolved_path = resolve_task.await;
|
||||
resolved_path.is_some()
|
||||
resolved_path.filter(|path| path.is_file())
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_abs_file_path(
|
||||
pub fn resolve_abs_path(
|
||||
&self,
|
||||
path: &str,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Option<ResolvedPath>> {
|
||||
if self.is_local() {
|
||||
let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
|
||||
|
||||
let fs = self.fs.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
let path = expanded.as_path();
|
||||
let exists = fs.is_file(path).await;
|
||||
let metadata = fs.metadata(path).await.ok().flatten();
|
||||
|
||||
exists.then(|| ResolvedPath::AbsPath(expanded))
|
||||
metadata.map(|metadata| ResolvedPath::AbsPath {
|
||||
path: expanded,
|
||||
is_dir: metadata.is_dir,
|
||||
})
|
||||
})
|
||||
} else if let Some(ssh_client) = self.ssh_client.as_ref() {
|
||||
let request = ssh_client
|
||||
.read(cx)
|
||||
.proto_client()
|
||||
.request(proto::CheckFileExists {
|
||||
.request(proto::GetPathMetadata {
|
||||
project_id: SSH_PROJECT_ID,
|
||||
path: path.to_string(),
|
||||
});
|
||||
cx.background_executor().spawn(async move {
|
||||
let response = request.await.log_err()?;
|
||||
if response.exists {
|
||||
Some(ResolvedPath::AbsPath(PathBuf::from(response.path)))
|
||||
Some(ResolvedPath::AbsPath {
|
||||
path: PathBuf::from(response.path),
|
||||
is_dir: response.is_dir,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -3168,10 +3190,14 @@ impl Project {
|
||||
resolved.strip_prefix(root_entry_path).unwrap_or(&resolved);
|
||||
|
||||
worktree.entry_for_path(stripped).map(|entry| {
|
||||
ResolvedPath::ProjectPath(ProjectPath {
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: worktree.id(),
|
||||
path: entry.path.clone(),
|
||||
})
|
||||
};
|
||||
ResolvedPath::ProjectPath {
|
||||
project_path,
|
||||
is_dir: entry.is_dir(),
|
||||
}
|
||||
})
|
||||
})
|
||||
.ok()?;
|
||||
@@ -3370,6 +3396,25 @@ impl Project {
|
||||
worktree.get_local_repo(&root_entry)?.repo().clone().into()
|
||||
}
|
||||
|
||||
pub fn branches(
|
||||
&self,
|
||||
project_path: ProjectPath,
|
||||
cx: &AppContext,
|
||||
) -> Task<Result<Vec<git::repository::Branch>>> {
|
||||
self.worktree_store().read(cx).branches(project_path, cx)
|
||||
}
|
||||
|
||||
pub fn update_or_create_branch(
|
||||
&self,
|
||||
repository: ProjectPath,
|
||||
new_branch: String,
|
||||
cx: &AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
self.worktree_store()
|
||||
.read(cx)
|
||||
.update_or_create_branch(repository, new_branch, cx)
|
||||
}
|
||||
|
||||
pub fn blame_buffer(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
@@ -3558,6 +3603,13 @@ impl Project {
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
|
||||
// We drop `this` to avoid holding a reference in this future for too
|
||||
// long.
|
||||
// If we keep the reference, we might not drop the `Project` early
|
||||
// enough when closing a window and it will only get releases on the
|
||||
// next `flush_effects()` call.
|
||||
drop(this);
|
||||
|
||||
let answer = rx.next().await;
|
||||
|
||||
Ok(LanguageServerPromptResponse {
|
||||
@@ -4117,24 +4169,41 @@ fn resolve_path(base: &Path, path: &Path) -> PathBuf {
|
||||
/// or an AbsPath and that *exists*.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ResolvedPath {
|
||||
ProjectPath(ProjectPath),
|
||||
AbsPath(PathBuf),
|
||||
ProjectPath {
|
||||
project_path: ProjectPath,
|
||||
is_dir: bool,
|
||||
},
|
||||
AbsPath {
|
||||
path: PathBuf,
|
||||
is_dir: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl ResolvedPath {
|
||||
pub fn abs_path(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Self::AbsPath(path) => Some(path.as_path()),
|
||||
Self::AbsPath { path, .. } => Some(path.as_path()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn project_path(&self) -> Option<&ProjectPath> {
|
||||
match self {
|
||||
Self::ProjectPath(path) => Some(&path),
|
||||
Self::ProjectPath { project_path, .. } => Some(&project_path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_file(&self) -> bool {
|
||||
!self.is_dir()
|
||||
}
|
||||
|
||||
pub fn is_dir(&self) -> bool {
|
||||
match self {
|
||||
Self::ProjectPath { is_dir, .. } => *is_dir,
|
||||
Self::AbsPath { is_dir, .. } => *is_dir,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for Buffer {
|
||||
|
||||
416
crates/project/src/toolchain_store.rs
Normal file
416
crates/project/src/toolchain_store.rs
Normal file
@@ -0,0 +1,416 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use collections::BTreeMap;
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
|
||||
WeakModel,
|
||||
};
|
||||
use language::{LanguageName, LanguageRegistry, LanguageToolchainStore, Toolchain, ToolchainList};
|
||||
use rpc::{proto, AnyProtoClient, TypedEnvelope};
|
||||
use settings::WorktreeId;
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::worktree_store::WorktreeStore;
|
||||
|
||||
pub struct ToolchainStore(ToolchainStoreInner);
|
||||
enum ToolchainStoreInner {
|
||||
Local(Model<LocalToolchainStore>, #[allow(dead_code)] Subscription),
|
||||
Remote(Model<RemoteToolchainStore>),
|
||||
}
|
||||
|
||||
impl EventEmitter<ToolchainStoreEvent> for ToolchainStore {}
|
||||
impl ToolchainStore {
|
||||
pub fn init(client: &AnyProtoClient) {
|
||||
client.add_model_request_handler(Self::handle_activate_toolchain);
|
||||
client.add_model_request_handler(Self::handle_list_toolchains);
|
||||
client.add_model_request_handler(Self::handle_active_toolchain);
|
||||
}
|
||||
|
||||
pub fn local(
|
||||
languages: Arc<LanguageRegistry>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let model = cx.new_model(|_| LocalToolchainStore {
|
||||
languages,
|
||||
worktree_store,
|
||||
active_toolchains: Default::default(),
|
||||
});
|
||||
let subscription = cx.subscribe(&model, |_, _, e: &ToolchainStoreEvent, cx| {
|
||||
cx.emit(e.clone())
|
||||
});
|
||||
Self(ToolchainStoreInner::Local(model, subscription))
|
||||
}
|
||||
pub(super) fn remote(project_id: u64, client: AnyProtoClient, cx: &mut AppContext) -> Self {
|
||||
Self(ToolchainStoreInner::Remote(
|
||||
cx.new_model(|_| RemoteToolchainStore { client, project_id }),
|
||||
))
|
||||
}
|
||||
pub(crate) fn activate_toolchain(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
toolchain: Toolchain,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Option<()>> {
|
||||
match &self.0 {
|
||||
ToolchainStoreInner::Local(local, _) => local.update(cx, |this, cx| {
|
||||
this.activate_toolchain(worktree_id, toolchain, cx)
|
||||
}),
|
||||
ToolchainStoreInner::Remote(remote) => {
|
||||
remote
|
||||
.read(cx)
|
||||
.activate_toolchain(worktree_id, toolchain, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) fn list_toolchains(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &AppContext,
|
||||
) -> Task<Option<ToolchainList>> {
|
||||
match &self.0 {
|
||||
ToolchainStoreInner::Local(local, _) => {
|
||||
local
|
||||
.read(cx)
|
||||
.list_toolchains(worktree_id, language_name, cx)
|
||||
}
|
||||
ToolchainStoreInner::Remote(remote) => {
|
||||
remote
|
||||
.read(cx)
|
||||
.list_toolchains(worktree_id, language_name, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) fn active_toolchain(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &AppContext,
|
||||
) -> Task<Option<Toolchain>> {
|
||||
match &self.0 {
|
||||
ToolchainStoreInner::Local(local, _) => {
|
||||
local
|
||||
.read(cx)
|
||||
.active_toolchain(worktree_id, language_name, cx)
|
||||
}
|
||||
ToolchainStoreInner::Remote(remote) => {
|
||||
remote
|
||||
.read(cx)
|
||||
.active_toolchain(worktree_id, language_name, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn handle_activate_toolchain(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::ActivateToolchain>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::Ack> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let language_name = LanguageName::from_proto(envelope.payload.language_name);
|
||||
let Some(toolchain) = envelope.payload.toolchain else {
|
||||
bail!("Missing `toolchain` in payload");
|
||||
};
|
||||
let toolchain = Toolchain {
|
||||
name: toolchain.name.into(),
|
||||
path: toolchain.path.into(),
|
||||
language_name,
|
||||
};
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
Ok(this.activate_toolchain(worktree_id, toolchain, cx))
|
||||
})??
|
||||
.await;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
async fn handle_active_toolchain(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::ActiveToolchain>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::ActiveToolchainResponse> {
|
||||
let toolchain = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
let language_name = LanguageName::from_proto(envelope.payload.language_name);
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
this.active_toolchain(worktree_id, language_name, cx)
|
||||
})?
|
||||
.await;
|
||||
|
||||
Ok(proto::ActiveToolchainResponse {
|
||||
toolchain: toolchain.map(|toolchain| proto::Toolchain {
|
||||
name: toolchain.name.into(),
|
||||
path: toolchain.path.into(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_list_toolchains(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::ListToolchains>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::ListToolchainsResponse> {
|
||||
let toolchains = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
let language_name = LanguageName::from_proto(envelope.payload.language_name);
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
this.list_toolchains(worktree_id, language_name, cx)
|
||||
})?
|
||||
.await;
|
||||
let has_values = toolchains.is_some();
|
||||
let groups = if let Some(toolchains) = &toolchains {
|
||||
toolchains
|
||||
.groups
|
||||
.iter()
|
||||
.filter_map(|group| {
|
||||
Some(proto::ToolchainGroup {
|
||||
start_index: u64::try_from(group.0).ok()?,
|
||||
name: String::from(group.1.as_ref()),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let toolchains = if let Some(toolchains) = toolchains {
|
||||
toolchains
|
||||
.toolchains
|
||||
.into_iter()
|
||||
.map(|toolchain| proto::Toolchain {
|
||||
name: toolchain.name.to_string(),
|
||||
path: toolchain.path.to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
Ok(proto::ListToolchainsResponse {
|
||||
has_values,
|
||||
toolchains,
|
||||
groups,
|
||||
})
|
||||
}
|
||||
pub(crate) fn as_language_toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
|
||||
match &self.0 {
|
||||
ToolchainStoreInner::Local(local, _) => Arc::new(LocalStore(local.downgrade())),
|
||||
ToolchainStoreInner::Remote(remote) => Arc::new(RemoteStore(remote.downgrade())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalToolchainStore {
|
||||
languages: Arc<LanguageRegistry>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
active_toolchains: BTreeMap<(WorktreeId, LanguageName), Toolchain>,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl language::LanguageToolchainStore for LocalStore {
|
||||
async fn active_toolchain(
|
||||
self: Arc<Self>,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<Toolchain> {
|
||||
self.0
|
||||
.update(cx, |this, cx| {
|
||||
this.active_toolchain(worktree_id, language_name, cx)
|
||||
})
|
||||
.ok()?
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl language::LanguageToolchainStore for RemoteStore {
|
||||
async fn active_toolchain(
|
||||
self: Arc<Self>,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<Toolchain> {
|
||||
self.0
|
||||
.update(cx, |this, cx| {
|
||||
this.active_toolchain(worktree_id, language_name, cx)
|
||||
})
|
||||
.ok()?
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct EmptyToolchainStore;
|
||||
#[async_trait(?Send)]
|
||||
impl language::LanguageToolchainStore for EmptyToolchainStore {
|
||||
async fn active_toolchain(
|
||||
self: Arc<Self>,
|
||||
_: WorktreeId,
|
||||
_: LanguageName,
|
||||
_: &mut AsyncAppContext,
|
||||
) -> Option<Toolchain> {
|
||||
None
|
||||
}
|
||||
}
|
||||
struct LocalStore(WeakModel<LocalToolchainStore>);
|
||||
struct RemoteStore(WeakModel<RemoteToolchainStore>);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum ToolchainStoreEvent {
|
||||
ToolchainActivated,
|
||||
}
|
||||
|
||||
impl EventEmitter<ToolchainStoreEvent> for LocalToolchainStore {}
|
||||
|
||||
impl LocalToolchainStore {
|
||||
pub(crate) fn activate_toolchain(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
toolchain: Toolchain,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Option<()>> {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.active_toolchains.insert(
|
||||
(worktree_id, toolchain.language_name.clone()),
|
||||
toolchain.clone(),
|
||||
);
|
||||
cx.emit(ToolchainStoreEvent::ToolchainActivated);
|
||||
})
|
||||
.ok();
|
||||
Some(())
|
||||
})
|
||||
}
|
||||
pub(crate) fn list_toolchains(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &AppContext,
|
||||
) -> Task<Option<ToolchainList>> {
|
||||
let registry = self.languages.clone();
|
||||
let Some(root) = self
|
||||
.worktree_store
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.map(|worktree| worktree.read(cx).abs_path())
|
||||
else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
cx.spawn(|_| async move {
|
||||
let language = registry.language_for_name(&language_name.0).await.ok()?;
|
||||
let toolchains = language.toolchain_lister()?.list(root.to_path_buf()).await;
|
||||
Some(toolchains)
|
||||
})
|
||||
}
|
||||
pub(crate) fn active_toolchain(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
_: &AppContext,
|
||||
) -> Task<Option<Toolchain>> {
|
||||
Task::ready(
|
||||
self.active_toolchains
|
||||
.get(&(worktree_id, language_name))
|
||||
.cloned(),
|
||||
)
|
||||
}
|
||||
}
|
||||
struct RemoteToolchainStore {
|
||||
client: AnyProtoClient,
|
||||
project_id: u64,
|
||||
}
|
||||
|
||||
impl RemoteToolchainStore {
|
||||
pub(crate) fn activate_toolchain(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
toolchain: Toolchain,
|
||||
cx: &AppContext,
|
||||
) -> Task<Option<()>> {
|
||||
let project_id = self.project_id;
|
||||
let client = self.client.clone();
|
||||
cx.spawn(move |_| async move {
|
||||
let _ = client
|
||||
.request(proto::ActivateToolchain {
|
||||
project_id,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
language_name: toolchain.language_name.into(),
|
||||
toolchain: Some(proto::Toolchain {
|
||||
name: toolchain.name.into(),
|
||||
path: toolchain.path.into(),
|
||||
}),
|
||||
})
|
||||
.await
|
||||
.log_err()?;
|
||||
Some(())
|
||||
})
|
||||
}
|
||||
pub(crate) fn list_toolchains(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &AppContext,
|
||||
) -> Task<Option<ToolchainList>> {
|
||||
let project_id = self.project_id;
|
||||
let client = self.client.clone();
|
||||
cx.spawn(move |_| async move {
|
||||
let response = client
|
||||
.request(proto::ListToolchains {
|
||||
project_id,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
language_name: language_name.clone().into(),
|
||||
})
|
||||
.await
|
||||
.log_err()?;
|
||||
if !response.has_values {
|
||||
return None;
|
||||
}
|
||||
let toolchains = response
|
||||
.toolchains
|
||||
.into_iter()
|
||||
.map(|toolchain| Toolchain {
|
||||
language_name: language_name.clone(),
|
||||
name: toolchain.name.into(),
|
||||
path: toolchain.path.into(),
|
||||
})
|
||||
.collect();
|
||||
let groups = response
|
||||
.groups
|
||||
.into_iter()
|
||||
.filter_map(|group| {
|
||||
Some((usize::try_from(group.start_index).ok()?, group.name.into()))
|
||||
})
|
||||
.collect();
|
||||
Some(ToolchainList {
|
||||
toolchains,
|
||||
default: None,
|
||||
groups,
|
||||
})
|
||||
})
|
||||
}
|
||||
pub(crate) fn active_toolchain(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &AppContext,
|
||||
) -> Task<Option<Toolchain>> {
|
||||
let project_id = self.project_id;
|
||||
let client = self.client.clone();
|
||||
cx.spawn(move |_| async move {
|
||||
let response = client
|
||||
.request(proto::ActiveToolchain {
|
||||
project_id,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
language_name: language_name.clone().into(),
|
||||
})
|
||||
.await
|
||||
.log_err()?;
|
||||
|
||||
response.toolchain.map(|toolchain| Toolchain {
|
||||
language_name: language_name.clone(),
|
||||
name: toolchain.name.into(),
|
||||
path: toolchain.path.into(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,8 @@ impl WorktreeStore {
|
||||
client.add_model_request_handler(Self::handle_copy_project_entry);
|
||||
client.add_model_request_handler(Self::handle_delete_project_entry);
|
||||
client.add_model_request_handler(Self::handle_expand_project_entry);
|
||||
client.add_model_request_handler(Self::handle_git_branches);
|
||||
client.add_model_request_handler(Self::handle_update_branch);
|
||||
}
|
||||
|
||||
pub fn local(retain_worktrees: bool, fs: Arc<dyn Fs>) -> Self {
|
||||
@@ -127,6 +129,13 @@ impl WorktreeStore {
|
||||
.find(|worktree| worktree.read(cx).id() == id)
|
||||
}
|
||||
|
||||
pub fn current_branch(&self, repository: ProjectPath, cx: &AppContext) -> Option<Arc<str>> {
|
||||
self.worktree_for_id(repository.worktree_id, cx)?
|
||||
.read(cx)
|
||||
.git_entry(repository.path)?
|
||||
.branch()
|
||||
}
|
||||
|
||||
pub fn worktree_for_entry(
|
||||
&self,
|
||||
entry_id: ProjectEntryId,
|
||||
@@ -836,6 +845,131 @@ impl WorktreeStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn branches(
|
||||
&self,
|
||||
project_path: ProjectPath,
|
||||
cx: &AppContext,
|
||||
) -> Task<Result<Vec<git::repository::Branch>>> {
|
||||
let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) else {
|
||||
return Task::ready(Err(anyhow!("No worktree found for ProjectPath")));
|
||||
};
|
||||
|
||||
match worktree.read(cx) {
|
||||
Worktree::Local(local_worktree) => {
|
||||
let branches = util::maybe!({
|
||||
let worktree_error = |error| {
|
||||
format!(
|
||||
"{} for worktree {}",
|
||||
error,
|
||||
local_worktree.abs_path().to_string_lossy()
|
||||
)
|
||||
};
|
||||
|
||||
let entry = local_worktree
|
||||
.git_entry(project_path.path)
|
||||
.with_context(|| worktree_error("No git entry found"))?;
|
||||
|
||||
let repo = local_worktree
|
||||
.get_local_repo(&entry)
|
||||
.with_context(|| worktree_error("No repository found"))?
|
||||
.repo()
|
||||
.clone();
|
||||
|
||||
repo.branches()
|
||||
});
|
||||
|
||||
Task::ready(branches)
|
||||
}
|
||||
Worktree::Remote(remote_worktree) => {
|
||||
let request = remote_worktree.client().request(proto::GitBranches {
|
||||
project_id: remote_worktree.project_id(),
|
||||
repository: Some(proto::ProjectPath {
|
||||
worktree_id: project_path.worktree_id.to_proto(),
|
||||
path: project_path.path.to_string_lossy().to_string(), // Root path
|
||||
}),
|
||||
});
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
let response = request.await?;
|
||||
|
||||
let branches = response
|
||||
.branches
|
||||
.into_iter()
|
||||
.map(|proto_branch| git::repository::Branch {
|
||||
is_head: proto_branch.is_head,
|
||||
name: proto_branch.name.into(),
|
||||
unix_timestamp: proto_branch
|
||||
.unix_timestamp
|
||||
.map(|timestamp| timestamp as i64),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(branches)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_or_create_branch(
|
||||
&self,
|
||||
repository: ProjectPath,
|
||||
new_branch: String,
|
||||
cx: &AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(worktree) = self.worktree_for_id(repository.worktree_id, cx) else {
|
||||
return Task::ready(Err(anyhow!("No worktree found for ProjectPath")));
|
||||
};
|
||||
|
||||
match worktree.read(cx) {
|
||||
Worktree::Local(local_worktree) => {
|
||||
let result = util::maybe!({
|
||||
let worktree_error = |error| {
|
||||
format!(
|
||||
"{} for worktree {}",
|
||||
error,
|
||||
local_worktree.abs_path().to_string_lossy()
|
||||
)
|
||||
};
|
||||
|
||||
let entry = local_worktree
|
||||
.git_entry(repository.path)
|
||||
.with_context(|| worktree_error("No git entry found"))?;
|
||||
|
||||
let repo = local_worktree
|
||||
.get_local_repo(&entry)
|
||||
.with_context(|| worktree_error("No repository found"))?
|
||||
.repo()
|
||||
.clone();
|
||||
|
||||
if !repo.branch_exits(&new_branch)? {
|
||||
repo.create_branch(&new_branch)?;
|
||||
}
|
||||
|
||||
repo.change_branch(&new_branch)?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
Task::ready(result)
|
||||
}
|
||||
Worktree::Remote(remote_worktree) => {
|
||||
let request = remote_worktree.client().request(proto::UpdateGitBranch {
|
||||
project_id: remote_worktree.project_id(),
|
||||
repository: Some(proto::ProjectPath {
|
||||
worktree_id: repository.worktree_id.to_proto(),
|
||||
path: repository.path.to_string_lossy().to_string(), // Root path
|
||||
}),
|
||||
branch_name: new_branch,
|
||||
});
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
request.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn filter_paths(
|
||||
fs: &Arc<dyn Fs>,
|
||||
mut input: Receiver<MatchingEntry>,
|
||||
@@ -917,6 +1051,61 @@ impl WorktreeStore {
|
||||
.ok_or_else(|| anyhow!("invalid request"))?;
|
||||
Worktree::handle_expand_entry(worktree, envelope.payload, cx).await
|
||||
}
|
||||
|
||||
pub async fn handle_git_branches(
|
||||
this: Model<Self>,
|
||||
branches: TypedEnvelope<proto::GitBranches>,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<proto::GitBranchesResponse> {
|
||||
let project_path = branches
|
||||
.payload
|
||||
.repository
|
||||
.clone()
|
||||
.context("Invalid GitBranches call")?;
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: WorktreeId::from_proto(project_path.worktree_id),
|
||||
path: Path::new(&project_path.path).into(),
|
||||
};
|
||||
|
||||
let branches = this
|
||||
.read_with(&cx, |this, cx| this.branches(project_path, cx))?
|
||||
.await?;
|
||||
|
||||
Ok(proto::GitBranchesResponse {
|
||||
branches: branches
|
||||
.into_iter()
|
||||
.map(|branch| proto::Branch {
|
||||
is_head: branch.is_head,
|
||||
name: branch.name.to_string(),
|
||||
unix_timestamp: branch.unix_timestamp.map(|timestamp| timestamp as u64),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn handle_update_branch(
|
||||
this: Model<Self>,
|
||||
update_branch: TypedEnvelope<proto::UpdateGitBranch>,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<proto::Ack> {
|
||||
let project_path = update_branch
|
||||
.payload
|
||||
.repository
|
||||
.clone()
|
||||
.context("Invalid GitBranches call")?;
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: WorktreeId::from_proto(project_path.worktree_id),
|
||||
path: Path::new(&project_path.path).into(),
|
||||
};
|
||||
let new_branch = update_branch.payload.branch_name;
|
||||
|
||||
this.read_with(&cx, |this, cx| {
|
||||
this.update_or_create_branch(project_path, new_branch, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -2821,6 +2821,17 @@ impl ProjectPanel {
|
||||
return None;
|
||||
}
|
||||
|
||||
let scroll_handle = self.scroll_handle.0.borrow();
|
||||
let longest_item_width = scroll_handle
|
||||
.last_item_size
|
||||
.filter(|size| size.contents.width > size.item.width)?
|
||||
.contents
|
||||
.width
|
||||
.0 as f64;
|
||||
if longest_item_width < scroll_handle.base_handle.bounds().size.width.0 as f64 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
div()
|
||||
.occlude()
|
||||
|
||||
@@ -196,8 +196,6 @@ message Envelope {
|
||||
GetImplementation get_implementation = 162;
|
||||
GetImplementationResponse get_implementation_response = 163;
|
||||
|
||||
JoinHostedProject join_hosted_project = 164;
|
||||
|
||||
CountLanguageModelTokens count_language_model_tokens = 230;
|
||||
CountLanguageModelTokensResponse count_language_model_tokens_response = 231;
|
||||
GetCachedEmbeddings get_cached_embeddings = 189;
|
||||
@@ -261,9 +259,6 @@ message Envelope {
|
||||
CloseBuffer close_buffer = 245;
|
||||
UpdateUserSettings update_user_settings = 246;
|
||||
|
||||
CheckFileExists check_file_exists = 255;
|
||||
CheckFileExistsResponse check_file_exists_response = 256;
|
||||
|
||||
ShutdownRemoteServer shutdown_remote_server = 257;
|
||||
|
||||
RemoveWorktree remove_worktree = 258;
|
||||
@@ -281,12 +276,25 @@ message Envelope {
|
||||
FlushBufferedMessages flush_buffered_messages = 267;
|
||||
|
||||
LanguageServerPromptRequest language_server_prompt_request = 268;
|
||||
LanguageServerPromptResponse language_server_prompt_response = 269; // current max
|
||||
}
|
||||
LanguageServerPromptResponse language_server_prompt_response = 269;
|
||||
GitBranches git_branches = 270;
|
||||
GitBranchesResponse git_branches_response = 271;
|
||||
|
||||
UpdateGitBranch update_git_branch = 272;
|
||||
|
||||
ListToolchains list_toolchains = 273;
|
||||
ListToolchainsResponse list_toolchains_response = 274;
|
||||
ActivateToolchain activate_toolchain = 275;
|
||||
ActiveToolchain active_toolchain = 276;
|
||||
ActiveToolchainResponse active_toolchain_response = 277;
|
||||
|
||||
GetPathMetadata get_path_metadata = 278;
|
||||
GetPathMetadataResponse get_path_metadata_response = 279; // current max
|
||||
}
|
||||
|
||||
reserved 87 to 88;
|
||||
reserved 158 to 161;
|
||||
reserved 164;
|
||||
reserved 166 to 169;
|
||||
reserved 177 to 185;
|
||||
reserved 188;
|
||||
@@ -297,6 +305,7 @@ message Envelope {
|
||||
reserved 221;
|
||||
reserved 224 to 229;
|
||||
reserved 247 to 254;
|
||||
reserved 255 to 256;
|
||||
}
|
||||
|
||||
// Messages
|
||||
@@ -518,11 +527,6 @@ message JoinProject {
|
||||
uint64 project_id = 1;
|
||||
}
|
||||
|
||||
message JoinHostedProject {
|
||||
uint64 project_id = 1;
|
||||
}
|
||||
|
||||
|
||||
message ListRemoteDirectory {
|
||||
uint64 dev_server_id = 1;
|
||||
string path = 2;
|
||||
@@ -1289,13 +1293,7 @@ message UpdateChannels {
|
||||
repeated ChannelMessageId latest_channel_message_ids = 8;
|
||||
repeated ChannelBufferVersion latest_channel_buffer_versions = 9;
|
||||
|
||||
repeated HostedProject hosted_projects = 10;
|
||||
repeated uint64 deleted_hosted_projects = 11;
|
||||
|
||||
reserved 12;
|
||||
reserved 13;
|
||||
reserved 14;
|
||||
reserved 15;
|
||||
reserved 10 to 15;
|
||||
}
|
||||
|
||||
message UpdateUserChannels {
|
||||
@@ -1324,13 +1322,6 @@ message ChannelParticipants {
|
||||
repeated uint64 participant_user_ids = 2;
|
||||
}
|
||||
|
||||
message HostedProject {
|
||||
uint64 project_id = 1;
|
||||
uint64 channel_id = 2;
|
||||
string name = 3;
|
||||
ChannelVisibility visibility = 4;
|
||||
}
|
||||
|
||||
message JoinChannel {
|
||||
uint64 channel_id = 1;
|
||||
}
|
||||
@@ -2367,14 +2358,15 @@ message UpdateUserSettings {
|
||||
}
|
||||
}
|
||||
|
||||
message CheckFileExists {
|
||||
message GetPathMetadata {
|
||||
uint64 project_id = 1;
|
||||
string path = 2;
|
||||
}
|
||||
|
||||
message CheckFileExistsResponse {
|
||||
message GetPathMetadataResponse {
|
||||
bool exists = 1;
|
||||
string path = 2;
|
||||
bool is_dir = 3;
|
||||
}
|
||||
|
||||
message ShutdownRemoteServer {}
|
||||
@@ -2407,7 +2399,6 @@ message GetPermalinkToLine {
|
||||
message GetPermalinkToLineResponse {
|
||||
string permalink = 1;
|
||||
}
|
||||
|
||||
message FlushBufferedMessages {}
|
||||
message FlushBufferedMessagesResponse {}
|
||||
|
||||
@@ -2432,3 +2423,64 @@ message LanguageServerPromptRequest {
|
||||
message LanguageServerPromptResponse {
|
||||
optional uint64 action_response = 1;
|
||||
}
|
||||
|
||||
message ListToolchains {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
string language_name = 3;
|
||||
}
|
||||
|
||||
message Toolchain {
|
||||
string name = 1;
|
||||
string path = 2;
|
||||
}
|
||||
|
||||
message ToolchainGroup {
|
||||
uint64 start_index = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
message ListToolchainsResponse {
|
||||
repeated Toolchain toolchains = 1;
|
||||
bool has_values = 2;
|
||||
repeated ToolchainGroup groups = 3;
|
||||
}
|
||||
|
||||
message ActivateToolchain {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
Toolchain toolchain = 3;
|
||||
string language_name = 4;
|
||||
}
|
||||
|
||||
message ActiveToolchain {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
string language_name = 3;
|
||||
}
|
||||
|
||||
message ActiveToolchainResponse {
|
||||
optional Toolchain toolchain = 1;
|
||||
}
|
||||
|
||||
message Branch {
|
||||
bool is_head = 1;
|
||||
string name = 2;
|
||||
optional uint64 unix_timestamp = 3;
|
||||
}
|
||||
|
||||
message GitBranches {
|
||||
uint64 project_id = 1;
|
||||
ProjectPath repository = 2;
|
||||
}
|
||||
|
||||
message GitBranchesResponse {
|
||||
repeated Branch branches = 1;
|
||||
}
|
||||
|
||||
message UpdateGitBranch {
|
||||
uint64 project_id = 1;
|
||||
string branch_name = 2;
|
||||
ProjectPath repository = 3;
|
||||
|
||||
}
|
||||
|
||||
@@ -228,7 +228,6 @@ messages!(
|
||||
(JoinChannelChat, Foreground),
|
||||
(JoinChannelChatResponse, Foreground),
|
||||
(JoinProject, Foreground),
|
||||
(JoinHostedProject, Foreground),
|
||||
(JoinProjectResponse, Foreground),
|
||||
(JoinRoom, Foreground),
|
||||
(JoinRoomResponse, Foreground),
|
||||
@@ -344,8 +343,6 @@ messages!(
|
||||
(FindSearchCandidatesResponse, Background),
|
||||
(CloseBuffer, Foreground),
|
||||
(UpdateUserSettings, Foreground),
|
||||
(CheckFileExists, Background),
|
||||
(CheckFileExistsResponse, Background),
|
||||
(ShutdownRemoteServer, Foreground),
|
||||
(RemoveWorktree, Foreground),
|
||||
(LanguageServerLog, Foreground),
|
||||
@@ -357,6 +354,16 @@ messages!(
|
||||
(FlushBufferedMessages, Foreground),
|
||||
(LanguageServerPromptRequest, Foreground),
|
||||
(LanguageServerPromptResponse, Foreground),
|
||||
(GitBranches, Background),
|
||||
(GitBranchesResponse, Background),
|
||||
(UpdateGitBranch, Background),
|
||||
(ListToolchains, Foreground),
|
||||
(ListToolchainsResponse, Foreground),
|
||||
(ActivateToolchain, Foreground),
|
||||
(ActiveToolchain, Foreground),
|
||||
(ActiveToolchainResponse, Foreground),
|
||||
(GetPathMetadata, Background),
|
||||
(GetPathMetadataResponse, Background)
|
||||
);
|
||||
|
||||
request_messages!(
|
||||
@@ -408,7 +415,6 @@ request_messages!(
|
||||
(JoinChannel, JoinRoomResponse),
|
||||
(JoinChannelBuffer, JoinChannelBufferResponse),
|
||||
(JoinChannelChat, JoinChannelChatResponse),
|
||||
(JoinHostedProject, JoinProjectResponse),
|
||||
(JoinProject, JoinProjectResponse),
|
||||
(JoinRoom, JoinRoomResponse),
|
||||
(LeaveChannelBuffer, Ack),
|
||||
@@ -466,13 +472,18 @@ request_messages!(
|
||||
(SynchronizeContexts, SynchronizeContextsResponse),
|
||||
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
|
||||
(AddWorktree, AddWorktreeResponse),
|
||||
(CheckFileExists, CheckFileExistsResponse),
|
||||
(ShutdownRemoteServer, Ack),
|
||||
(RemoveWorktree, Ack),
|
||||
(OpenServerSettings, OpenBufferResponse),
|
||||
(GetPermalinkToLine, GetPermalinkToLineResponse),
|
||||
(FlushBufferedMessages, Ack),
|
||||
(LanguageServerPromptRequest, LanguageServerPromptResponse),
|
||||
(GitBranches, GitBranchesResponse),
|
||||
(UpdateGitBranch, Ack),
|
||||
(ListToolchains, ListToolchainsResponse),
|
||||
(ActivateToolchain, Ack),
|
||||
(ActiveToolchain, ActiveToolchainResponse),
|
||||
(GetPathMetadata, GetPathMetadataResponse)
|
||||
);
|
||||
|
||||
entity_messages!(
|
||||
@@ -544,13 +555,18 @@ entity_messages!(
|
||||
SynchronizeContexts,
|
||||
LspExtSwitchSourceHeader,
|
||||
UpdateUserSettings,
|
||||
CheckFileExists,
|
||||
LanguageServerLog,
|
||||
Toast,
|
||||
HideToast,
|
||||
OpenServerSettings,
|
||||
GetPermalinkToLine,
|
||||
LanguageServerPromptRequest
|
||||
LanguageServerPromptRequest,
|
||||
GitBranches,
|
||||
UpdateGitBranch,
|
||||
ListToolchains,
|
||||
ActivateToolchain,
|
||||
ActiveToolchain,
|
||||
GetPathMetadata
|
||||
);
|
||||
|
||||
entity_messages!(
|
||||
|
||||
@@ -11,7 +11,7 @@ use ui::{
|
||||
};
|
||||
use workspace::{notifications::DetachAndPromptErr, ModalView, OpenOptions, Workspace};
|
||||
|
||||
use crate::{open_ssh_project, SshSettings};
|
||||
use crate::open_ssh_project;
|
||||
|
||||
enum Host {
|
||||
RemoteProject,
|
||||
@@ -102,16 +102,6 @@ impl DisconnectedOverlay {
|
||||
let paths = ssh_project.paths.iter().map(PathBuf::from).collect();
|
||||
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
let nickname = cx
|
||||
.update(|cx| {
|
||||
SshSettings::get_global(cx).nickname_for(
|
||||
&connection_options.host,
|
||||
connection_options.port,
|
||||
&connection_options.username,
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
open_ssh_project(
|
||||
connection_options,
|
||||
paths,
|
||||
@@ -120,7 +110,6 @@ impl DisconnectedOverlay {
|
||||
replace_window: Some(window),
|
||||
..Default::default()
|
||||
},
|
||||
nickname,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod disconnected_overlay;
|
||||
mod remote_servers;
|
||||
mod ssh_connections;
|
||||
use remote::SshConnectionOptions;
|
||||
pub use ssh_connections::open_ssh_project;
|
||||
|
||||
use disconnected_overlay::DisconnectedOverlay;
|
||||
@@ -331,23 +330,12 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let args = SshSettings::get_global(cx).args_for(
|
||||
&ssh_project.host,
|
||||
ssh_project.port,
|
||||
&ssh_project.user,
|
||||
);
|
||||
let nickname = SshSettings::get_global(cx).nickname_for(
|
||||
&ssh_project.host,
|
||||
ssh_project.port,
|
||||
&ssh_project.user,
|
||||
);
|
||||
let connection_options = SshConnectionOptions {
|
||||
host: ssh_project.host.clone(),
|
||||
username: ssh_project.user.clone(),
|
||||
port: ssh_project.port,
|
||||
password: None,
|
||||
args,
|
||||
};
|
||||
let connection_options = SshSettings::get_global(cx)
|
||||
.connection_options_for(
|
||||
ssh_project.host.clone(),
|
||||
ssh_project.port,
|
||||
ssh_project.user.clone(),
|
||||
);
|
||||
|
||||
let paths = ssh_project.paths.iter().map(PathBuf::from).collect();
|
||||
|
||||
@@ -357,7 +345,6 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
paths,
|
||||
app_state,
|
||||
open_options,
|
||||
nickname,
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user