Compare commits
1 Commits
git-panel-
...
refactor-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a55062dd1 |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -39,13 +39,10 @@ jobs:
|
||||
# When we're running in a merge queue, never assume that the changes
|
||||
# are docs-only, as there could be other PRs in the group that
|
||||
# contain non-docs changes.
|
||||
echo "Running in the merge queue"
|
||||
echo "docs_only=false" >> $GITHUB_OUTPUT
|
||||
elif git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -qvE '^docs/'; then
|
||||
echo "Detected non-docs changes"
|
||||
echo "docs_only=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Docs-only change"
|
||||
echo "docs_only=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
@@ -322,6 +319,9 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate license file
|
||||
run: script/generate-licenses
|
||||
|
||||
- name: Create macOS app bundle
|
||||
run: script/bundle-mac
|
||||
|
||||
|
||||
3
.github/workflows/release_nightly.yml
vendored
3
.github/workflows/release_nightly.yml
vendored
@@ -86,6 +86,9 @@ jobs:
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
|
||||
- name: Generate license file
|
||||
run: script/generate-licenses
|
||||
|
||||
- name: Create macOS app bundle
|
||||
run: script/bundle-mac
|
||||
|
||||
|
||||
69
Cargo.lock
generated
69
Cargo.lock
generated
@@ -5190,8 +5190,6 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"sum_tree",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
@@ -8413,7 +8411,8 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
[[package]]
|
||||
name = "oo7"
|
||||
version = "0.3.3"
|
||||
source = "git+https://github.com/zed-industries/oo7?branch=avoid-crypto-panic#9d5d5fcd7e4e0add9b420ffb58f67661b0b37568"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc6ce4692fbfd044ce22ca07dcab1a30fa12432ca2aa5b1294eca50d3332a24"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"async-fs 2.1.2",
|
||||
@@ -8955,7 +8954,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger 0.10.2",
|
||||
@@ -8974,7 +8973,6 @@ dependencies = [
|
||||
"pet-mac-python-org",
|
||||
"pet-mac-xcode",
|
||||
"pet-pipenv",
|
||||
"pet-pixi",
|
||||
"pet-poetry",
|
||||
"pet-pyenv",
|
||||
"pet-python-utils",
|
||||
@@ -8992,7 +8990,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-conda"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
@@ -9011,7 +9009,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-core"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"lazy_static",
|
||||
@@ -9026,7 +9024,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-env-var-path"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -9042,7 +9040,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-fs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -9051,7 +9049,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-global-virtualenvs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -9064,7 +9062,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-homebrew"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -9082,7 +9080,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-jsonrpc"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"log",
|
||||
@@ -9095,7 +9093,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-linux-global-python"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -9108,7 +9106,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-mac-commandlinetools"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -9121,7 +9119,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-mac-python-org"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -9134,7 +9132,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-mac-xcode"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -9147,7 +9145,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-pipenv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -9157,22 +9155,10 @@ dependencies = [
|
||||
"pet-virtualenv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-pixi"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
"pet-conda",
|
||||
"pet-core",
|
||||
"pet-python-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-poetry"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"lazy_static",
|
||||
@@ -9193,7 +9179,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-pyenv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -9211,7 +9197,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-python-utils"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
@@ -9228,7 +9214,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-reporter"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"log",
|
||||
@@ -9242,7 +9228,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-telemetry"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
@@ -9257,7 +9243,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-venv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -9269,7 +9255,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-virtualenv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -9281,7 +9267,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-virtualenvwrapper"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -9294,7 +9280,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-windows-registry"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -9312,7 +9298,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-windows-store"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -10357,6 +10343,7 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"itertools 0.13.0",
|
||||
"language",
|
||||
"log",
|
||||
"markdown",
|
||||
@@ -13829,9 +13816,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-python"
|
||||
version = "0.23.6"
|
||||
version = "0.23.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d065aaa27f3aaceaf60c1f0e0ac09e1cb9eb8ed28e7bcdaa52129cffc7f4b04"
|
||||
checksum = "70beaa47e19e1529e8787fc0a80ebbae5a9fdaefc5fcc8972c885c9abf6ab0f0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@@ -409,13 +409,12 @@ 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 = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
||||
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
||||
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
||||
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
||||
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
||||
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
||||
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-fs = { 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 = { version = "1.3.0", features = ["unstable"] }
|
||||
profiling = "1"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9416 2.99643C13.08 2.79636 12.9568 2.5 12.7352 2.5H3.26475C3.04317 2.5 2.91999 2.79636 3.0584 2.99643L6.04033 7.30646C6.24713 7.60535 6.35981 7.97674 6.35981 8.3596C6.35981 9.18422 6.35981 11.4639 6.35981 12.891C6.35981 13.2285 6.59643 13.5 6.88831 13.5H9.11168C9.40357 13.5 9.64019 13.2285 9.64019 12.891C9.64019 11.4639 9.64019 9.18422 9.64019 8.3596C9.64019 7.97674 9.75289 7.60535 9.95969 7.30646L12.9416 2.99643Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6749 2.40608C11.8058 2.24239 11.6893 1.99991 11.4796 1.99991H2.51996C2.31033 1.99991 2.19379 2.24239 2.32474 2.40608L5.14583 5.93246C5.34148 6.17701 5.44808 6.48087 5.44808 6.79412C5.44808 7.46881 5.44808 10.334 5.44808 11.5016C5.44808 11.7778 5.67194 11.9999 5.94808 11.9999H8.05153C8.32767 11.9999 8.55153 11.7778 8.55153 11.5016C8.55153 10.334 8.55153 7.46881 8.55153 6.79412C8.55153 6.48087 8.65815 6.17701 8.8538 5.93246L11.6749 2.40608Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 618 B After Width: | Height: | Size: 644 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 13L10.4138 10.4138M3 7.31034C3 4.92981 4.92981 3 7.31034 3C9.6909 3 11.6207 4.92981 11.6207 7.31034C11.6207 9.6909 9.6909 11.6207 7.31034 11.6207C4.92981 11.6207 3 9.6909 3 7.31034Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 12L9.41379 9.41379M2 6.31034C2 3.92981 3.92981 2 6.31034 2C8.6909 2 10.6207 3.92981 10.6207 6.31034C10.6207 8.6909 8.6909 10.6207 6.31034 10.6207C3.92981 10.6207 2 8.6909 2 6.31034Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 382 B After Width: | Height: | Size: 383 B |
@@ -172,7 +172,6 @@
|
||||
"context": "AssistantPanel",
|
||||
"bindings": {
|
||||
"ctrl-k c": "assistant::CopyCode",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-g": "search::SelectNextMatch",
|
||||
"ctrl-shift-g": "search::SelectPrevMatch",
|
||||
"ctrl-shift-m": "assistant::ToggleModelSelector",
|
||||
@@ -265,7 +264,7 @@
|
||||
"ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
|
||||
"ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||
"ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||
"ctrl-shift-f": "pane::DeploySearch",
|
||||
"ctrl-shift-f": "project_search::ToggleFocus",
|
||||
"ctrl-alt-g": "search::SelectNextMatch",
|
||||
"ctrl-alt-shift-g": "search::SelectPrevMatch",
|
||||
"ctrl-alt-shift-h": "search::ToggleReplace",
|
||||
@@ -412,7 +411,7 @@
|
||||
"ctrl-shift-p": "command_palette::Toggle",
|
||||
"f1": "command_palette::Toggle",
|
||||
"ctrl-shift-m": "diagnostics::Deploy",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-shift-b": "outline_panel::ToggleFocus",
|
||||
"ctrl-?": "assistant::ToggleFocus",
|
||||
"ctrl-alt-s": "workspace::SaveAll",
|
||||
@@ -533,7 +532,6 @@
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"shift-enter": "editor::ExpandExcerpts",
|
||||
"ctrl-k enter": "editor::OpenExcerptsSplit",
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-f8": "editor::GoToHunk",
|
||||
"ctrl-shift-f8": "editor::GoToPrevHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist"
|
||||
@@ -614,6 +612,7 @@
|
||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
|
||||
@@ -196,7 +196,6 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-k c": "assistant::CopyCode",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPrevMatch",
|
||||
"cmd-shift-m": "assistant::ToggleModelSelector",
|
||||
@@ -227,8 +226,7 @@
|
||||
"cmd-n": "assistant2::NewThread",
|
||||
"cmd-shift-h": "assistant2::OpenHistory",
|
||||
"cmd-shift-m": "assistant2::ToggleModelSelector",
|
||||
"cmd-shift-a": "assistant2::ToggleContextPicker",
|
||||
"cmd-alt-e": "assistant2::RemoveAllContext"
|
||||
"cmd-shift-a": "assistant2::ToggleContextPicker"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -436,7 +434,7 @@
|
||||
"ctrl--": "pane::GoBack",
|
||||
"ctrl-shift--": "pane::GoForward",
|
||||
"cmd-shift-t": "pane::ReopenClosedItem",
|
||||
"cmd-shift-f": "pane::DeploySearch"
|
||||
"cmd-shift-f": "project_search::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -477,7 +475,7 @@
|
||||
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
|
||||
"cmd-shift-p": "command_palette::Toggle",
|
||||
"cmd-shift-m": "diagnostics::Deploy",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-shift-e": "pane::RevealInProjectPanel",
|
||||
"cmd-shift-b": "outline_panel::ToggleFocus",
|
||||
"cmd-?": "assistant::ToggleFocus",
|
||||
"cmd-alt-s": "workspace::SaveAll",
|
||||
@@ -597,7 +595,6 @@
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"shift-enter": "editor::ExpandExcerpts",
|
||||
"cmd-k enter": "editor::OpenExcerptsSplit",
|
||||
"cmd-shift-e": "pane::RevealInProjectPanel",
|
||||
"cmd-f8": "editor::GoToHunk",
|
||||
"cmd-shift-f8": "editor::GoToPrevHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist"
|
||||
@@ -616,7 +613,6 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-a": "assistant2::ToggleContextPicker",
|
||||
"cmd-alt-e": "assistant2::RemoveAllContext",
|
||||
"ctrl-[": "assistant::CyclePreviousInlineAssist",
|
||||
"ctrl-]": "assistant::CycleNextInlineAssist"
|
||||
}
|
||||
@@ -667,6 +663,7 @@
|
||||
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-cmd-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"cmd-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
@@ -681,38 +678,6 @@
|
||||
"space": "project_panel::Open"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && !CommitEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "git_panel::Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && ChangesList",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "menu::SelectPrev",
|
||||
"down": "menu::SelectNext",
|
||||
"cmd-up": "menu::SelectFirst",
|
||||
"cmd-down": "menu::SelectLast",
|
||||
"enter": "menu::Confirm",
|
||||
"space": "git::ToggleStaged",
|
||||
"cmd-shift-space": "git::StageAll",
|
||||
"ctrl-shift-space": "git::UnstageAll",
|
||||
"alt-down": "git_panel::FocusEditor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && CommitEditor > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-up": "git_panel::FocusChanges",
|
||||
"escape": "git_panel::FocusChanges",
|
||||
"cmd-enter": "git::CommitChanges",
|
||||
"cmd-alt-enter": "git::CommitAllChanges"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "CollabPanel && not_editing",
|
||||
"use_key_equivalents": true,
|
||||
|
||||
@@ -57,9 +57,7 @@
|
||||
{
|
||||
"context": "Workspace && !Terminal",
|
||||
"bindings": {
|
||||
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
|
||||
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
|
||||
"ctrl-x 5 2": "workspace::NewWindow", // make-frame-command
|
||||
"ctrl-x ctrl-c": "workspace::CloseWindow", // kill-emacs
|
||||
"ctrl-x o": "workspace::ActivateNextPane", // other-window
|
||||
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
|
||||
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
|
||||
|
||||
@@ -55,11 +55,9 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"context": "Workspace && !Terminal",
|
||||
"bindings": {
|
||||
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
|
||||
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
|
||||
"ctrl-x 5 2": "workspace::NewWindow", // make-frame-command
|
||||
"ctrl-x ctrl-c": "workspace::CloseWindow", // kill-emacs
|
||||
"ctrl-x o": "workspace::ActivateNextPane", // other-window
|
||||
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
|
||||
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
|
||||
@@ -72,18 +70,6 @@
|
||||
"ctrl-x s": "workspace::SaveAll" // save-some-buffers
|
||||
}
|
||||
},
|
||||
{
|
||||
// Workaround to enable using emacs in the Zed terminal.
|
||||
// Unbind so Zed ignores these keys and lets emacs handle them.
|
||||
"context": "Terminal",
|
||||
"bindings": {
|
||||
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
|
||||
"ctrl-x ctrl-f": null, // find-file
|
||||
"ctrl-x ctrl-s": null, // save-buffer
|
||||
"ctrl-x ctrl-w": null, // write-file
|
||||
"ctrl-x s": null // save-some-buffers
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar > Editor",
|
||||
"bindings": {
|
||||
|
||||
@@ -197,7 +197,6 @@
|
||||
"d": ["vim::PushOperator", "Delete"],
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"g shift-j": "vim::JoinLinesNoWhitespace",
|
||||
"y": ["vim::PushOperator", "Yank"],
|
||||
"shift-y": "vim::YankLine",
|
||||
"i": "vim::InsertBefore",
|
||||
@@ -260,7 +259,7 @@
|
||||
"shift-d": "vim::VisualDeleteLine",
|
||||
"shift-x": "vim::VisualDeleteLine",
|
||||
"y": "vim::VisualYank",
|
||||
"shift-y": "vim::VisualYankLine",
|
||||
"shift-y": "vim::VisualYank",
|
||||
"p": "vim::Paste",
|
||||
"shift-p": ["vim::Paste", { "preserveClipboard": true }],
|
||||
"s": "vim::Substitute",
|
||||
@@ -279,7 +278,6 @@
|
||||
"g shift-i": "vim::VisualInsertFirstNonWhiteSpace",
|
||||
"g shift-a": "vim::VisualInsertEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"g shift-j": "vim::JoinLinesNoWhitespace",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
@@ -397,7 +395,6 @@
|
||||
"'": "vim::Quotes",
|
||||
"`": "vim::BackQuotes",
|
||||
"\"": "vim::DoubleQuotes",
|
||||
"q": "vim::AnyQuotes",
|
||||
"|": "vim::VerticalBars",
|
||||
"(": "vim::Parentheses",
|
||||
")": "vim::Parentheses",
|
||||
|
||||
@@ -501,11 +501,7 @@
|
||||
// Where to the git panel. Can be 'left' or 'right'.
|
||||
"dock": "left",
|
||||
// Default width of the git panel.
|
||||
"default_width": 360,
|
||||
"status_style": "icon",
|
||||
"scrollbar": {
|
||||
"show": "auto"
|
||||
}
|
||||
"default_width": 360
|
||||
},
|
||||
"message_editor": {
|
||||
// Whether to automatically replace emoji shortcodes with emoji characters.
|
||||
|
||||
@@ -103,7 +103,6 @@ pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
terminal_view = { workspace = true, features = ["test-support"] }
|
||||
text = { workspace = true, features = ["test-support"] }
|
||||
tree-sitter-md.workspace = true
|
||||
unindent.workspace = true
|
||||
|
||||
@@ -228,19 +228,20 @@ impl InlineAssistant {
|
||||
let newest_selection = newest_selection.unwrap();
|
||||
|
||||
let mut codegen_ranges = Vec::new();
|
||||
for (excerpt_id, buffer, buffer_range) in
|
||||
snapshot.excerpts_in_ranges(selections.iter().map(|selection| {
|
||||
for (excerpt, buffer_range) in
|
||||
snapshot.disjoint_ranges_to_buffer_ranges(selections.iter().map(|selection| {
|
||||
snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)
|
||||
}))
|
||||
{
|
||||
let buffer = excerpt.buffer();
|
||||
let start = Anchor {
|
||||
buffer_id: Some(buffer.remote_id()),
|
||||
excerpt_id,
|
||||
excerpt_id: excerpt.id(),
|
||||
text_anchor: buffer.anchor_before(buffer_range.start),
|
||||
};
|
||||
let end = Anchor {
|
||||
buffer_id: Some(buffer.remote_id()),
|
||||
excerpt_id,
|
||||
excerpt_id: excerpt.id(),
|
||||
text_anchor: buffer.anchor_after(buffer_range.end),
|
||||
};
|
||||
codegen_ranges.push(start..end);
|
||||
@@ -798,9 +799,10 @@ impl InlineAssistant {
|
||||
let language_name = assist.editor.upgrade().and_then(|editor| {
|
||||
let multibuffer = editor.read(cx).buffer().read(cx);
|
||||
let multibuffer_snapshot = multibuffer.snapshot(cx);
|
||||
let ranges = multibuffer_snapshot.range_to_buffer_ranges(assist.range.clone());
|
||||
let mut ranges =
|
||||
multibuffer_snapshot.range_to_buffer_ranges(assist.range.clone());
|
||||
ranges
|
||||
.first()
|
||||
.next()
|
||||
.and_then(|(excerpt, _)| excerpt.buffer().language())
|
||||
.map(|language| language.name())
|
||||
});
|
||||
@@ -2625,9 +2627,10 @@ impl CodegenAlternative {
|
||||
) -> Self {
|
||||
let snapshot = multi_buffer.read(cx).snapshot(cx);
|
||||
|
||||
// TODO: Could be made more efficient by using a reverse iterator.
|
||||
let (old_excerpt, _) = snapshot
|
||||
.range_to_buffer_ranges(range.clone())
|
||||
.pop()
|
||||
.last()
|
||||
.unwrap();
|
||||
let old_buffer = cx.new_model(|cx| {
|
||||
let text = old_excerpt.buffer().as_rope().clone();
|
||||
@@ -2872,9 +2875,9 @@ impl CodegenAlternative {
|
||||
let language_name = {
|
||||
let multibuffer = self.buffer.read(cx);
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let ranges = snapshot.range_to_buffer_ranges(self.range.clone());
|
||||
let mut ranges = snapshot.range_to_buffer_ranges(self.range.clone());
|
||||
ranges
|
||||
.first()
|
||||
.next()
|
||||
.and_then(|(excerpt, _)| excerpt.buffer().language())
|
||||
.map(|language| language.name())
|
||||
};
|
||||
|
||||
@@ -137,7 +137,7 @@ impl ActiveThread {
|
||||
inline_code: TextStyleRefinement {
|
||||
font_family: Some(theme_settings.buffer_font.family.clone()),
|
||||
font_size: Some(buffer_font_size.into()),
|
||||
background_color: Some(colors.editor_foreground.opacity(0.1)),
|
||||
background_color: Some(colors.editor_foreground.opacity(0.01)),
|
||||
..Default::default()
|
||||
},
|
||||
link: TextStyleRefinement {
|
||||
@@ -282,11 +282,13 @@ impl ActiveThread {
|
||||
.child(div().p_2p5().text_ui(cx).child(markdown.clone()))
|
||||
.when_some(context, |parent, context| {
|
||||
if !context.is_empty() {
|
||||
parent.child(h_flex().flex_wrap().gap_1().px_1p5().pb_1p5().children(
|
||||
context.iter().map(|context| {
|
||||
ContextPill::new_added(context.clone(), false, None)
|
||||
}),
|
||||
))
|
||||
parent.child(
|
||||
h_flex().flex_wrap().gap_1().px_1p5().pb_1p5().children(
|
||||
context
|
||||
.iter()
|
||||
.map(|context| ContextPill::new(context.clone())),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
parent
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ actions!(
|
||||
NewThread,
|
||||
ToggleContextPicker,
|
||||
ToggleModelSelector,
|
||||
RemoveAllContext,
|
||||
OpenHistory,
|
||||
Chat,
|
||||
CycleNextInlineAssist,
|
||||
|
||||
@@ -300,30 +300,20 @@ impl AssistantPanel {
|
||||
fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
let title = if self.thread.read(cx).is_empty() {
|
||||
SharedString::from("New Thread")
|
||||
} else {
|
||||
self.thread
|
||||
.read(cx)
|
||||
.summary(cx)
|
||||
.unwrap_or_else(|| SharedString::from("Loading Summary…"))
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.id("assistant-toolbar")
|
||||
.px(DynamicSpacing::Base08.rems(cx))
|
||||
.h(Tab::container_height(cx))
|
||||
.flex_none()
|
||||
.justify_between()
|
||||
.gap(DynamicSpacing::Base08.rems(cx))
|
||||
.h(Tab::container_height(cx))
|
||||
.px(DynamicSpacing::Base08.rems(cx))
|
||||
.bg(cx.theme().colors().tab_bar_background)
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(h_flex().child(Label::new(title)))
|
||||
.child(h_flex().children(self.thread.read(cx).summary(cx).map(Label::new)))
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.pl_1p5()
|
||||
.pl_1()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.gap(DynamicSpacing::Base02.rems(cx))
|
||||
|
||||
@@ -257,9 +257,10 @@ impl CodegenAlternative {
|
||||
) -> Self {
|
||||
let snapshot = buffer.read(cx).snapshot(cx);
|
||||
|
||||
// TODO: Could be more efficient by using a reverse iterator.
|
||||
let (old_excerpt, _) = snapshot
|
||||
.range_to_buffer_ranges(range.clone())
|
||||
.pop()
|
||||
.last()
|
||||
.unwrap();
|
||||
let old_buffer = cx.new_model(|cx| {
|
||||
let text = old_excerpt.buffer().as_rope().clone();
|
||||
@@ -475,9 +476,9 @@ impl CodegenAlternative {
|
||||
let language_name = {
|
||||
let multibuffer = self.buffer.read(cx);
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let ranges = snapshot.range_to_buffer_ranges(self.range.clone());
|
||||
let mut ranges = snapshot.range_to_buffer_ranges(self.range.clone());
|
||||
ranges
|
||||
.first()
|
||||
.next()
|
||||
.and_then(|(excerpt, _)| excerpt.buffer().language())
|
||||
.map(|language| language.name())
|
||||
};
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use gpui::SharedString;
|
||||
use language_model::{LanguageModelRequestMessage, MessageContent};
|
||||
use project::ProjectEntryId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::post_inc;
|
||||
|
||||
use crate::thread::ThreadId;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct ContextId(pub(crate) usize);
|
||||
|
||||
@@ -17,18 +20,16 @@ impl ContextId {
|
||||
pub struct Context {
|
||||
pub id: ContextId,
|
||||
pub name: SharedString,
|
||||
pub parent: Option<SharedString>,
|
||||
pub tooltip: Option<SharedString>,
|
||||
pub kind: ContextKind,
|
||||
pub text: SharedString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ContextKind {
|
||||
File,
|
||||
File(ProjectEntryId),
|
||||
Directory,
|
||||
FetchedUrl,
|
||||
Thread,
|
||||
Thread(ThreadId),
|
||||
}
|
||||
|
||||
pub fn attach_context_to_message(
|
||||
@@ -42,7 +43,7 @@ pub fn attach_context_to_message(
|
||||
|
||||
for context in context.into_iter() {
|
||||
match context.kind {
|
||||
ContextKind::File { .. } => {
|
||||
ContextKind::File(_) => {
|
||||
file_context.push_str(&context.text);
|
||||
file_context.push('\n');
|
||||
}
|
||||
@@ -56,7 +57,7 @@ pub fn attach_context_to_message(
|
||||
fetch_context.push_str(&context.text);
|
||||
fetch_context.push('\n');
|
||||
}
|
||||
ContextKind::Thread => {
|
||||
ContextKind::Thread(_) => {
|
||||
thread_context.push_str(&context.name);
|
||||
thread_context.push('\n');
|
||||
thread_context.push_str(&context.text);
|
||||
|
||||
@@ -14,7 +14,6 @@ use ui::{prelude::*, ListItem, ListItemSpacing};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::directory_context_picker::DirectoryContextPicker;
|
||||
use crate::context_picker::fetch_context_picker::FetchContextPicker;
|
||||
use crate::context_picker::file_context_picker::FileContextPicker;
|
||||
@@ -53,24 +52,24 @@ impl ContextPicker {
|
||||
let mut entries = Vec::new();
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "File".into(),
|
||||
kind: ContextKind::File,
|
||||
kind: ContextPickerEntryKind::File,
|
||||
icon: IconName::File,
|
||||
});
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "Folder".into(),
|
||||
kind: ContextKind::Directory,
|
||||
kind: ContextPickerEntryKind::Directory,
|
||||
icon: IconName::Folder,
|
||||
});
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "Fetch".into(),
|
||||
kind: ContextKind::FetchedUrl,
|
||||
kind: ContextPickerEntryKind::FetchedUrl,
|
||||
icon: IconName::Globe,
|
||||
});
|
||||
|
||||
if thread_store.is_some() {
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "Thread".into(),
|
||||
kind: ContextKind::Thread,
|
||||
kind: ContextPickerEntryKind::Thread,
|
||||
icon: IconName::MessageCircle,
|
||||
});
|
||||
}
|
||||
@@ -134,10 +133,18 @@ impl Render for ContextPicker {
|
||||
#[derive(Clone)]
|
||||
struct ContextPickerEntry {
|
||||
name: SharedString,
|
||||
kind: ContextKind,
|
||||
kind: ContextPickerEntryKind,
|
||||
icon: IconName,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ContextPickerEntryKind {
|
||||
File,
|
||||
Directory,
|
||||
FetchedUrl,
|
||||
Thread,
|
||||
}
|
||||
|
||||
pub(crate) struct ContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
@@ -177,7 +184,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
self.context_picker
|
||||
.update(cx, |this, cx| {
|
||||
match entry.kind {
|
||||
ContextKind::File => {
|
||||
ContextPickerEntryKind::File => {
|
||||
this.mode = ContextPickerMode::File(cx.new_view(|cx| {
|
||||
FileContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
@@ -188,7 +195,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::Directory => {
|
||||
ContextPickerEntryKind::Directory => {
|
||||
this.mode = ContextPickerMode::Directory(cx.new_view(|cx| {
|
||||
DirectoryContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
@@ -199,7 +206,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::FetchedUrl => {
|
||||
ContextPickerEntryKind::FetchedUrl => {
|
||||
this.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
|
||||
FetchContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
@@ -210,7 +217,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::Thread => {
|
||||
ContextPickerEntryKind::Thread => {
|
||||
if let Some(thread_store) = self.thread_store.as_ref() {
|
||||
this.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
|
||||
ThreadContextPicker::new(
|
||||
|
||||
@@ -11,8 +11,10 @@ use ui::{prelude::*, ListItem};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::file_context_picker::codeblock_fence_for_path;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::{push_fenced_codeblock, ContextStore};
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
pub struct DirectoryContextPicker {
|
||||
picker: View<Picker<DirectoryContextPickerDelegate>>,
|
||||
@@ -187,22 +189,6 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
return;
|
||||
};
|
||||
let path = mat.path.clone();
|
||||
|
||||
let already_included = self
|
||||
.context_store
|
||||
.update(cx, |context_store, _cx| {
|
||||
if let Some(context_id) = context_store.included_directory(&path) {
|
||||
context_store.remove_context(&context_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(true);
|
||||
if already_included {
|
||||
return;
|
||||
}
|
||||
|
||||
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
@@ -249,15 +235,23 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
let mut text = String::new();
|
||||
|
||||
for buffer in buffers {
|
||||
let buffer = buffer.read(cx);
|
||||
let path = buffer.file().map_or(&path, |file| file.path());
|
||||
push_fenced_codeblock(&path, buffer.text(), &mut text);
|
||||
text.push_str(&codeblock_fence_for_path(Some(&path), None));
|
||||
text.push_str(&buffer.read(cx).text());
|
||||
if !text.ends_with('\n') {
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
text.push_str("```\n");
|
||||
}
|
||||
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, _cx| {
|
||||
context_store.insert_directory(&path, text);
|
||||
context_store.insert_context(
|
||||
ContextKind::Directory,
|
||||
path.to_string_lossy().to_string(),
|
||||
text,
|
||||
);
|
||||
})?;
|
||||
|
||||
match confirm_behavior {
|
||||
@@ -286,35 +280,16 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
_cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let path_match = &self.matches[ix];
|
||||
let directory_name = path_match.path.to_string_lossy().to_string();
|
||||
|
||||
let added = self.context_store.upgrade().map_or(false, |context_store| {
|
||||
context_store
|
||||
.read(cx)
|
||||
.included_directory(&path_match.path)
|
||||
.is_some()
|
||||
});
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.toggle_state(selected)
|
||||
.child(h_flex().gap_2().child(Label::new(directory_name)))
|
||||
.when(added, |el| {
|
||||
el.end_slot(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.child(Label::new("Added").size(LabelSize::Small)),
|
||||
)
|
||||
}),
|
||||
.child(h_flex().gap_2().child(Label::new(directory_name))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use picker::{Picker, PickerDelegate};
|
||||
use ui::{prelude::*, ListItem, ViewContext};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
@@ -200,9 +201,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, _cx| {
|
||||
if context_store.included_url(&url).is_none() {
|
||||
context_store.insert_fetched_url(url, text);
|
||||
}
|
||||
context_store.insert_context(ContextKind::FetchedUrl, url, text);
|
||||
})?;
|
||||
|
||||
match confirm_behavior {
|
||||
@@ -231,29 +230,13 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
_cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let added = self.context_store.upgrade().map_or(false, |context_store| {
|
||||
context_store.read(cx).included_url(&self.url).is_some()
|
||||
});
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.toggle_state(selected)
|
||||
.child(Label::new(self.url.clone()))
|
||||
.when(added, |child| {
|
||||
child.disabled(true).end_slot(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.child(Label::new("Added").size(LabelSize::Small)),
|
||||
)
|
||||
}),
|
||||
.child(Label::new(self.url.clone())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
@@ -6,12 +8,13 @@ use fuzzy::PathMatch;
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
||||
use ui::{prelude::*, ListItem, Tooltip};
|
||||
use ui::{prelude::*, ListItem};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::{ContextStore, IncludedFile};
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
pub struct FileContextPicker {
|
||||
picker: View<Picker<FileContextPickerDelegate>>,
|
||||
@@ -201,37 +204,20 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
return;
|
||||
};
|
||||
let path = mat.path.clone();
|
||||
|
||||
let already_included = self
|
||||
.context_store
|
||||
.update(cx, |context_store, _cx| {
|
||||
match context_store.included_file(&path) {
|
||||
Some(IncludedFile::Direct(context_id)) => {
|
||||
context_store.remove_context(&context_id);
|
||||
true
|
||||
}
|
||||
Some(IncludedFile::InDirectory(_)) => true,
|
||||
None => false,
|
||||
}
|
||||
})
|
||||
.unwrap_or(true);
|
||||
if already_included {
|
||||
return;
|
||||
}
|
||||
|
||||
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let Some(open_buffer_task) = project
|
||||
let Some((entry_id, open_buffer_task)) = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: path.clone(),
|
||||
};
|
||||
|
||||
let entry_id = project.entry_for_path(&project_path, cx)?.id;
|
||||
let task = project.open_buffer(project_path, cx);
|
||||
|
||||
Some(task)
|
||||
Some((entry_id, task))
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
@@ -245,7 +231,20 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
context_store.insert_file(buffer.read(cx));
|
||||
let mut text = String::new();
|
||||
text.push_str(&codeblock_fence_for_path(Some(&path), None));
|
||||
text.push_str(&buffer.read(cx).text());
|
||||
if !text.ends_with('\n') {
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
text.push_str("```\n");
|
||||
|
||||
context_store.insert_context(
|
||||
ContextKind::File(entry_id),
|
||||
path.to_string_lossy().to_string(),
|
||||
text,
|
||||
);
|
||||
})?;
|
||||
|
||||
match confirm_behavior {
|
||||
@@ -274,7 +273,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
_cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let path_match = &self.matches[ix];
|
||||
|
||||
@@ -302,52 +301,42 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
(file_name, Some(directory))
|
||||
};
|
||||
|
||||
let added = self
|
||||
.context_store
|
||||
.upgrade()
|
||||
.and_then(|context_store| context_store.read(cx).included_file(&path_match.path));
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.toggle_state(selected)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Label::new(file_name))
|
||||
.children(directory.map(|directory| {
|
||||
Label::new(directory)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
})),
|
||||
)
|
||||
.when_some(added, |el, added| match added {
|
||||
IncludedFile::Direct(_) => el.end_slot(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.child(Label::new("Added").size(LabelSize::Small)),
|
||||
),
|
||||
IncludedFile::InDirectory(dir_name) => {
|
||||
let dir_name = dir_name.to_string_lossy().into_owned();
|
||||
|
||||
el.end_slot(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.child(Label::new("Included").size(LabelSize::Small)),
|
||||
)
|
||||
.tooltip(move |cx| Tooltip::text(format!("in {dir_name}"), cx))
|
||||
}
|
||||
}),
|
||||
ListItem::new(ix).inset(true).toggle_state(selected).child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Label::new(file_name))
|
||||
.children(directory.map(|directory| {
|
||||
Label::new(directory)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
})),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn codeblock_fence_for_path(
|
||||
path: Option<&Path>,
|
||||
row_range: Option<RangeInclusive<u32>>,
|
||||
) -> String {
|
||||
let mut text = String::new();
|
||||
write!(text, "```").unwrap();
|
||||
|
||||
if let Some(path) = path {
|
||||
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
|
||||
write!(text, "{} ", extension).unwrap();
|
||||
}
|
||||
|
||||
write!(text, "{}", path.display()).unwrap();
|
||||
} else {
|
||||
write!(text, "untitled").unwrap();
|
||||
}
|
||||
|
||||
if let Some(row_range) = row_range {
|
||||
write!(text, ":{}-{}", row_range.start() + 1, row_range.end() + 1).unwrap();
|
||||
}
|
||||
|
||||
text.push('\n');
|
||||
text
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, Wea
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use ui::{prelude::*, ListItem};
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store;
|
||||
use crate::thread::ThreadId;
|
||||
@@ -168,11 +169,11 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
|
||||
self.context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
if let Some(context_id) = context_store.included_thread(&entry.id) {
|
||||
context_store.remove_context(&context_id);
|
||||
} else {
|
||||
context_store.insert_thread(thread.read(cx));
|
||||
}
|
||||
context_store.insert_context(
|
||||
ContextKind::Thread(thread.read(cx).id().clone()),
|
||||
entry.summary.clone(),
|
||||
thread.read(cx).text(),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
|
||||
@@ -195,31 +196,15 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
_cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let thread = &self.matches[ix];
|
||||
|
||||
let added = self.context_store.upgrade().map_or(false, |ctx_store| {
|
||||
ctx_store.read(cx).included_thread(&thread.id).is_some()
|
||||
});
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.toggle_state(selected)
|
||||
.child(Label::new(thread.summary.clone()))
|
||||
.when(added, |el| {
|
||||
el.end_slot(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.child(Label::new("Added").size(LabelSize::Small)),
|
||||
)
|
||||
}),
|
||||
.child(Label::new(thread.summary.clone())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use collections::{HashMap, HashSet};
|
||||
use gpui::SharedString;
|
||||
use language::Buffer;
|
||||
use project::ProjectEntryId;
|
||||
|
||||
use crate::thread::Thread;
|
||||
use crate::{
|
||||
context::{Context, ContextId, ContextKind},
|
||||
thread::ThreadId,
|
||||
@@ -14,10 +9,6 @@ use crate::{
|
||||
pub struct ContextStore {
|
||||
context: Vec<Context>,
|
||||
next_context_id: ContextId,
|
||||
files: HashMap<PathBuf, ContextId>,
|
||||
directories: HashMap<PathBuf, ContextId>,
|
||||
threads: HashMap<ThreadId, ContextId>,
|
||||
fetched_urls: HashMap<String, ContextId>,
|
||||
}
|
||||
|
||||
impl ContextStore {
|
||||
@@ -25,10 +16,6 @@ impl ContextStore {
|
||||
Self {
|
||||
context: Vec::new(),
|
||||
next_context_id: ContextId(0),
|
||||
files: HashMap::default(),
|
||||
directories: HashMap::default(),
|
||||
threads: HashMap::default(),
|
||||
fetched_urls: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,192 +23,43 @@ impl ContextStore {
|
||||
&self.context
|
||||
}
|
||||
|
||||
pub fn drain(&mut self) -> Vec<Context> {
|
||||
self.context.drain(..).collect()
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.context.clear();
|
||||
self.files.clear();
|
||||
self.directories.clear();
|
||||
self.threads.clear();
|
||||
self.fetched_urls.clear();
|
||||
}
|
||||
|
||||
pub fn insert_file(&mut self, buffer: &Buffer) {
|
||||
let Some(file) = buffer.file() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let path = file.path();
|
||||
|
||||
let id = self.next_context_id.post_inc();
|
||||
self.files.insert(path.to_path_buf(), id);
|
||||
|
||||
let full_path: SharedString = path.to_string_lossy().into_owned().into();
|
||||
|
||||
let name = match path.file_name() {
|
||||
Some(name) => name.to_string_lossy().into_owned().into(),
|
||||
None => full_path.clone(),
|
||||
};
|
||||
|
||||
let parent = path
|
||||
.parent()
|
||||
.and_then(|p| p.file_name())
|
||||
.map(|p| p.to_string_lossy().into_owned().into());
|
||||
|
||||
let mut text = String::new();
|
||||
push_fenced_codeblock(path, buffer.text(), &mut text);
|
||||
|
||||
pub fn insert_context(
|
||||
&mut self,
|
||||
kind: ContextKind,
|
||||
name: impl Into<SharedString>,
|
||||
text: impl Into<SharedString>,
|
||||
) {
|
||||
self.context.push(Context {
|
||||
id,
|
||||
name,
|
||||
parent,
|
||||
tooltip: Some(full_path),
|
||||
kind: ContextKind::File,
|
||||
text: text.into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn insert_directory(&mut self, path: &Path, text: impl Into<SharedString>) {
|
||||
let id = self.next_context_id.post_inc();
|
||||
self.directories.insert(path.to_path_buf(), id);
|
||||
|
||||
let full_path: SharedString = path.to_string_lossy().into_owned().into();
|
||||
|
||||
let name = match path.file_name() {
|
||||
Some(name) => name.to_string_lossy().into_owned().into(),
|
||||
None => full_path.clone(),
|
||||
};
|
||||
|
||||
let parent = path
|
||||
.parent()
|
||||
.and_then(|p| p.file_name())
|
||||
.map(|p| p.to_string_lossy().into_owned().into());
|
||||
|
||||
self.context.push(Context {
|
||||
id,
|
||||
name,
|
||||
parent,
|
||||
tooltip: Some(full_path),
|
||||
kind: ContextKind::Directory,
|
||||
text: text.into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn insert_thread(&mut self, thread: &Thread) {
|
||||
let context_id = self.next_context_id.post_inc();
|
||||
self.threads.insert(thread.id().clone(), context_id);
|
||||
|
||||
self.context.push(Context {
|
||||
id: context_id,
|
||||
name: thread.summary().unwrap_or("New thread".into()),
|
||||
parent: None,
|
||||
tooltip: None,
|
||||
kind: ContextKind::Thread,
|
||||
text: thread.text().into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn insert_fetched_url(&mut self, url: String, text: impl Into<SharedString>) {
|
||||
let context_id = self.next_context_id.post_inc();
|
||||
self.fetched_urls.insert(url.clone(), context_id);
|
||||
|
||||
self.context.push(Context {
|
||||
id: context_id,
|
||||
name: url.into(),
|
||||
parent: None,
|
||||
tooltip: None,
|
||||
kind: ContextKind::FetchedUrl,
|
||||
id: self.next_context_id.post_inc(),
|
||||
name: name.into(),
|
||||
kind,
|
||||
text: text.into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn remove_context(&mut self, id: &ContextId) {
|
||||
let Some(ix) = self.context.iter().position(|context| context.id == *id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
match self.context.remove(ix).kind {
|
||||
ContextKind::File => {
|
||||
self.files.retain(|_, context_id| context_id != id);
|
||||
}
|
||||
ContextKind::Directory => {
|
||||
self.directories.retain(|_, context_id| context_id != id);
|
||||
}
|
||||
ContextKind::FetchedUrl => {
|
||||
self.fetched_urls.retain(|_, context_id| context_id != id);
|
||||
}
|
||||
ContextKind::Thread => {
|
||||
self.threads.retain(|_, context_id| context_id != id);
|
||||
}
|
||||
}
|
||||
self.context.retain(|context| context.id != *id);
|
||||
}
|
||||
|
||||
pub fn included_file(&self, path: &Path) -> Option<IncludedFile> {
|
||||
if let Some(id) = self.files.get(path) {
|
||||
return Some(IncludedFile::Direct(*id));
|
||||
}
|
||||
|
||||
if self.directories.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut buf = path.to_path_buf();
|
||||
|
||||
while buf.pop() {
|
||||
if let Some(_) = self.directories.get(&buf) {
|
||||
return Some(IncludedFile::InDirectory(buf));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
pub fn contains_project_entry(&self, entry_id: ProjectEntryId) -> bool {
|
||||
self.context.iter().any(|probe| match probe.kind {
|
||||
ContextKind::File(probe_entry_id) => probe_entry_id == entry_id,
|
||||
ContextKind::Directory | ContextKind::FetchedUrl | ContextKind::Thread(_) => false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn included_directory(&self, path: &Path) -> Option<ContextId> {
|
||||
self.directories.get(path).copied()
|
||||
}
|
||||
|
||||
pub fn included_thread(&self, thread_id: &ThreadId) -> Option<ContextId> {
|
||||
self.threads.get(thread_id).copied()
|
||||
}
|
||||
|
||||
pub fn included_url(&self, url: &str) -> Option<ContextId> {
|
||||
self.fetched_urls.get(url).copied()
|
||||
}
|
||||
|
||||
pub fn duplicated_names(&self) -> HashSet<SharedString> {
|
||||
let mut seen = HashSet::default();
|
||||
let mut dupes = HashSet::default();
|
||||
|
||||
for context in self.context().iter() {
|
||||
if !seen.insert(&context.name) {
|
||||
dupes.insert(context.name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
dupes
|
||||
pub fn contains_thread(&self, thread_id: &ThreadId) -> bool {
|
||||
self.context.iter().any(|probe| match probe.kind {
|
||||
ContextKind::Thread(ref probe_thread_id) => probe_thread_id == thread_id,
|
||||
ContextKind::File(_) | ContextKind::Directory | ContextKind::FetchedUrl => false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub enum IncludedFile {
|
||||
Direct(ContextId),
|
||||
InDirectory(PathBuf),
|
||||
}
|
||||
|
||||
pub(crate) fn push_fenced_codeblock(path: &Path, content: String, buffer: &mut String) {
|
||||
buffer.reserve(content.len() + 64);
|
||||
|
||||
write!(buffer, "```").unwrap();
|
||||
|
||||
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
|
||||
write!(buffer, "{} ", extension).unwrap();
|
||||
}
|
||||
|
||||
write!(buffer, "{}", path.display()).unwrap();
|
||||
|
||||
buffer.push('\n');
|
||||
buffer.push_str(&content);
|
||||
|
||||
if !buffer.ends_with('\n') {
|
||||
buffer.push('\n');
|
||||
}
|
||||
|
||||
buffer.push_str("```\n");
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
AppContext, DismissEvent, EventEmitter, FocusHandle, Model, Subscription, View, WeakModel,
|
||||
WeakView,
|
||||
};
|
||||
use gpui::{AppContext, FocusHandle, Model, View, WeakModel, WeakView};
|
||||
use language::Buffer;
|
||||
use project::ProjectEntryId;
|
||||
use ui::{prelude::*, KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread::Thread;
|
||||
use crate::thread::{Thread, ThreadId};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::ui::ContextPill;
|
||||
use crate::{AssistantPanel, RemoveAllContext, ToggleContextPicker};
|
||||
use crate::{AssistantPanel, ToggleContextPicker};
|
||||
|
||||
pub struct ContextStrip {
|
||||
context_store: Model<ContextStore>,
|
||||
@@ -24,7 +22,6 @@ pub struct ContextStrip {
|
||||
focus_handle: FocusHandle,
|
||||
suggest_context_kind: SuggestContextKind,
|
||||
workspace: WeakView<Workspace>,
|
||||
_context_picker_subscription: Subscription,
|
||||
}
|
||||
|
||||
impl ContextStrip {
|
||||
@@ -37,27 +34,21 @@ impl ContextStrip {
|
||||
suggest_context_kind: SuggestContextKind,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let context_picker = cx.new_view(|cx| {
|
||||
ContextPicker::new(
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
context_store.downgrade(),
|
||||
ConfirmBehavior::KeepOpen,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let context_picker_subscription =
|
||||
cx.subscribe(&context_picker, Self::handle_context_picker_event);
|
||||
|
||||
Self {
|
||||
context_store: context_store.clone(),
|
||||
context_picker,
|
||||
context_picker: cx.new_view(|cx| {
|
||||
ContextPicker::new(
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
context_store.downgrade(),
|
||||
ConfirmBehavior::KeepOpen,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
context_picker_menu_handle,
|
||||
focus_handle,
|
||||
suggest_context_kind,
|
||||
workspace,
|
||||
_context_picker_subscription: context_picker_subscription,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,23 +62,21 @@ impl ContextStrip {
|
||||
fn suggested_file(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let active_item = workspace.read(cx).active_item(cx)?;
|
||||
let entry_id = *active_item.project_entry_ids(cx).first()?;
|
||||
|
||||
if self.context_store.read(cx).contains_project_entry(entry_id) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
|
||||
let active_buffer = editor.buffer().read(cx).as_singleton()?;
|
||||
|
||||
let path = active_buffer.read(cx).file()?.path();
|
||||
|
||||
if self.context_store.read(cx).included_file(path).is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = match path.file_name() {
|
||||
Some(name) => name.to_string_lossy().into_owned().into(),
|
||||
None => path.to_string_lossy().into_owned().into(),
|
||||
};
|
||||
let file = active_buffer.read(cx).file()?;
|
||||
let title = file.path().to_string_lossy().into_owned().into();
|
||||
|
||||
Some(SuggestedContext::File {
|
||||
name,
|
||||
entry_id,
|
||||
title,
|
||||
buffer: active_buffer.downgrade(),
|
||||
})
|
||||
}
|
||||
@@ -106,26 +95,17 @@ impl ContextStrip {
|
||||
if self
|
||||
.context_store
|
||||
.read(cx)
|
||||
.included_thread(active_thread.id())
|
||||
.is_some()
|
||||
.contains_thread(active_thread.id())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SuggestedContext::Thread {
|
||||
name: active_thread.summary().unwrap_or("New Thread".into()),
|
||||
id: active_thread.id().clone(),
|
||||
title: active_thread.summary().unwrap_or("Active Thread".into()),
|
||||
thread: weak_active_thread,
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_context_picker_event(
|
||||
&mut self,
|
||||
_picker: View<ContextPicker>,
|
||||
_event: &DismissEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
cx.emit(ContextStripEvent::PickerDismissed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ContextStrip {
|
||||
@@ -137,8 +117,6 @@ impl Render for ContextStrip {
|
||||
|
||||
let suggested_context = self.suggested_context(cx);
|
||||
|
||||
let dupe_names = context_store.duplicated_names();
|
||||
|
||||
h_flex()
|
||||
.flex_wrap()
|
||||
.gap_1()
|
||||
@@ -190,71 +168,60 @@ impl Render for ContextStrip {
|
||||
}
|
||||
})
|
||||
.children(context.iter().map(|context| {
|
||||
ContextPill::new_added(
|
||||
context.clone(),
|
||||
dupe_names.contains(&context.name),
|
||||
Some({
|
||||
let context = context.clone();
|
||||
let context_store = self.context_store.clone();
|
||||
Rc::new(cx.listener(move |_this, _event, cx| {
|
||||
context_store.update(cx, |this, _cx| {
|
||||
this.remove_context(&context.id);
|
||||
});
|
||||
cx.notify();
|
||||
}))
|
||||
}),
|
||||
)
|
||||
ContextPill::new(context.clone()).on_remove({
|
||||
let context = context.clone();
|
||||
let context_store = self.context_store.clone();
|
||||
Rc::new(cx.listener(move |_this, _event, cx| {
|
||||
context_store.update(cx, |this, _cx| {
|
||||
this.remove_context(&context.id);
|
||||
});
|
||||
cx.notify();
|
||||
}))
|
||||
})
|
||||
}))
|
||||
.when_some(suggested_context, |el, suggested| {
|
||||
el.child(ContextPill::new_suggested(
|
||||
suggested.name().clone(),
|
||||
suggested.kind(),
|
||||
{
|
||||
let context_store = self.context_store.clone();
|
||||
Rc::new(cx.listener(move |_this, _event, cx| {
|
||||
context_store.update(cx, |context_store, cx| {
|
||||
suggested.accept(context_store, cx);
|
||||
});
|
||||
el.child(
|
||||
Button::new("add-suggested-context", suggested.title().clone())
|
||||
.on_click({
|
||||
let context_store = self.context_store.clone();
|
||||
|
||||
cx.notify();
|
||||
}))
|
||||
},
|
||||
))
|
||||
cx.listener(move |_this, _event, cx| {
|
||||
context_store.update(cx, |context_store, cx| {
|
||||
suggested.accept(context_store, cx);
|
||||
});
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.icon(IconName::Plus)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.label_size(LabelSize::Small)
|
||||
.style(ButtonStyle::Filled)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::with_meta("Suggested Context", None, "Click to add it", cx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(!context.is_empty(), {
|
||||
move |parent| {
|
||||
parent.child(
|
||||
IconButton::new("remove-all-context", IconName::Eraser)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Remove All Context",
|
||||
&RemoveAllContext,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |_this, _event, cx| {
|
||||
focus_handle.dispatch_action(&RemoveAllContext, cx);
|
||||
}
|
||||
})),
|
||||
.tooltip(move |cx| Tooltip::text("Remove All Context", cx))
|
||||
.on_click({
|
||||
let context_store = self.context_store.clone();
|
||||
cx.listener(move |_this, _event, cx| {
|
||||
context_store.update(cx, |this, _cx| this.clear());
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ContextStripEvent {
|
||||
PickerDismissed,
|
||||
}
|
||||
|
||||
impl EventEmitter<ContextStripEvent> for ContextStrip {}
|
||||
|
||||
pub enum SuggestContextKind {
|
||||
File,
|
||||
Thread,
|
||||
@@ -263,42 +230,54 @@ pub enum SuggestContextKind {
|
||||
#[derive(Clone)]
|
||||
pub enum SuggestedContext {
|
||||
File {
|
||||
name: SharedString,
|
||||
entry_id: ProjectEntryId,
|
||||
title: SharedString,
|
||||
buffer: WeakModel<Buffer>,
|
||||
},
|
||||
Thread {
|
||||
name: SharedString,
|
||||
id: ThreadId,
|
||||
title: SharedString,
|
||||
thread: WeakModel<Thread>,
|
||||
},
|
||||
}
|
||||
|
||||
impl SuggestedContext {
|
||||
pub fn name(&self) -> &SharedString {
|
||||
pub fn title(&self) -> &SharedString {
|
||||
match self {
|
||||
Self::File { name, .. } => name,
|
||||
Self::Thread { name, .. } => name,
|
||||
Self::File { title, .. } => title,
|
||||
Self::Thread { title, .. } => title,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accept(&self, context_store: &mut ContextStore, cx: &mut AppContext) {
|
||||
match self {
|
||||
Self::File { buffer, name: _ } => {
|
||||
if let Some(buffer) = buffer.upgrade() {
|
||||
context_store.insert_file(buffer.read(cx));
|
||||
Self::File {
|
||||
entry_id,
|
||||
title,
|
||||
buffer,
|
||||
} => {
|
||||
let Some(buffer) = buffer.upgrade() else {
|
||||
return;
|
||||
};
|
||||
}
|
||||
Self::Thread { thread, name: _ } => {
|
||||
if let Some(thread) = thread.upgrade() {
|
||||
context_store.insert_thread(thread.read(cx));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
let text = buffer.read(cx).text();
|
||||
|
||||
pub fn kind(&self) -> ContextKind {
|
||||
match self {
|
||||
Self::File { .. } => ContextKind::File,
|
||||
Self::Thread { .. } => ContextKind::Thread,
|
||||
context_store.insert_context(
|
||||
ContextKind::File(*entry_id),
|
||||
title.clone(),
|
||||
text.clone(),
|
||||
);
|
||||
}
|
||||
Self::Thread { id, title, thread } => {
|
||||
let Some(thread) = thread.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
context_store.insert_context(
|
||||
ContextKind::Thread(id.clone()),
|
||||
title.clone(),
|
||||
thread.read(cx).text(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,19 +294,20 @@ impl InlineAssistant {
|
||||
let newest_selection = newest_selection.unwrap();
|
||||
|
||||
let mut codegen_ranges = Vec::new();
|
||||
for (excerpt_id, buffer, buffer_range) in
|
||||
snapshot.excerpts_in_ranges(selections.iter().map(|selection| {
|
||||
for (excerpt, buffer_range) in
|
||||
snapshot.disjoint_ranges_to_buffer_ranges(selections.iter().map(|selection| {
|
||||
snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)
|
||||
}))
|
||||
{
|
||||
let buffer = excerpt.buffer();
|
||||
let start = Anchor {
|
||||
buffer_id: Some(buffer.remote_id()),
|
||||
excerpt_id,
|
||||
excerpt_id: excerpt.id(),
|
||||
text_anchor: buffer.anchor_before(buffer_range.start),
|
||||
};
|
||||
let end = Anchor {
|
||||
buffer_id: Some(buffer.remote_id()),
|
||||
excerpt_id,
|
||||
excerpt_id: excerpt.id(),
|
||||
text_anchor: buffer.anchor_after(buffer_range.end),
|
||||
};
|
||||
codegen_ranges.push(start..end);
|
||||
@@ -872,9 +873,9 @@ impl InlineAssistant {
|
||||
let language_name = assist.editor.upgrade().and_then(|editor| {
|
||||
let multibuffer = editor.read(cx).buffer().read(cx);
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let ranges = snapshot.range_to_buffer_ranges(assist.range.clone());
|
||||
let mut ranges = snapshot.range_to_buffer_ranges(assist.range.clone());
|
||||
ranges
|
||||
.first()
|
||||
.next()
|
||||
.and_then(|(excerpt, _)| excerpt.buffer().language())
|
||||
.map(|language| language.name())
|
||||
});
|
||||
|
||||
@@ -2,11 +2,11 @@ use crate::assistant_model_selector::AssistantModelSelector;
|
||||
use crate::buffer_codegen::BufferCodegen;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::context_strip::{ContextStrip, SuggestContextKind};
|
||||
use crate::terminal_codegen::TerminalCodegen;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
|
||||
use crate::{RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
|
||||
use crate::{ToggleContextPicker, ToggleModelSelector};
|
||||
use client::ErrorExt;
|
||||
use collections::VecDeque;
|
||||
use editor::{
|
||||
@@ -27,7 +27,6 @@ use settings::Settings;
|
||||
use std::cmp;
|
||||
use std::sync::Arc;
|
||||
use theme::ThemeSettings;
|
||||
use ui::utils::WithRemSize;
|
||||
use ui::{
|
||||
prelude::*, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, PopoverMenuHandle, Tooltip,
|
||||
};
|
||||
@@ -37,7 +36,6 @@ use workspace::Workspace;
|
||||
pub struct PromptEditor<T> {
|
||||
pub editor: View<Editor>,
|
||||
mode: PromptEditorMode,
|
||||
context_store: Model<ContextStore>,
|
||||
context_strip: View<ContextStrip>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
model_selector: View<AssistantModelSelector>,
|
||||
@@ -48,7 +46,6 @@ pub struct PromptEditor<T> {
|
||||
pending_prompt: String,
|
||||
_codegen_subscription: Subscription,
|
||||
editor_subscriptions: Vec<Subscription>,
|
||||
_context_strip_subscription: Subscription,
|
||||
show_rate_limit_notice: bool,
|
||||
_phantom: std::marker::PhantomData<T>,
|
||||
}
|
||||
@@ -57,10 +54,9 @@ impl<T: 'static> EventEmitter<PromptEditorEvent> for PromptEditor<T> {}
|
||||
|
||||
impl<T: 'static> Render for PromptEditor<T> {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
||||
let mut buttons = Vec::new();
|
||||
|
||||
let left_gutter_width = match &self.mode {
|
||||
let left_gutter_spacing = match &self.mode {
|
||||
PromptEditorMode::Buffer {
|
||||
id: _,
|
||||
codegen,
|
||||
@@ -110,17 +106,12 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
.on_action(cx.listener(Self::move_down))
|
||||
.on_action(cx.listener(Self::remove_all_context))
|
||||
.capture_action(cx.listener(Self::cycle_prev))
|
||||
.capture_action(cx.listener(Self::cycle_next))
|
||||
.child(
|
||||
WithRemSize::new(ui_font_size)
|
||||
.flex()
|
||||
.flex_row()
|
||||
.flex_shrink_0()
|
||||
.items_center()
|
||||
h_flex()
|
||||
.h_full()
|
||||
.w(left_gutter_width)
|
||||
.w(left_gutter_spacing)
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(self.render_close_button(cx))
|
||||
@@ -180,31 +171,19 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(div().flex_1().child(self.render_editor(cx)))
|
||||
.child(
|
||||
WithRemSize::new(ui_font_size)
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.children(buttons),
|
||||
),
|
||||
.child(h_flex().gap_1().children(buttons)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
WithRemSize::new(ui_font_size)
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.child(h_flex().flex_shrink_0().w(left_gutter_width))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.pl_1()
|
||||
.items_start()
|
||||
.justify_between()
|
||||
.child(self.context_strip.clone())
|
||||
.child(self.model_selector.clone()),
|
||||
),
|
||||
h_flex().child(div().w(left_gutter_spacing)).child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.pl_1()
|
||||
.items_start()
|
||||
.justify_between()
|
||||
.child(self.context_strip.clone())
|
||||
.child(self.model_selector.clone()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -341,11 +320,6 @@ impl<T: 'static> PromptEditor<T> {
|
||||
self.model_selector_menu_handle.toggle(cx);
|
||||
}
|
||||
|
||||
pub fn remove_all_context(&mut self, _: &RemoveAllContext, cx: &mut ViewContext<Self>) {
|
||||
self.context_store.update(cx, |store, _cx| store.clear());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
|
||||
match self.codegen_status(cx) {
|
||||
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
@@ -706,10 +680,9 @@ impl<T: 'static> PromptEditor<T> {
|
||||
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
|
||||
|
||||
div()
|
||||
.key_context("InlineAssistEditor")
|
||||
.key_context("MessageEditor")
|
||||
.size_full()
|
||||
.p_2()
|
||||
.pl_1()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
@@ -734,16 +707,6 @@ impl<T: 'static> PromptEditor<T> {
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn handle_context_strip_event(
|
||||
&mut self,
|
||||
_context_strip: View<ContextStrip>,
|
||||
ContextStripEvent::PickerDismissed: &ContextStripEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||
cx.focus(&editor_focus_handle);
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PromptEditorMode {
|
||||
@@ -821,25 +784,19 @@ impl PromptEditor<BufferCodegen> {
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let context_strip = cx.new_view(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store.clone(),
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::Thread,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let context_strip_subscription =
|
||||
cx.subscribe(&context_strip, Self::handle_context_strip_event);
|
||||
|
||||
let mut this: PromptEditor<BufferCodegen> = PromptEditor {
|
||||
editor: prompt_editor.clone(),
|
||||
context_store,
|
||||
context_strip,
|
||||
context_strip: cx.new_view(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store,
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::Thread,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
context_picker_menu_handle,
|
||||
model_selector: cx.new_view(|cx| {
|
||||
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
|
||||
@@ -851,7 +808,6 @@ impl PromptEditor<BufferCodegen> {
|
||||
pending_prompt: String::new(),
|
||||
_codegen_subscription: codegen_subscription,
|
||||
editor_subscriptions: Vec::new(),
|
||||
_context_strip_subscription: context_strip_subscription,
|
||||
show_rate_limit_notice: false,
|
||||
mode,
|
||||
_phantom: Default::default(),
|
||||
@@ -968,25 +924,19 @@ impl PromptEditor<TerminalCodegen> {
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let context_strip = cx.new_view(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store.clone(),
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::Thread,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let context_strip_subscription =
|
||||
cx.subscribe(&context_strip, Self::handle_context_strip_event);
|
||||
|
||||
let mut this = Self {
|
||||
editor: prompt_editor.clone(),
|
||||
context_store,
|
||||
context_strip,
|
||||
context_strip: cx.new_view(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store,
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::Thread,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
context_picker_menu_handle,
|
||||
model_selector: cx.new_view(|cx| {
|
||||
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
|
||||
@@ -998,7 +948,6 @@ impl PromptEditor<TerminalCodegen> {
|
||||
pending_prompt: String::new(),
|
||||
_codegen_subscription: codegen_subscription,
|
||||
editor_subscriptions: Vec::new(),
|
||||
_context_strip_subscription: context_strip_subscription,
|
||||
mode,
|
||||
show_rate_limit_notice: false,
|
||||
_phantom: Default::default(),
|
||||
|
||||
@@ -20,10 +20,10 @@ use workspace::Workspace;
|
||||
use crate::assistant_model_selector::AssistantModelSelector;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::context_strip::{ContextStrip, SuggestContextKind};
|
||||
use crate::thread::{RequestKind, Thread};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{Chat, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
|
||||
use crate::{Chat, ToggleContextPicker, ToggleModelSelector};
|
||||
|
||||
pub struct MessageEditor {
|
||||
thread: Model<Thread>,
|
||||
@@ -59,7 +59,6 @@ impl MessageEditor {
|
||||
|
||||
editor
|
||||
});
|
||||
|
||||
let inline_context_picker = cx.new_view(|cx| {
|
||||
ContextPicker::new(
|
||||
workspace.clone(),
|
||||
@@ -69,33 +68,29 @@ impl MessageEditor {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let context_strip = cx.new_view(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store.clone(),
|
||||
workspace.clone(),
|
||||
Some(thread_store.clone()),
|
||||
editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::File,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let subscriptions = vec![
|
||||
cx.subscribe(&editor, Self::handle_editor_event),
|
||||
cx.subscribe(
|
||||
&inline_context_picker,
|
||||
Self::handle_inline_context_picker_event,
|
||||
),
|
||||
cx.subscribe(&context_strip, Self::handle_context_strip_event),
|
||||
];
|
||||
|
||||
Self {
|
||||
thread,
|
||||
editor: editor.clone(),
|
||||
context_store,
|
||||
context_strip,
|
||||
context_store: context_store.clone(),
|
||||
context_strip: cx.new_view(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store,
|
||||
workspace.clone(),
|
||||
Some(thread_store.clone()),
|
||||
editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::File,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
context_picker_menu_handle,
|
||||
inline_context_picker,
|
||||
inline_context_picker_menu_handle,
|
||||
@@ -116,11 +111,6 @@ impl MessageEditor {
|
||||
self.context_picker_menu_handle.toggle(cx);
|
||||
}
|
||||
|
||||
pub fn remove_all_context(&mut self, _: &RemoveAllContext, cx: &mut ViewContext<Self>) {
|
||||
self.context_store.update(cx, |store, _cx| store.clear());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
|
||||
self.send_to_model(RequestKind::Chat, cx);
|
||||
}
|
||||
@@ -147,9 +137,7 @@ impl MessageEditor {
|
||||
editor.clear(cx);
|
||||
text
|
||||
});
|
||||
let context = self
|
||||
.context_store
|
||||
.update(cx, |this, _cx| this.context().clone());
|
||||
let context = self.context_store.update(cx, |this, _cx| this.drain());
|
||||
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.insert_user_message(user_message, context, cx);
|
||||
@@ -207,16 +195,6 @@ impl MessageEditor {
|
||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||
cx.focus(&editor_focus_handle);
|
||||
}
|
||||
|
||||
fn handle_context_strip_event(
|
||||
&mut self,
|
||||
_context_strip: View<ContextStrip>,
|
||||
ContextStripEvent::PickerDismissed: &ContextStripEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||
cx.focus(&editor_focus_handle);
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for MessageEditor {
|
||||
@@ -238,7 +216,6 @@ impl Render for MessageEditor {
|
||||
.on_action(cx.listener(Self::chat))
|
||||
.on_action(cx.listener(Self::toggle_model_selector))
|
||||
.on_action(cx.listener(Self::toggle_context_picker))
|
||||
.on_action(cx.listener(Self::remove_all_context))
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.p_2()
|
||||
@@ -285,7 +262,7 @@ impl Render for MessageEditor {
|
||||
.justify_between()
|
||||
.child(SwitchWithLabel::new(
|
||||
"use-tools",
|
||||
Label::new("Tools").size(LabelSize::Small),
|
||||
Label::new("Tools"),
|
||||
self.use_tools.into(),
|
||||
cx.listener(|this, selection, _cx| {
|
||||
this.use_tools = match selection {
|
||||
@@ -301,7 +278,7 @@ impl Render for MessageEditor {
|
||||
ButtonLike::new("chat")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new("Submit").size(LabelSize::Small))
|
||||
.child(Label::new("Submit"))
|
||||
.children(
|
||||
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::{HashMap, HashSet};
|
||||
use collections::HashMap;
|
||||
use futures::future::Shared;
|
||||
use futures::{FutureExt as _, StreamExt as _};
|
||||
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
|
||||
@@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
|
||||
use util::{post_inc, TryFutureExt as _};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::context::{attach_context_to_message, Context, ContextId};
|
||||
use crate::context::{attach_context_to_message, Context};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum RequestKind {
|
||||
@@ -64,8 +64,7 @@ pub struct Thread {
|
||||
pending_summary: Task<Option<()>>,
|
||||
messages: Vec<Message>,
|
||||
next_message_id: MessageId,
|
||||
context: HashMap<ContextId, Context>,
|
||||
context_by_message: HashMap<MessageId, Vec<ContextId>>,
|
||||
context_by_message: HashMap<MessageId, Vec<Context>>,
|
||||
completion_count: usize,
|
||||
pending_completions: Vec<PendingCompletion>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
@@ -83,7 +82,6 @@ impl Thread {
|
||||
pending_summary: Task::ready(None),
|
||||
messages: Vec::new(),
|
||||
next_message_id: MessageId(0),
|
||||
context: HashMap::default(),
|
||||
context_by_message: HashMap::default(),
|
||||
completion_count: 0,
|
||||
pending_completions: Vec::new(),
|
||||
@@ -131,15 +129,8 @@ impl Thread {
|
||||
&self.tools
|
||||
}
|
||||
|
||||
pub fn context_for_message(&self, id: MessageId) -> Option<Vec<Context>> {
|
||||
let context = self.context_by_message.get(&id)?;
|
||||
Some(
|
||||
context
|
||||
.into_iter()
|
||||
.filter_map(|context_id| self.context.get(&context_id))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
pub fn context_for_message(&self, id: MessageId) -> Option<&Vec<Context>> {
|
||||
self.context_by_message.get(&id)
|
||||
}
|
||||
|
||||
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
|
||||
@@ -153,10 +144,7 @@ impl Thread {
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let message_id = self.insert_message(Role::User, text, cx);
|
||||
let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>();
|
||||
self.context
|
||||
.extend(context.into_iter().map(|context| (context.id, context)));
|
||||
self.context_by_message.insert(message_id, context_ids);
|
||||
self.context_by_message.insert(message_id, context);
|
||||
}
|
||||
|
||||
pub fn insert_message(
|
||||
@@ -209,13 +197,7 @@ impl Thread {
|
||||
temperature: None,
|
||||
};
|
||||
|
||||
let mut referenced_context_ids = HashSet::default();
|
||||
|
||||
for message in &self.messages {
|
||||
if let Some(context_ids) = self.context_by_message.get(&message.id) {
|
||||
referenced_context_ids.extend(context_ids);
|
||||
}
|
||||
|
||||
let mut request_message = LanguageModelRequestMessage {
|
||||
role: message.role,
|
||||
content: Vec::new(),
|
||||
@@ -230,6 +212,10 @@ impl Thread {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(context) = self.context_for_message(message.id) {
|
||||
attach_context_to_message(&mut request_message, context.clone());
|
||||
}
|
||||
|
||||
if !message.text.is_empty() {
|
||||
request_message
|
||||
.content
|
||||
@@ -247,22 +233,6 @@ impl Thread {
|
||||
request.messages.push(request_message);
|
||||
}
|
||||
|
||||
if !referenced_context_ids.is_empty() {
|
||||
let mut context_message = LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: Vec::new(),
|
||||
cache: false,
|
||||
};
|
||||
|
||||
let referenced_context = referenced_context_ids
|
||||
.into_iter()
|
||||
.filter_map(|context_id| self.context.get(context_id))
|
||||
.cloned();
|
||||
attach_context_to_message(&mut context_message, referenced_context);
|
||||
|
||||
request.messages.push(context_message);
|
||||
}
|
||||
|
||||
request
|
||||
}
|
||||
|
||||
|
||||
@@ -1,153 +1,65 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::ClickEvent;
|
||||
use ui::{prelude::*, IconButtonShape, Tooltip};
|
||||
use ui::{prelude::*, IconButtonShape};
|
||||
|
||||
use crate::context::{Context, ContextKind};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub enum ContextPill {
|
||||
Added {
|
||||
context: Context,
|
||||
dupe_name: bool,
|
||||
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||
},
|
||||
Suggested {
|
||||
name: SharedString,
|
||||
kind: ContextKind,
|
||||
on_add: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
|
||||
},
|
||||
pub struct ContextPill {
|
||||
context: Context,
|
||||
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||
}
|
||||
|
||||
impl ContextPill {
|
||||
pub fn new_added(
|
||||
context: Context,
|
||||
dupe_name: bool,
|
||||
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||
) -> Self {
|
||||
Self::Added {
|
||||
pub fn new(context: Context) -> Self {
|
||||
Self {
|
||||
context,
|
||||
dupe_name,
|
||||
on_remove,
|
||||
on_remove: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_suggested(
|
||||
name: SharedString,
|
||||
kind: ContextKind,
|
||||
on_add: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
|
||||
) -> Self {
|
||||
Self::Suggested { name, kind, on_add }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> ElementId {
|
||||
match self {
|
||||
Self::Added { context, .. } => {
|
||||
ElementId::NamedInteger("context-pill".into(), context.id.0)
|
||||
}
|
||||
Self::Suggested { .. } => "suggested-context-pill".into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> &ContextKind {
|
||||
match self {
|
||||
Self::Added { context, .. } => &context.kind,
|
||||
Self::Suggested { kind, .. } => kind,
|
||||
}
|
||||
pub fn on_remove(mut self, on_remove: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>) -> Self {
|
||||
self.on_remove = Some(on_remove);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ContextPill {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let icon = match &self.kind() {
|
||||
ContextKind::File => IconName::File,
|
||||
let padding_right = if self.on_remove.is_some() {
|
||||
px(2.)
|
||||
} else {
|
||||
px(4.)
|
||||
};
|
||||
let icon = match self.context.kind {
|
||||
ContextKind::File(_) => IconName::File,
|
||||
ContextKind::Directory => IconName::Folder,
|
||||
ContextKind::FetchedUrl => IconName::Globe,
|
||||
ContextKind::Thread => IconName::MessageCircle,
|
||||
ContextKind::Thread(_) => IconName::MessageCircle,
|
||||
};
|
||||
|
||||
let color = cx.theme().colors();
|
||||
|
||||
let base_pill = h_flex()
|
||||
.id(self.id())
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.pl_1()
|
||||
.pr(padding_right)
|
||||
.pb(px(1.))
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border.opacity(0.5))
|
||||
.bg(cx.theme().colors().element_background)
|
||||
.rounded_md()
|
||||
.gap_1()
|
||||
.child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted));
|
||||
|
||||
match &self {
|
||||
ContextPill::Added {
|
||||
context,
|
||||
dupe_name,
|
||||
on_remove,
|
||||
} => base_pill
|
||||
.bg(color.element_background)
|
||||
.border_color(color.border.opacity(0.5))
|
||||
.pr(if on_remove.is_some() { px(2.) } else { px(4.) })
|
||||
.child(
|
||||
h_flex()
|
||||
.id("context-data")
|
||||
.gap_1()
|
||||
.child(Label::new(context.name.clone()).size(LabelSize::Small))
|
||||
.when_some(context.parent.as_ref(), |element, parent_name| {
|
||||
if *dupe_name {
|
||||
element.child(
|
||||
Label::new(parent_name.clone())
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
} else {
|
||||
element
|
||||
}
|
||||
})
|
||||
.when_some(context.tooltip.clone(), |element, tooltip| {
|
||||
element.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
|
||||
.child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted))
|
||||
.child(Label::new(self.context.name.clone()).size(LabelSize::Small))
|
||||
.when_some(self.on_remove, |parent, on_remove| {
|
||||
parent.child(
|
||||
IconButton::new(("remove", self.context.id.0), IconName::Close)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click({
|
||||
let on_remove = on_remove.clone();
|
||||
move |event, cx| on_remove(event, cx)
|
||||
}),
|
||||
)
|
||||
.when_some(on_remove.as_ref(), |element, on_remove| {
|
||||
element.child(
|
||||
IconButton::new(("remove", context.id.0), IconName::Close)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.tooltip(|cx| Tooltip::text("Remove Context", cx))
|
||||
.on_click({
|
||||
let on_remove = on_remove.clone();
|
||||
move |event, cx| on_remove(event, cx)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
ContextPill::Suggested { name, kind, on_add } => base_pill
|
||||
.cursor_pointer()
|
||||
.pr_1()
|
||||
.border_color(color.border_variant.opacity(0.5))
|
||||
.hover(|style| style.bg(color.element_hover.opacity(0.5)))
|
||||
.child(
|
||||
Label::new(name.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Label::new(match kind {
|
||||
ContextKind::File => "Open File",
|
||||
ContextKind::Thread | ContextKind::Directory | ContextKind::FetchedUrl => {
|
||||
"Active"
|
||||
}
|
||||
})
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::Plus)
|
||||
.size(IconSize::XSmall)
|
||||
.into_any_element(),
|
||||
)
|
||||
.tooltip(|cx| Tooltip::with_meta("Suggested Context", None, "Click to add it", cx))
|
||||
.on_click({
|
||||
let on_add = on_add.clone();
|
||||
move |event, cx| on_add(event, cx)
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4065,7 +4065,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
DiagnosticEntry {
|
||||
range: Point::new(0, 4)..Point::new(0, 7),
|
||||
diagnostic: Diagnostic {
|
||||
group_id: 2,
|
||||
group_id: 3,
|
||||
message: "message 1".to_string(),
|
||||
severity: lsp::DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
@@ -4075,7 +4075,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
DiagnosticEntry {
|
||||
range: Point::new(0, 10)..Point::new(0, 13),
|
||||
diagnostic: Diagnostic {
|
||||
group_id: 3,
|
||||
group_id: 4,
|
||||
severity: lsp::DiagnosticSeverity::WARNING,
|
||||
message: "message 2".to_string(),
|
||||
is_primary: true,
|
||||
|
||||
@@ -1134,7 +1134,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
let end = PointUtf16::new(end_row, end_column);
|
||||
let range = if start > end { end..start } else { start..end };
|
||||
highlights.push(lsp::DocumentHighlight {
|
||||
range: range_to_lsp(range.clone()).unwrap(),
|
||||
range: range_to_lsp(range.clone()),
|
||||
kind: Some(lsp::DocumentHighlightKind::READ),
|
||||
});
|
||||
}
|
||||
@@ -1222,7 +1222,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
id,
|
||||
guest_project.remote_id(),
|
||||
);
|
||||
assert_eq!(guest_snapshot.repositories().iter().collect::<Vec<_>>(), host_snapshot.repositories().iter().collect::<Vec<_>>(),
|
||||
assert_eq!(guest_snapshot.repositories().collect::<Vec<_>>(), host_snapshot.repositories().collect::<Vec<_>>(),
|
||||
"{} has different repositories than the host for worktree {:?} and project {:?}",
|
||||
client.username,
|
||||
host_snapshot.abs_path(),
|
||||
|
||||
@@ -63,10 +63,6 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
false
|
||||
}
|
||||
|
||||
fn show_completions_in_normal_mode() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
|
||||
@@ -841,61 +841,72 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
|
||||
|
||||
h_flex()
|
||||
.id(DIAGNOSTIC_HEADER)
|
||||
.block_mouse_down()
|
||||
.h(2. * cx.line_height())
|
||||
.w_full()
|
||||
.px_9()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.relative()
|
||||
.child(
|
||||
div()
|
||||
.top(px(0.))
|
||||
.absolute()
|
||||
.w_full()
|
||||
.h_px()
|
||||
.bg(color.border_variant),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.block_mouse_down()
|
||||
.h(2. * cx.line_height())
|
||||
.pl_10()
|
||||
.pr_5()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.px_1()
|
||||
.rounded_md()
|
||||
.bg(color.surface_background.opacity(0.5))
|
||||
.map(|stack| {
|
||||
stack.child(
|
||||
svg()
|
||||
.size(cx.text_style().font_size)
|
||||
.flex_none()
|
||||
.map(|icon| {
|
||||
if diagnostic.severity == DiagnosticSeverity::ERROR {
|
||||
icon.path(IconName::XCircle.path())
|
||||
.text_color(Color::Error.color(cx))
|
||||
} else {
|
||||
icon.path(IconName::Warning.path())
|
||||
.text_color(Color::Warning.color(cx))
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.gap_3()
|
||||
.map(|stack| {
|
||||
stack.child(svg().size(cx.text_style().font_size).flex_none().map(
|
||||
|icon| {
|
||||
if diagnostic.severity == DiagnosticSeverity::ERROR {
|
||||
icon.path(IconName::XCircle.path())
|
||||
.text_color(Color::Error.color(cx))
|
||||
} else {
|
||||
icon.path(IconName::Warning.path())
|
||||
.text_color(Color::Warning.color(cx))
|
||||
}
|
||||
},
|
||||
))
|
||||
})
|
||||
.child(
|
||||
StyledText::new(message.clone()).with_highlights(
|
||||
&cx.text_style(),
|
||||
code_ranges
|
||||
.iter()
|
||||
.map(|range| (range.clone(), highlight_style)),
|
||||
),
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
StyledText::new(message.clone()).with_highlights(
|
||||
&cx.text_style(),
|
||||
code_ranges
|
||||
.iter()
|
||||
.map(|range| (range.clone(), highlight_style)),
|
||||
),
|
||||
)
|
||||
.when_some(diagnostic.code.as_ref(), |stack, code| {
|
||||
stack.child(
|
||||
div()
|
||||
.child(SharedString::from(format!("({code})")))
|
||||
.text_color(cx.theme().colors().text_muted),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(h_flex().gap_1().when_some(
|
||||
diagnostic.source.as_ref(),
|
||||
|stack, source| {
|
||||
stack.child(
|
||||
div()
|
||||
.child(SharedString::from(source.clone()))
|
||||
.text_color(cx.theme().colors().text_muted),
|
||||
)
|
||||
.when_some(diagnostic.code.as_ref(), |stack, code| {
|
||||
stack.child(
|
||||
div()
|
||||
.child(SharedString::from(format!("({code})")))
|
||||
.text_color(color.text_muted),
|
||||
)
|
||||
}),
|
||||
),
|
||||
},
|
||||
)),
|
||||
)
|
||||
.when_some(diagnostic.source.as_ref(), |stack, source| {
|
||||
stack.child(
|
||||
div()
|
||||
.child(SharedString::from(source.clone()))
|
||||
.text_color(color.text_muted),
|
||||
)
|
||||
})
|
||||
.into_any_element()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
is_primary: false,
|
||||
is_disk_based: true,
|
||||
group_id: 1,
|
||||
group_id: 2,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -95,7 +95,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
is_primary: false,
|
||||
is_disk_based: true,
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -106,7 +106,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
is_primary: false,
|
||||
is_disk_based: true,
|
||||
group_id: 1,
|
||||
group_id: 2,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -117,7 +117,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
is_primary: false,
|
||||
is_disk_based: true,
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -128,7 +128,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -139,7 +139,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 1,
|
||||
group_id: 2,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -241,7 +241,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
}],
|
||||
@@ -348,7 +348,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -359,7 +359,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 1,
|
||||
group_id: 2,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -775,7 +775,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
assert!(view.focus_handle.is_focused(cx));
|
||||
});
|
||||
|
||||
let mut next_group_id = 0;
|
||||
let mut next_group_id = 1;
|
||||
let mut next_filename = 0;
|
||||
let mut language_server_ids = vec![LanguageServerId(0)];
|
||||
let mut updated_language_servers = HashSet::default();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! This module contains all actions supported by [`Editor`].
|
||||
use super::*;
|
||||
use gpui::{action_aliases, action_as};
|
||||
use gpui::action_as;
|
||||
use util::serde::default_true;
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
@@ -204,7 +204,7 @@ impl_actions!(
|
||||
ToggleCodeActions,
|
||||
ToggleComments,
|
||||
UnfoldAt,
|
||||
FoldAtLevel,
|
||||
FoldAtLevel
|
||||
]
|
||||
);
|
||||
|
||||
@@ -311,6 +311,7 @@ gpui::actions!(
|
||||
OpenExcerpts,
|
||||
OpenExcerptsSplit,
|
||||
OpenProposedChangesEditor,
|
||||
OpenFile,
|
||||
OpenDocs,
|
||||
OpenPermalinkToLine,
|
||||
OpenUrl,
|
||||
@@ -388,5 +389,3 @@ gpui::actions!(
|
||||
);
|
||||
|
||||
action_as!(go_to_line, ToggleGoToLine as Toggle);
|
||||
|
||||
action_aliases!(editor, OpenSelectedFilename, [OpenFile]);
|
||||
|
||||
@@ -334,9 +334,6 @@ impl CompletionsMenu {
|
||||
entries
|
||||
}
|
||||
_ => {
|
||||
if self.selected_item != 0 {
|
||||
self.selected_item += 1;
|
||||
}
|
||||
let mut entries = Vec::with_capacity(self.entries.len() + 1);
|
||||
entries.push(hint);
|
||||
entries.extend_from_slice(&self.entries);
|
||||
@@ -344,6 +341,9 @@ impl CompletionsMenu {
|
||||
}
|
||||
}
|
||||
.into();
|
||||
if self.selected_item != 0 && self.selected_item + 1 < self.entries.len() {
|
||||
self.selected_item += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_visible_completions(
|
||||
|
||||
@@ -42,7 +42,7 @@ use fold_map::{FoldMap, FoldSnapshot};
|
||||
use gpui::{
|
||||
AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
|
||||
};
|
||||
pub use inlay_map::Inlay;
|
||||
pub(crate) use inlay_map::Inlay;
|
||||
use inlay_map::{InlayMap, InlaySnapshot};
|
||||
pub use inlay_map::{InlayOffset, InlayPoint};
|
||||
use invisibles::{is_invisible, replacement};
|
||||
|
||||
@@ -33,7 +33,7 @@ enum Transform {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Inlay {
|
||||
pub(crate) struct Inlay {
|
||||
pub(crate) id: InlayId,
|
||||
pub position: Anchor,
|
||||
pub text: text::Rope,
|
||||
|
||||
@@ -109,7 +109,7 @@ pub use proposed_changes_editor::{
|
||||
ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
|
||||
};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use std::iter::Peekable;
|
||||
use std::iter::{self, Peekable};
|
||||
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
||||
|
||||
use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
|
||||
@@ -258,7 +258,7 @@ pub fn render_parsed_markdown(
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum InlayId {
|
||||
pub(crate) enum InlayId {
|
||||
InlineCompletion(usize),
|
||||
Hint(usize),
|
||||
}
|
||||
@@ -3550,8 +3550,7 @@ impl Editor {
|
||||
);
|
||||
let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
|
||||
multi_buffer_snapshot
|
||||
.range_to_buffer_ranges(multi_buffer_visible_range)
|
||||
.into_iter()
|
||||
.disjoint_ranges_to_buffer_ranges(iter::once(multi_buffer_visible_range))
|
||||
.filter(|(_, excerpt_visible_range)| !excerpt_visible_range.is_empty())
|
||||
.filter_map(|(excerpt, excerpt_visible_range)| {
|
||||
let buffer_file = project::File::from_dyn(excerpt.buffer().file())?;
|
||||
@@ -3592,7 +3591,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn splice_inlays(
|
||||
fn splice_inlays(
|
||||
&self,
|
||||
to_remove: Vec<InlayId>,
|
||||
to_insert: Vec<Inlay>,
|
||||
@@ -4883,7 +4882,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
|
||||
fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
|
||||
Some(self.inline_completion_provider.as_ref()?.provider.clone())
|
||||
}
|
||||
|
||||
@@ -5851,7 +5850,7 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn join_lines_impl(&mut self, insert_whitespace: bool, cx: &mut ViewContext<Self>) {
|
||||
pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
@@ -5893,12 +5892,11 @@ impl Editor {
|
||||
let indent = snapshot.indent_size_for_line(next_line_row);
|
||||
let start_of_next_line = Point::new(next_line_row.0, indent.len);
|
||||
|
||||
let replace =
|
||||
if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let replace = if snapshot.line_len(next_line_row) > indent.len {
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
|
||||
@@ -5912,10 +5910,6 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
|
||||
self.join_lines_impl(true, cx);
|
||||
}
|
||||
|
||||
pub fn sort_lines_case_sensitive(
|
||||
&mut self,
|
||||
_: &SortLinesCaseSensitive,
|
||||
@@ -9114,18 +9108,15 @@ impl Editor {
|
||||
};
|
||||
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
let mut excerpt_ids = selections
|
||||
.iter()
|
||||
.flat_map(|selection| {
|
||||
snapshot
|
||||
.excerpts_for_range(selection.range())
|
||||
.map(|excerpt| excerpt.id())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
excerpt_ids.sort();
|
||||
excerpt_ids.dedup();
|
||||
buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
|
||||
buffer.expand_excerpts(
|
||||
selections
|
||||
.iter()
|
||||
.map(|selection| selection.head().excerpt_id)
|
||||
.dedup(),
|
||||
lines,
|
||||
direction,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9511,7 +9502,7 @@ impl Editor {
|
||||
url_finder.detach();
|
||||
}
|
||||
|
||||
pub fn open_selected_filename(&mut self, _: &OpenSelectedFilename, cx: &mut ViewContext<Self>) {
|
||||
pub fn open_file(&mut self, _: &OpenFile, cx: &mut ViewContext<Self>) {
|
||||
let Some(workspace) = self.workspace() else {
|
||||
return;
|
||||
};
|
||||
@@ -10506,13 +10497,13 @@ impl Editor {
|
||||
} else {
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut toggled_buffers = HashSet::default();
|
||||
for (_, buffer_snapshot, _) in multi_buffer_snapshot.excerpts_in_ranges(
|
||||
for (excerpt, _) in multi_buffer_snapshot.disjoint_ranges_to_buffer_ranges(
|
||||
self.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.map(|selection| selection.range()),
|
||||
) {
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
let buffer_id = excerpt.buffer().remote_id();
|
||||
if toggled_buffers.insert(buffer_id) {
|
||||
if self.buffer_folded(buffer_id, cx) {
|
||||
self.unfold_buffer(buffer_id, cx);
|
||||
@@ -10592,13 +10583,13 @@ impl Editor {
|
||||
} else {
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut folded_buffers = HashSet::default();
|
||||
for (_, buffer_snapshot, _) in multi_buffer_snapshot.excerpts_in_ranges(
|
||||
for (excerpt, _) in multi_buffer_snapshot.disjoint_ranges_to_buffer_ranges(
|
||||
self.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.map(|selection| selection.range()),
|
||||
) {
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
let buffer_id = excerpt.buffer().remote_id();
|
||||
if folded_buffers.insert(buffer_id) {
|
||||
self.fold_buffer(buffer_id, cx);
|
||||
}
|
||||
@@ -10758,13 +10749,13 @@ impl Editor {
|
||||
} else {
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut unfolded_buffers = HashSet::default();
|
||||
for (_, buffer_snapshot, _) in multi_buffer_snapshot.excerpts_in_ranges(
|
||||
for (excerpt, _) in multi_buffer_snapshot.disjoint_ranges_to_buffer_ranges(
|
||||
self.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.map(|selection| selection.range()),
|
||||
) {
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
let buffer_id = excerpt.buffer().remote_id();
|
||||
if unfolded_buffers.insert(buffer_id) {
|
||||
self.unfold_buffer(buffer_id, cx);
|
||||
}
|
||||
@@ -11508,36 +11499,36 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn get_permalink_to_line(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<url::Url>> {
|
||||
let buffer_and_selection = maybe!({
|
||||
let selection = self.selections.newest::<Point>(cx);
|
||||
let buffer_and_selection_rows = maybe!({
|
||||
let multi_buffer = self.buffer().read(cx);
|
||||
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
|
||||
let selection = self.selections.newest_anchor();
|
||||
let selection_range = selection.range();
|
||||
|
||||
let (buffer, selection) = if let Some(buffer) = self.buffer().read(cx).as_singleton() {
|
||||
(buffer, selection_range.start.row..selection_range.end.row)
|
||||
let (buffer, selection_rows) = if let Some(buffer) = multi_buffer.as_singleton() {
|
||||
(
|
||||
buffer,
|
||||
selection_range.start.to_point(&multi_buffer_snapshot).row
|
||||
..selection_range.end.to_point(&multi_buffer_snapshot).row,
|
||||
)
|
||||
} else {
|
||||
let multi_buffer = self.buffer().read(cx);
|
||||
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
|
||||
let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
|
||||
|
||||
let (excerpt, range) = if selection.reversed {
|
||||
buffer_ranges.first()
|
||||
} else {
|
||||
buffer_ranges.last()
|
||||
}?;
|
||||
|
||||
let selection_head = selection.head();
|
||||
let excerpt =
|
||||
multi_buffer_snapshot.excerpt_containing(selection_head..selection_head)?;
|
||||
let range =
|
||||
excerpt.map_range_to_buffer(selection_range.to_offset(&multi_buffer_snapshot));
|
||||
let snapshot = excerpt.buffer();
|
||||
let selection = text::ToPoint::to_point(&range.start, &snapshot).row
|
||||
..text::ToPoint::to_point(&range.end, &snapshot).row;
|
||||
(
|
||||
multi_buffer.buffer(excerpt.buffer_id()).unwrap().clone(),
|
||||
selection,
|
||||
text::ToPoint::to_point(&range.start, &snapshot).row
|
||||
..text::ToPoint::to_point(&range.end, &snapshot).row,
|
||||
)
|
||||
};
|
||||
|
||||
Some((buffer, selection))
|
||||
Some((buffer, selection_rows))
|
||||
});
|
||||
|
||||
let Some((buffer, selection)) = buffer_and_selection else {
|
||||
let Some((buffer, selection_rows)) = buffer_and_selection_rows else {
|
||||
return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
|
||||
};
|
||||
|
||||
@@ -11546,7 +11537,7 @@ impl Editor {
|
||||
};
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
project.get_permalink_to_line(&buffer, selection, cx)
|
||||
project.get_permalink_to_line(&buffer, selection_rows, cx)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -5962,8 +5962,8 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(script_element
|
||||
(raw_text) @injection.content
|
||||
(#set! injection.language "javascript"))
|
||||
(raw_text) @content
|
||||
(#set! "language" "javascript"))
|
||||
"#,
|
||||
)
|
||||
.unwrap(),
|
||||
@@ -9068,8 +9068,8 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(script_element
|
||||
(raw_text) @injection.content
|
||||
(#set! injection.language "javascript"))
|
||||
(raw_text) @content
|
||||
(#set! "language" "javascript"))
|
||||
"#,
|
||||
)
|
||||
.unwrap(),
|
||||
|
||||
@@ -342,7 +342,7 @@ impl EditorElement {
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
register_action(view, cx, Editor::open_url);
|
||||
register_action(view, cx, Editor::open_selected_filename);
|
||||
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);
|
||||
@@ -5578,21 +5578,21 @@ impl LineWithInvisibles {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
invisibles.extend(line_chunk.char_indices().filter_map(
|
||||
|(index, c)| {
|
||||
let is_whitespace = c.is_whitespace();
|
||||
non_whitespace_added |= !is_whitespace;
|
||||
if is_whitespace
|
||||
&& (non_whitespace_added || !is_soft_wrapped)
|
||||
{
|
||||
Some(Invisible::Whitespace {
|
||||
line_offset: line.len() + index,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
))
|
||||
invisibles.extend(
|
||||
line_chunk
|
||||
.bytes()
|
||||
.enumerate()
|
||||
.filter(|(_, line_byte)| {
|
||||
let is_whitespace =
|
||||
(*line_byte as char).is_whitespace();
|
||||
non_whitespace_added |= !is_whitespace;
|
||||
is_whitespace
|
||||
&& (non_whitespace_added || !is_soft_wrapped)
|
||||
})
|
||||
.map(|(whitespace_index, _)| Invisible::Whitespace {
|
||||
line_offset: line.len() + whitespace_index,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,10 +197,9 @@ impl ProjectDiffEditor {
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
let applicable_entries = snapshot
|
||||
.repositories()
|
||||
.iter()
|
||||
.flat_map(|entry| {
|
||||
entry.status().map(|git_entry| {
|
||||
(git_entry.combined_status(), entry.join(git_entry.repo_path))
|
||||
(git_entry.status, entry.join(git_entry.repo_path))
|
||||
})
|
||||
})
|
||||
.filter_map(|(status, path)| {
|
||||
|
||||
@@ -11,11 +11,11 @@ use gpui::{
|
||||
StyleRefinement, Styled, Task, TextStyleRefinement, View, ViewContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{DiagnosticEntry, Language, LanguageRegistry};
|
||||
use language::{Diagnostic, DiagnosticEntry, Language, LanguageRegistry};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use markdown::{Markdown, MarkdownStyle};
|
||||
use multi_buffer::ToOffset;
|
||||
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
|
||||
use project::{HoverBlock, InlayHintLabelPart};
|
||||
use settings::Settings;
|
||||
use std::rc::Rc;
|
||||
use std::{borrow::Cow, cell::RefCell};
|
||||
@@ -263,14 +263,50 @@ fn show_hover(
|
||||
delay.await;
|
||||
}
|
||||
|
||||
let local_diagnostic = snapshot
|
||||
let local_diagnostic = if let Some(invisible) = snapshot
|
||||
.buffer_snapshot
|
||||
.diagnostics_in_range(anchor..anchor, false)
|
||||
// Find the entry with the most specific range
|
||||
.min_by_key(|entry| {
|
||||
let range = entry.range.to_offset(&snapshot.buffer_snapshot);
|
||||
range.end - range.start
|
||||
});
|
||||
.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(),
|
||||
);
|
||||
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(),
|
||||
);
|
||||
Some(DiagnosticEntry {
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: format!("Unicode character U+{:02X}", invisible as u32),
|
||||
..Default::default()
|
||||
},
|
||||
range: before..anchor,
|
||||
})
|
||||
} else {
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.diagnostics_in_range(anchor..anchor, false)
|
||||
// Find the entry with the most specific range
|
||||
.min_by_key(|entry| {
|
||||
let range = entry.range.to_offset(&snapshot.buffer_snapshot);
|
||||
range.end - range.start
|
||||
})
|
||||
};
|
||||
|
||||
let diagnostic_popover = if let Some(local_diagnostic) = local_diagnostic {
|
||||
let text = match local_diagnostic.diagnostic.source {
|
||||
@@ -353,31 +389,6 @@ fn show_hover(
|
||||
this.hover_state.diagnostic_popover = diagnostic_popover;
|
||||
})?;
|
||||
|
||||
let invisible_char = 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(),
|
||||
);
|
||||
Some((invisible, 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(),
|
||||
);
|
||||
|
||||
Some((invisible, before..anchor))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let hovers_response = if let Some(hover_request) = hover_request {
|
||||
hover_request.await
|
||||
} else {
|
||||
@@ -385,26 +396,8 @@ fn show_hover(
|
||||
};
|
||||
let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;
|
||||
let mut hover_highlights = Vec::with_capacity(hovers_response.len());
|
||||
let mut info_popovers = Vec::with_capacity(
|
||||
hovers_response.len() + if invisible_char.is_some() { 1 } else { 0 },
|
||||
);
|
||||
|
||||
if let Some((invisible, range)) = invisible_char {
|
||||
let blocks = vec![HoverBlock {
|
||||
text: format!("Unicode character U+{:02X}", invisible as u32),
|
||||
kind: HoverBlockKind::PlainText,
|
||||
}];
|
||||
let parsed_content = parse_blocks(&blocks, &language_registry, None, &mut cx).await;
|
||||
let scroll_handle = ScrollHandle::new();
|
||||
info_popovers.push(InfoPopover {
|
||||
symbol_range: RangeInEditor::Text(range),
|
||||
parsed_content,
|
||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
|
||||
scroll_handle,
|
||||
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
|
||||
anchor: Some(anchor),
|
||||
})
|
||||
}
|
||||
let mut info_popovers = Vec::with_capacity(hovers_response.len());
|
||||
let mut info_popover_tasks = Vec::with_capacity(hovers_response.len());
|
||||
|
||||
for hover_result in hovers_response {
|
||||
// Create symbol range of anchors for highlighting and filtering of future requests.
|
||||
@@ -417,6 +410,7 @@ fn show_hover(
|
||||
let end = snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_in_excerpt(excerpt_id, range.end)?;
|
||||
|
||||
Some(start..end)
|
||||
})
|
||||
.or_else(|| {
|
||||
@@ -434,15 +428,21 @@ fn show_hover(
|
||||
let parsed_content =
|
||||
parse_blocks(&blocks, &language_registry, language, &mut cx).await;
|
||||
let scroll_handle = ScrollHandle::new();
|
||||
hover_highlights.push(range.clone());
|
||||
info_popovers.push(InfoPopover {
|
||||
symbol_range: RangeInEditor::Text(range),
|
||||
parsed_content,
|
||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
|
||||
scroll_handle,
|
||||
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
|
||||
anchor: Some(anchor),
|
||||
});
|
||||
info_popover_tasks.push((
|
||||
range.clone(),
|
||||
InfoPopover {
|
||||
symbol_range: RangeInEditor::Text(range),
|
||||
parsed_content,
|
||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
|
||||
scroll_handle,
|
||||
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
|
||||
anchor: Some(anchor),
|
||||
},
|
||||
));
|
||||
}
|
||||
for (highlight_range, info_popover) in info_popover_tasks {
|
||||
hover_highlights.push(highlight_range);
|
||||
info_popovers.push(info_popover);
|
||||
}
|
||||
|
||||
this.update(&mut cx, |editor, cx| {
|
||||
@@ -732,7 +732,6 @@ impl InfoPopover {
|
||||
cx.notify();
|
||||
self.scroll_handle.set_offset(current);
|
||||
}
|
||||
|
||||
fn render_vertical_scrollbar(&self, cx: &mut ViewContext<Editor>) -> Stateful<Div> {
|
||||
div()
|
||||
.occlude()
|
||||
|
||||
@@ -458,10 +458,7 @@ impl Editor {
|
||||
) -> Option<()> {
|
||||
let multi_buffer = self.buffer.read(cx);
|
||||
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
|
||||
let (excerpt, range) = multi_buffer_snapshot
|
||||
.range_to_buffer_ranges(range)
|
||||
.into_iter()
|
||||
.next()?;
|
||||
let (excerpt, range) = multi_buffer_snapshot.range_to_buffer_ranges(range).next()?;
|
||||
|
||||
multi_buffer
|
||||
.buffer(excerpt.buffer_id())
|
||||
|
||||
@@ -325,10 +325,6 @@ impl InlineCompletionProvider for FakeInlineCompletionProvider {
|
||||
false
|
||||
}
|
||||
|
||||
fn show_completions_in_normal_mode() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
_buffer: &gpui::Model<language::Buffer>,
|
||||
|
||||
@@ -1468,10 +1468,11 @@ impl SearchableItem for Editor {
|
||||
search_within_ranges
|
||||
};
|
||||
|
||||
for (excerpt_id, search_buffer, search_range) in
|
||||
buffer.excerpts_in_ranges(search_within_ranges)
|
||||
for (excerpt, search_range) in
|
||||
buffer.disjoint_ranges_to_buffer_ranges(search_within_ranges)
|
||||
{
|
||||
if !search_range.is_empty() {
|
||||
let search_buffer = excerpt.buffer();
|
||||
ranges.extend(
|
||||
query
|
||||
.search(search_buffer, Some(search_range.clone()))
|
||||
@@ -1482,8 +1483,8 @@ impl SearchableItem for Editor {
|
||||
.anchor_after(search_range.start + match_range.start);
|
||||
let end = search_buffer
|
||||
.anchor_before(search_range.start + match_range.end);
|
||||
buffer.anchor_in_excerpt(excerpt_id, start).unwrap()
|
||||
..buffer.anchor_in_excerpt(excerpt_id, end).unwrap()
|
||||
buffer.anchor_in_excerpt(excerpt.id(), start).unwrap()
|
||||
..buffer.anchor_in_excerpt(excerpt.id(), end).unwrap()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
mod mac_watcher;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub mod fs_watcher;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
pub mod linux_watcher;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use git::GitHostingProviderRegistry;
|
||||
@@ -11,7 +11,8 @@ use git::GitHostingProviderRegistry;
|
||||
use ashpd::desktop::trash;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use smol::process::Command;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use std::fs::File;
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::AsFd;
|
||||
#[cfg(unix)]
|
||||
@@ -444,13 +445,7 @@ impl Fs for RealFs {
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
async fn trash_file(&self, path: &Path, _options: RemoveOptions) -> Result<()> {
|
||||
if let Ok(Some(metadata)) = self.metadata(path).await {
|
||||
if metadata.is_symlink {
|
||||
// TODO: trash_file does not support trashing symlinks yet - https://github.com/bilelmoussaoui/ashpd/issues/255
|
||||
return self.remove_file(path, RemoveOptions::default()).await;
|
||||
}
|
||||
}
|
||||
let file = smol::fs::File::open(path).await?;
|
||||
let file = File::open(path)?;
|
||||
match trash::trash_file(&file.as_fd()).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(anyhow::Error::new(err)),
|
||||
@@ -700,7 +695,7 @@ impl Fs for RealFs {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
async fn watch(
|
||||
&self,
|
||||
path: &Path,
|
||||
@@ -710,11 +705,10 @@ impl Fs for RealFs {
|
||||
Arc<dyn Watcher>,
|
||||
) {
|
||||
use parking_lot::Mutex;
|
||||
use util::paths::SanitizedPath;
|
||||
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
|
||||
let watcher = Arc::new(fs_watcher::FsWatcher::new(tx, pending_paths.clone()));
|
||||
let watcher = Arc::new(linux_watcher::LinuxWatcher::new(tx, pending_paths.clone()));
|
||||
|
||||
if watcher.add(path).is_err() {
|
||||
// If the path doesn't exist yet (e.g. settings.json), watch the parent dir to learn when it's created.
|
||||
@@ -732,7 +726,7 @@ impl Fs for RealFs {
|
||||
if let Some(parent) = path.parent() {
|
||||
target = parent.join(target);
|
||||
if let Ok(canonical) = self.canonicalize(&target).await {
|
||||
target = SanitizedPath::from(canonical).as_path().to_path_buf();
|
||||
target = canonical;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -759,6 +753,56 @@ impl Fs for RealFs {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
async fn watch(
|
||||
&self,
|
||||
path: &Path,
|
||||
_latency: Duration,
|
||||
) -> (
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||
Arc<dyn Watcher>,
|
||||
) {
|
||||
use notify::{EventKind, Watcher};
|
||||
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
|
||||
let mut file_watcher = notify::recommended_watcher({
|
||||
let tx = tx.clone();
|
||||
move |event: Result<notify::Event, _>| {
|
||||
if let Some(event) = event.log_err() {
|
||||
let kind = match event.kind {
|
||||
EventKind::Create(_) => Some(PathEventKind::Created),
|
||||
EventKind::Modify(_) => Some(PathEventKind::Changed),
|
||||
EventKind::Remove(_) => Some(PathEventKind::Removed),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
tx.try_send(
|
||||
event
|
||||
.paths
|
||||
.into_iter()
|
||||
.map(|path| PathEvent { path, kind })
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
.expect("Could not start file watcher");
|
||||
|
||||
file_watcher
|
||||
.watch(path, notify::RecursiveMode::Recursive)
|
||||
.log_err();
|
||||
|
||||
(
|
||||
Box::pin(rx.chain(futures::stream::once(async move {
|
||||
drop(file_watcher);
|
||||
vec![]
|
||||
}))),
|
||||
Arc::new(RealWatcher {}),
|
||||
)
|
||||
}
|
||||
|
||||
fn open_repo(&self, dotgit_path: &Path) -> Option<Arc<dyn GitRepository>> {
|
||||
// with libgit2, we can open git repo from an existing work dir
|
||||
// https://libgit2.org/docs/reference/main/repository/git_repository_open.html
|
||||
|
||||
@@ -5,12 +5,12 @@ use util::ResultExt;
|
||||
|
||||
use crate::{PathEvent, PathEventKind, Watcher};
|
||||
|
||||
pub struct FsWatcher {
|
||||
pub struct LinuxWatcher {
|
||||
tx: smol::channel::Sender<()>,
|
||||
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
|
||||
}
|
||||
|
||||
impl FsWatcher {
|
||||
impl LinuxWatcher {
|
||||
pub fn new(
|
||||
tx: smol::channel::Sender<()>,
|
||||
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
|
||||
@@ -22,7 +22,7 @@ impl FsWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
impl Watcher for FsWatcher {
|
||||
impl Watcher for LinuxWatcher {
|
||||
fn add(&self, path: &std::path::Path) -> gpui::Result<()> {
|
||||
let root_path = path.to_path_buf();
|
||||
|
||||
@@ -69,7 +69,7 @@ impl Watcher for FsWatcher {
|
||||
})?;
|
||||
|
||||
global(|g| {
|
||||
g.watcher
|
||||
g.inotify
|
||||
.lock()
|
||||
.watch(path, notify::RecursiveMode::NonRecursive)
|
||||
})??;
|
||||
@@ -79,18 +79,16 @@ impl Watcher for FsWatcher {
|
||||
|
||||
fn remove(&self, path: &std::path::Path) -> gpui::Result<()> {
|
||||
use notify::Watcher;
|
||||
Ok(global(|w| w.watcher.lock().unwatch(path))??)
|
||||
Ok(global(|w| w.inotify.lock().unwatch(path))??)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlobalWatcher {
|
||||
// two mutexes because calling watcher.add triggers an watcher.event, which needs watchers.
|
||||
// two mutexes because calling inotify.add triggers an inotify.event, which needs watchers.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(super) watcher: Mutex<notify::INotifyWatcher>,
|
||||
pub(super) inotify: Mutex<notify::INotifyWatcher>,
|
||||
#[cfg(target_os = "freebsd")]
|
||||
pub(super) watcher: Mutex<notify::KqueueWatcher>,
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(super) watcher: Mutex<notify::ReadDirectoryChangesWatcher>,
|
||||
pub(super) inotify: Mutex<notify::KqueueWatcher>,
|
||||
pub(super) watchers: Mutex<Vec<Box<dyn Fn(¬ify::Event) + Send + Sync>>>,
|
||||
}
|
||||
|
||||
@@ -100,8 +98,7 @@ impl GlobalWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
static FS_WATCHER_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> =
|
||||
OnceLock::new();
|
||||
static INOTIFY_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> = OnceLock::new();
|
||||
|
||||
fn handle_event(event: Result<notify::Event, notify::Error>) {
|
||||
let Some(event) = event.log_err() else { return };
|
||||
@@ -114,9 +111,9 @@ fn handle_event(event: Result<notify::Event, notify::Error>) {
|
||||
}
|
||||
|
||||
pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
|
||||
let result = FS_WATCHER_INSTANCE.get_or_init(|| {
|
||||
let result = INOTIFY_INSTANCE.get_or_init(|| {
|
||||
notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
|
||||
watcher: Mutex::new(file_watcher),
|
||||
inotify: Mutex::new(file_watcher),
|
||||
watchers: Default::default(),
|
||||
})
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::status::GitStatusPair;
|
||||
use crate::GitHostingProviderRegistry;
|
||||
use crate::{blame::Blame, status::GitStatus};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use anyhow::{Context, Result};
|
||||
use collections::{HashMap, HashSet};
|
||||
use git2::BranchType;
|
||||
use gpui::SharedString;
|
||||
@@ -16,7 +15,6 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
use sum_tree::MapSeekTarget;
|
||||
use util::command::new_std_command;
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
@@ -53,8 +51,6 @@ pub trait GitRepository: Send + Sync {
|
||||
|
||||
/// Returns the path to the repository, typically the `.git` folder.
|
||||
fn dot_git_dir(&self) -> PathBuf;
|
||||
|
||||
fn update_index(&self, stage: &[RepoPath], unstage: &[RepoPath]) -> Result<()>;
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for dyn GitRepository {
|
||||
@@ -156,7 +152,7 @@ impl GitRepository for RealGitRepository {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) => match e.code() {
|
||||
git2::ErrorCode::NotFound => Ok(false),
|
||||
_ => Err(anyhow!(e)),
|
||||
_ => Err(anyhow::anyhow!(e)),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -200,7 +196,7 @@ impl GitRepository for RealGitRepository {
|
||||
repo.set_head(
|
||||
revision
|
||||
.name()
|
||||
.ok_or_else(|| anyhow!("Branch name could not be retrieved"))?,
|
||||
.ok_or_else(|| anyhow::anyhow!("Branch name could not be retrieved"))?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -232,36 +228,6 @@ impl GitRepository for RealGitRepository {
|
||||
self.hosting_provider_registry.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn update_index(&self, stage: &[RepoPath], unstage: &[RepoPath]) -> Result<()> {
|
||||
let working_directory = self
|
||||
.repository
|
||||
.lock()
|
||||
.workdir()
|
||||
.context("failed to read git work directory")?
|
||||
.to_path_buf();
|
||||
if !stage.is_empty() {
|
||||
let add = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["add", "--"])
|
||||
.args(stage.iter().map(|p| p.as_ref()))
|
||||
.status()?;
|
||||
if !add.success() {
|
||||
return Err(anyhow!("Failed to stage files: {add}"));
|
||||
}
|
||||
}
|
||||
if !unstage.is_empty() {
|
||||
let rm = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["restore", "--staged", "--"])
|
||||
.args(unstage.iter().map(|p| p.as_ref()))
|
||||
.status()?;
|
||||
if !rm.success() {
|
||||
return Err(anyhow!("Failed to unstage files: {rm}"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -332,24 +298,18 @@ impl GitRepository for FakeGitRepository {
|
||||
let mut entries = state
|
||||
.worktree_statuses
|
||||
.iter()
|
||||
.filter_map(|(repo_path, status_worktree)| {
|
||||
.filter_map(|(repo_path, status)| {
|
||||
if path_prefixes
|
||||
.iter()
|
||||
.any(|path_prefix| repo_path.0.starts_with(path_prefix))
|
||||
{
|
||||
Some((
|
||||
repo_path.to_owned(),
|
||||
GitStatusPair {
|
||||
index_status: None,
|
||||
worktree_status: Some(*status_worktree),
|
||||
},
|
||||
))
|
||||
Some((repo_path.to_owned(), *status))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(&b));
|
||||
entries.sort_unstable_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
Ok(GitStatus {
|
||||
entries: entries.into(),
|
||||
@@ -403,10 +363,6 @@ impl GitRepository for FakeGitRepository {
|
||||
.with_context(|| format!("failed to get blame for {:?}", path))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn update_index(&self, _stage: &[RepoPath], _unstage: &[RepoPath]) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
|
||||
@@ -442,7 +398,6 @@ fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
|
||||
pub enum GitFileStatus {
|
||||
Added,
|
||||
Modified,
|
||||
// TODO conflicts should be represented by the GitStatusPair
|
||||
Conflict,
|
||||
Deleted,
|
||||
Untracked,
|
||||
@@ -471,16 +426,6 @@ impl GitFileStatus {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_byte(byte: u8) -> Option<Self> {
|
||||
match byte {
|
||||
b'M' => Some(GitFileStatus::Modified),
|
||||
b'A' => Some(GitFileStatus::Added),
|
||||
b'D' => Some(GitFileStatus::Deleted),
|
||||
b'?' => Some(GitFileStatus::Untracked),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub static WORK_DIRECTORY_REPO_PATH: LazyLock<RepoPath> =
|
||||
@@ -508,12 +453,6 @@ impl RepoPath {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RepoPath {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.to_string_lossy().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Path> for RepoPath {
|
||||
fn from(value: &Path) -> Self {
|
||||
RepoPath::new(value.into())
|
||||
|
||||
@@ -2,33 +2,9 @@ use crate::repository::{GitFileStatus, RepoPath};
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{path::Path, process::Stdio, sync::Arc};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct GitStatusPair {
|
||||
// Not both `None`.
|
||||
pub index_status: Option<GitFileStatus>,
|
||||
pub worktree_status: Option<GitFileStatus>,
|
||||
}
|
||||
|
||||
impl GitStatusPair {
|
||||
pub fn is_staged(&self) -> Option<bool> {
|
||||
match (self.index_status, self.worktree_status) {
|
||||
(Some(_), None) => Some(true),
|
||||
(None, Some(_)) => Some(false),
|
||||
(Some(GitFileStatus::Untracked), Some(GitFileStatus::Untracked)) => Some(false),
|
||||
(Some(_), Some(_)) => None,
|
||||
(None, None) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO reconsider uses of this
|
||||
pub fn combined(&self) -> GitFileStatus {
|
||||
self.index_status.or(self.worktree_status).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GitStatus {
|
||||
pub entries: Arc<[(RepoPath, GitStatusPair)]>,
|
||||
pub entries: Arc<[(RepoPath, GitFileStatus)]>,
|
||||
}
|
||||
|
||||
impl GitStatus {
|
||||
@@ -44,7 +20,6 @@ impl GitStatus {
|
||||
"status",
|
||||
"--porcelain=v1",
|
||||
"--untracked-files=all",
|
||||
"--no-renames",
|
||||
"-z",
|
||||
])
|
||||
.args(path_prefixes.iter().map(|path_prefix| {
|
||||
@@ -72,32 +47,36 @@ impl GitStatus {
|
||||
let mut entries = stdout
|
||||
.split('\0')
|
||||
.filter_map(|entry| {
|
||||
let sep = entry.get(2..3)?;
|
||||
if sep != " " {
|
||||
return None;
|
||||
};
|
||||
let path = &entry[3..];
|
||||
let status = entry[0..2].as_bytes();
|
||||
let index_status = GitFileStatus::from_byte(status[0]);
|
||||
let worktree_status = GitFileStatus::from_byte(status[1]);
|
||||
if (index_status, worktree_status) == (None, None) {
|
||||
return None;
|
||||
if entry.is_char_boundary(3) {
|
||||
let (status, path) = entry.split_at(3);
|
||||
let status = status.trim();
|
||||
Some((
|
||||
RepoPath(Path::new(path).into()),
|
||||
match status {
|
||||
"A" => GitFileStatus::Added,
|
||||
"M" => GitFileStatus::Modified,
|
||||
"D" => GitFileStatus::Deleted,
|
||||
"??" => GitFileStatus::Untracked,
|
||||
_ => return None,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let path = RepoPath(Path::new(path).into());
|
||||
Some((
|
||||
path,
|
||||
GitStatusPair {
|
||||
index_status,
|
||||
worktree_status,
|
||||
},
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(&b));
|
||||
entries.sort_unstable_by(|a, b| a.0.cmp(&b.0));
|
||||
Ok(Self {
|
||||
entries: entries.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, path: &Path) -> Option<GitFileStatus> {
|
||||
self.entries
|
||||
.binary_search_by(|(repo_path, _)| repo_path.0.as_ref().cmp(path))
|
||||
.ok()
|
||||
.map(|index| self.entries[index].1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GitStatus {
|
||||
|
||||
@@ -28,8 +28,6 @@ serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
sum_tree.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
45
crates/git_ui/TODO.md
Normal file
45
crates/git_ui/TODO.md
Normal file
@@ -0,0 +1,45 @@
|
||||
### General
|
||||
|
||||
- [x] Disable staging and committing actions for read-only projects
|
||||
|
||||
### List
|
||||
|
||||
- [x] Add uniform list
|
||||
- [x] Git status item
|
||||
- [ ] Directory item
|
||||
- [x] Scrollbar
|
||||
- [ ] Add indent size setting
|
||||
- [ ] Add tree settings
|
||||
|
||||
### List Items
|
||||
|
||||
- [x] Checkbox for staging
|
||||
- [x] Git status icon
|
||||
- [ ] Context menu
|
||||
- [ ] Discard Changes
|
||||
- ---
|
||||
- [ ] Ignore
|
||||
- [ ] Ignore directory
|
||||
- ---
|
||||
- [ ] Copy path
|
||||
- [ ] Copy relative path
|
||||
- ---
|
||||
- [ ] Reveal in Finder
|
||||
|
||||
### Commit Editor
|
||||
|
||||
- [ ] Add commit editor
|
||||
- [ ] Add commit message placeholder & add commit message to store
|
||||
- [ ] Add a way to get the current collaborators & automatically add them to the commit message as co-authors
|
||||
- [ ] Add action to clear commit message
|
||||
- [x] Swap commit button between "Commit" and "Commit All" based on modifier key
|
||||
|
||||
### Component Updates
|
||||
|
||||
- [ ] ChangedLineCount (new)
|
||||
- takes `lines_added: usize, lines_removed: usize`, returns a added/removed badge
|
||||
- [x] GitStatusIcon (new)
|
||||
- [ ] Checkbox
|
||||
- update checkbox design
|
||||
- [ ] ScrollIndicator
|
||||
- shows a gradient overlay when more content is available to be scrolled
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,282 +1,25 @@
|
||||
use ::settings::Settings;
|
||||
use collections::HashMap;
|
||||
use futures::{future::FusedFuture, select, FutureExt};
|
||||
use git::repository::{GitFileStatus, GitRepository, RepoPath};
|
||||
use gpui::{actions, AppContext, Context, Global, Hsla, Model, ModelContext};
|
||||
use project::{Project, WorktreeId};
|
||||
use git::repository::GitFileStatus;
|
||||
use gpui::{actions, AppContext, Hsla};
|
||||
use settings::GitPanelSettings;
|
||||
use std::sync::mpsc;
|
||||
use std::{
|
||||
pin::{pin, Pin},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use sum_tree::SumTree;
|
||||
use ui::{Color, Icon, IconName, IntoElement, SharedString};
|
||||
use worktree::RepositoryEntry;
|
||||
use ui::{Color, Icon, IconName, IntoElement};
|
||||
|
||||
pub mod git_panel;
|
||||
mod settings;
|
||||
|
||||
const GIT_TASK_DEBOUNCE: Duration = Duration::from_millis(50);
|
||||
|
||||
actions!(
|
||||
git,
|
||||
git_ui,
|
||||
[
|
||||
StageFile,
|
||||
UnstageFile,
|
||||
ToggleStaged,
|
||||
// Revert actions are currently in the editor crate:
|
||||
// editor::RevertFile,
|
||||
// editor::RevertSelectedHunks
|
||||
StageAll,
|
||||
UnstageAll,
|
||||
RevertAll,
|
||||
CommitChanges,
|
||||
CommitAllChanges,
|
||||
ClearCommitMessage
|
||||
DiscardAll,
|
||||
CommitStagedChanges,
|
||||
CommitAllChanges
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
GitPanelSettings::register(cx);
|
||||
let git_state = cx.new_model(GitState::new);
|
||||
cx.set_global(GlobalGitState(git_state));
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum GitViewMode {
|
||||
#[default]
|
||||
List,
|
||||
Tree,
|
||||
}
|
||||
|
||||
struct GlobalGitState(Model<GitState>);
|
||||
|
||||
impl Global for GlobalGitState {}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum StatusAction {
|
||||
Stage,
|
||||
Unstage,
|
||||
}
|
||||
|
||||
pub struct GitState {
|
||||
/// The current commit message being composed.
|
||||
commit_message: Option<SharedString>,
|
||||
|
||||
/// When a git repository is selected, this is used to track which repository's changes
|
||||
/// are currently being viewed or modified in the UI.
|
||||
active_repository: Option<(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)>,
|
||||
|
||||
updater_tx: mpsc::Sender<(Arc<dyn GitRepository>, Vec<RepoPath>, StatusAction)>,
|
||||
|
||||
all_repositories: HashMap<WorktreeId, SumTree<RepositoryEntry>>,
|
||||
|
||||
list_view_mode: GitViewMode,
|
||||
}
|
||||
|
||||
impl GitState {
|
||||
pub fn new(cx: &mut ModelContext<'_, Self>) -> Self {
|
||||
let (updater_tx, updater_rx) = mpsc::channel();
|
||||
cx.spawn(|_, cx| async move {
|
||||
// Long-running task to periodically update git indices based on messages from the panel.
|
||||
|
||||
// We read messages from the channel in batches that refer to the same repository.
|
||||
// When we read a message whose repository is different from the current batch's repository,
|
||||
// the batch is finished, and since we can't un-receive this last message, we save it
|
||||
// to begin the next batch.
|
||||
let mut leftover_message: Option<(
|
||||
Arc<dyn GitRepository>,
|
||||
Vec<RepoPath>,
|
||||
StatusAction,
|
||||
)> = None;
|
||||
let mut git_task = None;
|
||||
loop {
|
||||
let mut timer = cx.background_executor().timer(GIT_TASK_DEBOUNCE).fuse();
|
||||
let _result = {
|
||||
let mut task: Pin<&mut dyn FusedFuture<Output = anyhow::Result<()>>> =
|
||||
match git_task.as_mut() {
|
||||
Some(task) => pin!(task),
|
||||
// If no git task is running, just wait for the timeout.
|
||||
None => pin!(std::future::pending().fuse()),
|
||||
};
|
||||
select! {
|
||||
result = task => {
|
||||
// Task finished.
|
||||
git_task = None;
|
||||
Some(result)
|
||||
}
|
||||
_ = timer => None,
|
||||
}
|
||||
};
|
||||
|
||||
// TODO handle failure of the git command
|
||||
|
||||
if git_task.is_none() {
|
||||
// No git task running now; let's see if we should launch a new one.
|
||||
let mut to_stage = Vec::new();
|
||||
let mut to_unstage = Vec::new();
|
||||
let mut current_repo = leftover_message.as_ref().map(|msg| msg.0.clone());
|
||||
for (git_repo, paths, action) in leftover_message
|
||||
.take()
|
||||
.into_iter()
|
||||
.chain(updater_rx.try_iter())
|
||||
{
|
||||
if current_repo
|
||||
.as_ref()
|
||||
.map_or(false, |repo| !Arc::ptr_eq(repo, &git_repo))
|
||||
{
|
||||
// End of a batch, save this for the next one.
|
||||
leftover_message = Some((git_repo.clone(), paths, action));
|
||||
break;
|
||||
} else if current_repo.is_none() {
|
||||
// Start of a batch.
|
||||
current_repo = Some(git_repo);
|
||||
}
|
||||
|
||||
if action == StatusAction::Stage {
|
||||
to_stage.extend(paths);
|
||||
} else {
|
||||
to_unstage.extend(paths);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO handle the same path being staged and unstaged
|
||||
|
||||
if to_stage.is_empty() && to_unstage.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(git_repo) = current_repo {
|
||||
git_task = Some(
|
||||
cx.background_executor()
|
||||
.spawn(async move { git_repo.update_index(&to_stage, &to_unstage) })
|
||||
.fuse(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
GitState {
|
||||
commit_message: None,
|
||||
active_repository: None,
|
||||
updater_tx,
|
||||
list_view_mode: GitViewMode::default(),
|
||||
all_repositories: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_global(cx: &mut AppContext) -> Model<GitState> {
|
||||
cx.global::<GlobalGitState>().0.clone()
|
||||
}
|
||||
|
||||
pub fn activate_repository(
|
||||
&mut self,
|
||||
worktree_id: WorktreeId,
|
||||
active_repository: RepositoryEntry,
|
||||
git_repo: Arc<dyn GitRepository>,
|
||||
) {
|
||||
self.active_repository = Some((worktree_id, active_repository, git_repo));
|
||||
}
|
||||
|
||||
pub fn active_repository(
|
||||
&self,
|
||||
) -> Option<&(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)> {
|
||||
self.active_repository.as_ref()
|
||||
}
|
||||
|
||||
pub fn commit_message(&mut self, message: Option<SharedString>) {
|
||||
self.commit_message = message;
|
||||
}
|
||||
|
||||
pub fn clear_commit_message(&mut self) {
|
||||
self.commit_message = None;
|
||||
}
|
||||
|
||||
pub fn stage_entry(&mut self, repo_path: RepoPath) {
|
||||
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
|
||||
let _ = self
|
||||
.updater_tx
|
||||
.send((git_repo.clone(), vec![repo_path], StatusAction::Stage));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unstage_entry(&mut self, repo_path: RepoPath) {
|
||||
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
|
||||
let _ =
|
||||
self.updater_tx
|
||||
.send((git_repo.clone(), vec![repo_path], StatusAction::Unstage));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stage_entries(&mut self, entries: Vec<RepoPath>) {
|
||||
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
|
||||
let _ = self
|
||||
.updater_tx
|
||||
.send((git_repo.clone(), entries, StatusAction::Stage));
|
||||
}
|
||||
}
|
||||
|
||||
fn act_on_all(&mut self, action: StatusAction) {
|
||||
if let Some((_, active_repository, git_repo)) = self.active_repository.as_ref() {
|
||||
let _ = self.updater_tx.send((
|
||||
git_repo.clone(),
|
||||
active_repository
|
||||
.status()
|
||||
.map(|entry| entry.repo_path)
|
||||
.collect(),
|
||||
action,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stage_all(&mut self) {
|
||||
self.act_on_all(StatusAction::Stage);
|
||||
}
|
||||
|
||||
pub fn unstage_all(&mut self) {
|
||||
self.act_on_all(StatusAction::Unstage);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn first_worktree_repository(
|
||||
project: &Model<Project>,
|
||||
worktree_id: WorktreeId,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<(RepositoryEntry, Arc<dyn GitRepository>)> {
|
||||
project
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.and_then(|worktree| {
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
let repo = snapshot.repositories().iter().next()?.clone();
|
||||
let git_repo = worktree
|
||||
.read(cx)
|
||||
.as_local()?
|
||||
.get_local_repo(&repo)?
|
||||
.repo()
|
||||
.clone();
|
||||
Some((repo, git_repo))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn first_repository_in_project(
|
||||
project: &Model<Project>,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)> {
|
||||
project.read(cx).worktrees(cx).next().and_then(|worktree| {
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
let repo = snapshot.repositories().iter().next()?.clone();
|
||||
let git_repo = worktree
|
||||
.read(cx)
|
||||
.as_local()?
|
||||
.get_local_repo(&repo)?
|
||||
.repo()
|
||||
.clone();
|
||||
Some((snapshot.id(), repo, git_repo))
|
||||
})
|
||||
}
|
||||
|
||||
const ADDED_COLOR: Hsla = Hsla {
|
||||
@@ -308,8 +51,6 @@ pub fn git_status_icon(status: GitFileStatus) -> impl IntoElement {
|
||||
Icon::new(IconName::SquareDot).color(Color::Custom(MODIFIED_COLOR))
|
||||
}
|
||||
GitFileStatus::Conflict => Icon::new(IconName::Warning).color(Color::Custom(REMOVED_COLOR)),
|
||||
GitFileStatus::Deleted => {
|
||||
Icon::new(IconName::SquareMinus).color(Color::Custom(REMOVED_COLOR))
|
||||
}
|
||||
GitFileStatus::Deleted => Icon::new(IconName::Warning).color(Color::Custom(REMOVED_COLOR)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,18 @@
|
||||
use editor::ShowScrollbar;
|
||||
use gpui::Pixels;
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use workspace::dock::DockPosition;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct ScrollbarSettingsContent {
|
||||
/// When to show the scrollbar in the git panel.
|
||||
///
|
||||
/// Default: inherits editor scrollbar settings
|
||||
pub show: Option<Option<ShowScrollbar>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct ScrollbarSettings {
|
||||
/// When to show the scrollbar in the git panel.
|
||||
///
|
||||
/// Default: inherits editor scrollbar settings
|
||||
pub show: Option<ShowScrollbar>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum StatusStyleContent {
|
||||
Icon,
|
||||
Label,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum StatusStyle {
|
||||
#[default]
|
||||
Icon,
|
||||
Label,
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct GitPanelSettings {
|
||||
pub button: bool,
|
||||
pub dock: DockPosition,
|
||||
pub default_width: Pixels,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct GitPanelSettingsContent {
|
||||
pub struct PanelSettingsContent {
|
||||
/// Whether to show the panel button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
@@ -50,29 +25,12 @@ pub struct GitPanelSettingsContent {
|
||||
///
|
||||
/// Default: 360
|
||||
pub default_width: Option<f32>,
|
||||
/// How entry statuses are displayed.
|
||||
///
|
||||
/// Default: icon
|
||||
pub status_style: Option<StatusStyle>,
|
||||
/// How and when the scrollbar should be displayed.
|
||||
///
|
||||
/// Default: inherits editor scrollbar settings
|
||||
pub scrollbar: Option<ScrollbarSettings>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct GitPanelSettings {
|
||||
pub button: bool,
|
||||
pub dock: DockPosition,
|
||||
pub default_width: Pixels,
|
||||
pub status_style: StatusStyle,
|
||||
pub scrollbar: ScrollbarSettings,
|
||||
}
|
||||
|
||||
impl Settings for GitPanelSettings {
|
||||
const KEY: Option<&'static str> = Some("git_panel");
|
||||
|
||||
type FileContent = GitPanelSettingsContent;
|
||||
type FileContent = PanelSettingsContent;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mod supported_countries;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -15,20 +15,6 @@ pub async fn stream_generate_content(
|
||||
api_key: &str,
|
||||
mut request: GenerateContentRequest,
|
||||
) -> Result<BoxStream<'static, Result<GenerateContentResponse>>> {
|
||||
if request.contents.is_empty() {
|
||||
bail!("Request must contain at least one content item");
|
||||
}
|
||||
|
||||
if let Some(user_content) = request
|
||||
.contents
|
||||
.iter()
|
||||
.find(|content| content.role == Role::User)
|
||||
{
|
||||
if user_content.parts.is_empty() {
|
||||
bail!("User content must contain at least one part");
|
||||
}
|
||||
}
|
||||
|
||||
let uri = format!(
|
||||
"{api_url}/v1beta/models/{model}:streamGenerateContent?alt=sse&key={api_key}",
|
||||
model = request.model
|
||||
@@ -154,7 +140,7 @@ pub struct Content {
|
||||
pub role: Role,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Role {
|
||||
User,
|
||||
@@ -305,8 +291,6 @@ pub enum Model {
|
||||
Gemini15Pro,
|
||||
#[serde(rename = "gemini-1.5-flash")]
|
||||
Gemini15Flash,
|
||||
#[serde(rename = "gemini-2.0-flash-exp")]
|
||||
Gemini20Flash,
|
||||
#[serde(rename = "custom")]
|
||||
Custom {
|
||||
name: String,
|
||||
@@ -321,7 +305,6 @@ impl Model {
|
||||
match self {
|
||||
Model::Gemini15Pro => "gemini-1.5-pro",
|
||||
Model::Gemini15Flash => "gemini-1.5-flash",
|
||||
Model::Gemini20Flash => "gemini-2.0-flash-exp",
|
||||
Model::Custom { name, .. } => name,
|
||||
}
|
||||
}
|
||||
@@ -330,7 +313,6 @@ impl Model {
|
||||
match self {
|
||||
Model::Gemini15Pro => "Gemini 1.5 Pro",
|
||||
Model::Gemini15Flash => "Gemini 1.5 Flash",
|
||||
Model::Gemini20Flash => "Gemini 2.0 Flash",
|
||||
Self::Custom {
|
||||
name, display_name, ..
|
||||
} => display_name.as_ref().unwrap_or(name),
|
||||
@@ -341,7 +323,6 @@ impl Model {
|
||||
match self {
|
||||
Model::Gemini15Pro => 2_000_000,
|
||||
Model::Gemini15Flash => 1_000_000,
|
||||
Model::Gemini20Flash => 1_000_000,
|
||||
Model::Custom { max_tokens, .. } => *max_tokens,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ pathfinder_geometry = "0.5"
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||
# Always used
|
||||
flume = "0.11"
|
||||
oo7 = { git = "https://github.com/zed-industries/oo7", branch = "avoid-crypto-panic" }
|
||||
oo7 = "0.3.0"
|
||||
|
||||
# Used in both windowing options
|
||||
ashpd = { workspace = true, optional = true }
|
||||
|
||||
@@ -62,14 +62,6 @@ pub trait Action: 'static + Send {
|
||||
fn build(value: serde_json::Value) -> Result<Box<dyn Action>>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// A list of alternate, deprecated names for this action.
|
||||
fn deprecated_aliases() -> &'static [&'static str]
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for dyn Action {
|
||||
@@ -93,7 +85,6 @@ pub(crate) struct ActionRegistry {
|
||||
builders_by_name: HashMap<SharedString, ActionBuilder>,
|
||||
names_by_type_id: HashMap<TypeId, SharedString>,
|
||||
all_names: Vec<SharedString>, // So we can return a static slice.
|
||||
deprecations: Vec<(SharedString, SharedString)>,
|
||||
}
|
||||
|
||||
impl Default for ActionRegistry {
|
||||
@@ -102,7 +93,6 @@ impl Default for ActionRegistry {
|
||||
builders_by_name: Default::default(),
|
||||
names_by_type_id: Default::default(),
|
||||
all_names: Default::default(),
|
||||
deprecations: Default::default(),
|
||||
};
|
||||
|
||||
this.load_actions();
|
||||
@@ -121,7 +111,6 @@ pub type MacroActionBuilder = fn() -> ActionData;
|
||||
#[doc(hidden)]
|
||||
pub struct ActionData {
|
||||
pub name: &'static str,
|
||||
pub aliases: &'static [&'static str],
|
||||
pub type_id: TypeId,
|
||||
pub build: ActionBuilder,
|
||||
}
|
||||
@@ -145,7 +134,6 @@ impl ActionRegistry {
|
||||
pub(crate) fn load_action<A: Action>(&mut self) {
|
||||
self.insert_action(ActionData {
|
||||
name: A::debug_name(),
|
||||
aliases: A::deprecated_aliases(),
|
||||
type_id: TypeId::of::<A>(),
|
||||
build: A::build,
|
||||
});
|
||||
@@ -154,10 +142,6 @@ impl ActionRegistry {
|
||||
fn insert_action(&mut self, action: ActionData) {
|
||||
let name: SharedString = action.name.into();
|
||||
self.builders_by_name.insert(name.clone(), action.build);
|
||||
for &alias in action.aliases {
|
||||
self.builders_by_name.insert(alias.into(), action.build);
|
||||
self.deprecations.push((alias.into(), name.clone()));
|
||||
}
|
||||
self.names_by_type_id.insert(action.type_id, name.clone());
|
||||
self.all_names.push(name);
|
||||
}
|
||||
@@ -190,10 +174,6 @@ impl ActionRegistry {
|
||||
pub fn all_action_names(&self) -> &[SharedString] {
|
||||
self.all_names.as_slice()
|
||||
}
|
||||
|
||||
pub fn action_deprecations(&self) -> &[(SharedString, SharedString)] {
|
||||
self.deprecations.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines unit structs that can be used as actions.
|
||||
@@ -226,7 +206,7 @@ macro_rules! actions {
|
||||
/// `impl_action_as!`
|
||||
#[macro_export]
|
||||
macro_rules! action_as {
|
||||
($namespace:path, $name:ident as $visual_name:ident) => {
|
||||
($namespace:path, $name:ident as $visual_name:tt) => {
|
||||
#[doc = "The `"]
|
||||
#[doc = stringify!($name)]
|
||||
#[doc = "` action, see [`gpui::actions!`]"]
|
||||
@@ -248,43 +228,6 @@ macro_rules! action_as {
|
||||
};
|
||||
}
|
||||
|
||||
/// Defines a unit struct that can be used as an action, with some deprecated aliases.
|
||||
#[macro_export]
|
||||
macro_rules! action_aliases {
|
||||
($namespace:path, $name:ident, [$($alias:ident),* $(,)?]) => {
|
||||
#[doc = "The `"]
|
||||
#[doc = stringify!($name)]
|
||||
#[doc = "` action, see [`gpui::actions!`]"]
|
||||
#[derive(
|
||||
::std::cmp::PartialEq,
|
||||
::std::clone::Clone,
|
||||
::std::default::Default,
|
||||
::std::fmt::Debug,
|
||||
gpui::private::serde_derive::Deserialize,
|
||||
)]
|
||||
#[serde(crate = "gpui::private::serde")]
|
||||
pub struct $name;
|
||||
|
||||
gpui::__impl_action!(
|
||||
$namespace,
|
||||
$name,
|
||||
$name,
|
||||
fn build(
|
||||
_: gpui::private::serde_json::Value,
|
||||
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
||||
Ok(Box::new(Self))
|
||||
},
|
||||
fn deprecated_aliases() -> &'static [&'static str] {
|
||||
&[
|
||||
$(concat!(stringify!($namespace), "::", stringify!($alias))),*
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
gpui::register_action!($name);
|
||||
};
|
||||
}
|
||||
|
||||
/// Implements the Action trait for any struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize
|
||||
#[macro_export]
|
||||
macro_rules! impl_actions {
|
||||
@@ -326,7 +269,7 @@ macro_rules! impl_action_as {
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __impl_action {
|
||||
($namespace:path, $name:ident, $visual_name:tt, $($items:item),*) => {
|
||||
($namespace:path, $name:ident, $visual_name:tt, $build:item) => {
|
||||
impl gpui::Action for $name {
|
||||
fn name(&self) -> &'static str
|
||||
{
|
||||
@@ -348,6 +291,8 @@ macro_rules! __impl_action {
|
||||
)
|
||||
}
|
||||
|
||||
$build
|
||||
|
||||
fn partial_eq(&self, action: &dyn gpui::Action) -> bool {
|
||||
action
|
||||
.as_any()
|
||||
@@ -362,8 +307,6 @@ macro_rules! __impl_action {
|
||||
fn as_any(&self) -> &dyn ::std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
$($items)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1226,11 +1226,6 @@ impl AppContext {
|
||||
self.actions.all_action_names()
|
||||
}
|
||||
|
||||
/// Get a list of all deprecated action aliases and their canonical names.
|
||||
pub fn action_deprecations(&self) -> &[(SharedString, SharedString)] {
|
||||
self.actions.action_deprecations()
|
||||
}
|
||||
|
||||
/// Register a callback to be invoked when the application is about to quit.
|
||||
/// It is not possible to cancel the quit event at this point.
|
||||
pub fn on_app_quit<Fut>(
|
||||
|
||||
@@ -134,6 +134,8 @@ impl Keymap {
|
||||
/// If a user has disabled a binding with `"x": null` it will not be returned. Disabled
|
||||
/// bindings are evaluated with the same precedence rules so you can disable a rule in
|
||||
/// a given context only.
|
||||
///
|
||||
/// In the case of multi-key bindings, the
|
||||
pub fn bindings_for_input(
|
||||
&self,
|
||||
input: &[Keystroke],
|
||||
|
||||
@@ -47,10 +47,6 @@ impl LinuxClient for HeadlessClient {
|
||||
f(&mut self.0.borrow_mut().common)
|
||||
}
|
||||
|
||||
fn keyboard_layout(&self) -> String {
|
||||
"unknown".to_string()
|
||||
}
|
||||
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{
|
||||
env,
|
||||
panic::AssertUnwindSafe,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
rc::Rc,
|
||||
@@ -17,7 +18,7 @@ use std::{
|
||||
use anyhow::{anyhow, Context as _};
|
||||
use async_task::Runnable;
|
||||
use calloop::{channel::Channel, LoopSignal};
|
||||
use futures::channel::oneshot;
|
||||
use futures::{channel::oneshot, future::FutureExt};
|
||||
use util::ResultExt as _;
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use xkbcommon::xkb::{self, Keycode, Keysym, State};
|
||||
@@ -45,7 +46,6 @@ const FILE_PICKER_PORTAL_MISSING: &str =
|
||||
pub trait LinuxClient {
|
||||
fn compositor_name(&self) -> &'static str;
|
||||
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
|
||||
fn keyboard_layout(&self) -> String;
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
||||
#[allow(unused)]
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
@@ -76,7 +76,6 @@ pub(crate) struct PlatformHandlers {
|
||||
pub(crate) app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
|
||||
pub(crate) will_open_app_menu: Option<Box<dyn FnMut()>>,
|
||||
pub(crate) validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
||||
pub(crate) keyboard_layout_change: Option<Box<dyn FnMut()>>,
|
||||
}
|
||||
|
||||
pub(crate) struct LinuxCommon {
|
||||
@@ -134,11 +133,11 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
}
|
||||
|
||||
fn keyboard_layout(&self) -> String {
|
||||
self.keyboard_layout()
|
||||
"unknown".into()
|
||||
}
|
||||
|
||||
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
|
||||
self.with_common(|common| common.callbacks.keyboard_layout_change = Some(callback));
|
||||
fn on_keyboard_layout_change(&self, _callback: Box<dyn FnMut()>) {
|
||||
// todo(linux)
|
||||
}
|
||||
|
||||
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
|
||||
@@ -484,7 +483,12 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
let username = attributes
|
||||
.get("username")
|
||||
.ok_or_else(|| anyhow!("Cannot find username in stored credentials"))?;
|
||||
let secret = item.secret().await?;
|
||||
// oo7 panics if the retrieved secret can't be decrypted due to
|
||||
// unexpected padding.
|
||||
let secret = AssertUnwindSafe(item.secret())
|
||||
.catch_unwind()
|
||||
.await
|
||||
.map_err(|_| anyhow!("oo7 panicked while trying to read credentials"))??;
|
||||
|
||||
// we lose the zeroizing capabilities at this boundary,
|
||||
// a current limitation GPUI's credentials api
|
||||
|
||||
@@ -588,19 +588,6 @@ impl WaylandClient {
|
||||
}
|
||||
|
||||
impl LinuxClient for WaylandClient {
|
||||
fn keyboard_layout(&self) -> String {
|
||||
let state = self.0.borrow();
|
||||
if let Some(keymap_state) = &state.keymap_state {
|
||||
let layout_idx = keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE);
|
||||
keymap_state
|
||||
.get_keymap()
|
||||
.layout_get_name(layout_idx)
|
||||
.to_string()
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
self.0
|
||||
.borrow()
|
||||
@@ -1152,13 +1139,6 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
};
|
||||
state.keymap_state = Some(xkb::State::new(&keymap));
|
||||
state.compose_state = get_xkb_compose_state(&xkb_context);
|
||||
|
||||
if let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take() {
|
||||
drop(state);
|
||||
callback();
|
||||
state = client.borrow_mut();
|
||||
state.common.callbacks.keyboard_layout_change = Some(callback);
|
||||
}
|
||||
}
|
||||
wl_keyboard::Event::Enter { surface, .. } => {
|
||||
state.keyboard_focused_window = get_window(&mut state, &surface.id());
|
||||
@@ -1196,21 +1176,9 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
let focused_window = state.keyboard_focused_window.clone();
|
||||
|
||||
let keymap_state = state.keymap_state.as_mut().unwrap();
|
||||
let old_layout =
|
||||
keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE);
|
||||
keymap_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
||||
state.modifiers = Modifiers::from_xkb(keymap_state);
|
||||
|
||||
if group != old_layout {
|
||||
if let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take()
|
||||
{
|
||||
drop(state);
|
||||
callback();
|
||||
state = client.borrow_mut();
|
||||
state.common.callbacks.keyboard_layout_change = Some(callback);
|
||||
}
|
||||
}
|
||||
|
||||
let Some(focused_window) = focused_window else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ use x11rb::{
|
||||
};
|
||||
use xim::{x11rb::X11rbClient, AttributeName, Client, InputStyle};
|
||||
use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
|
||||
use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask, STATE_LAYOUT_EFFECTIVE};
|
||||
use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask};
|
||||
|
||||
use super::{
|
||||
button_or_scroll_from_event_detail, get_valuator_axis_index, modifiers_from_state,
|
||||
@@ -840,8 +840,6 @@ impl X11Client {
|
||||
}
|
||||
Event::XkbStateNotify(event) => {
|
||||
let mut state = self.0.borrow_mut();
|
||||
let old_layout = state.xkb.serialize_layout(STATE_LAYOUT_EFFECTIVE);
|
||||
let new_layout = u32::from(event.group);
|
||||
state.xkb.update_mask(
|
||||
event.base_mods.into(),
|
||||
event.latched_mods.into(),
|
||||
@@ -855,17 +853,6 @@ impl X11Client {
|
||||
latched_layout: event.latched_group as u32,
|
||||
locked_layout: event.locked_group.into(),
|
||||
};
|
||||
|
||||
if new_layout != old_layout {
|
||||
if let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take()
|
||||
{
|
||||
drop(state);
|
||||
callback();
|
||||
state = self.0.borrow_mut();
|
||||
state.common.callbacks.keyboard_layout_change = Some(callback);
|
||||
}
|
||||
}
|
||||
|
||||
let modifiers = Modifiers::from_xkb(&state.xkb);
|
||||
if state.modifiers == modifiers {
|
||||
drop(state);
|
||||
@@ -1278,16 +1265,6 @@ impl LinuxClient for X11Client {
|
||||
f(&mut self.0.borrow_mut().common)
|
||||
}
|
||||
|
||||
fn keyboard_layout(&self) -> String {
|
||||
let state = self.0.borrow();
|
||||
let layout_idx = state.xkb.serialize_layout(STATE_LAYOUT_EFFECTIVE);
|
||||
state
|
||||
.xkb
|
||||
.get_keymap()
|
||||
.layout_get_name(layout_idx)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
let state = self.0.borrow();
|
||||
let setup = state.xcb_connection.setup();
|
||||
|
||||
@@ -227,27 +227,21 @@ fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
|
||||
}
|
||||
}
|
||||
|
||||
float alpha;
|
||||
if (shadow.blur_radius == 0.) {
|
||||
float distance = quad_sdf(input.position.xy, shadow.bounds, shadow.corner_radii);
|
||||
alpha = saturate(0.5 - distance);
|
||||
} else {
|
||||
// The signal is only non-zero in a limited range, so don't waste samples
|
||||
float low = point.y - half_size.y;
|
||||
float high = point.y + half_size.y;
|
||||
float start = clamp(-3. * shadow.blur_radius, low, high);
|
||||
float end = clamp(3. * shadow.blur_radius, low, high);
|
||||
// The signal is only non-zero in a limited range, so don't waste samples
|
||||
float low = point.y - half_size.y;
|
||||
float high = point.y + half_size.y;
|
||||
float start = clamp(-3. * shadow.blur_radius, low, high);
|
||||
float end = clamp(3. * shadow.blur_radius, low, high);
|
||||
|
||||
// Accumulate samples (we can get away with surprisingly few samples)
|
||||
float step = (end - start) / 4.;
|
||||
float y = start + step * 0.5;
|
||||
alpha = 0.;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
|
||||
corner_radius, half_size) *
|
||||
gaussian(y, shadow.blur_radius) * step;
|
||||
y += step;
|
||||
}
|
||||
// Accumulate samples (we can get away with surprisingly few samples)
|
||||
float step = (end - start) / 4.;
|
||||
float y = start + step * 0.5;
|
||||
float alpha = 0.;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
|
||||
corner_radius, half_size) *
|
||||
gaussian(y, shadow.blur_radius) * step;
|
||||
y += step;
|
||||
}
|
||||
|
||||
return input.color * float4(1., 1., 1., alpha);
|
||||
|
||||
@@ -32,7 +32,6 @@ pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream {
|
||||
fn #action_builder_fn_name() -> gpui::ActionData {
|
||||
gpui::ActionData {
|
||||
name: <#type_name as gpui::Action>::debug_name(),
|
||||
aliases: <#type_name as gpui::Action>::deprecated_aliases(),
|
||||
type_id: ::std::any::TypeId::of::<#type_name>(),
|
||||
build: <#type_name as gpui::Action>::build,
|
||||
}
|
||||
|
||||
@@ -24,6 +24,3 @@ theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[features]
|
||||
test-support = ["gpui/test-support"]
|
||||
|
||||
@@ -21,7 +21,6 @@ pub trait InlineCompletionProvider: 'static + Sized {
|
||||
fn name() -> &'static str;
|
||||
fn display_name() -> &'static str;
|
||||
fn show_completions_in_menu() -> bool;
|
||||
fn show_completions_in_normal_mode() -> bool;
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
@@ -62,7 +61,6 @@ pub trait InlineCompletionProviderHandle {
|
||||
cx: &AppContext,
|
||||
) -> bool;
|
||||
fn show_completions_in_menu(&self) -> bool;
|
||||
fn show_completions_in_normal_mode(&self) -> bool;
|
||||
fn refresh(
|
||||
&self,
|
||||
buffer: Model<Buffer>,
|
||||
@@ -103,10 +101,6 @@ where
|
||||
T::show_completions_in_menu()
|
||||
}
|
||||
|
||||
fn show_completions_in_normal_mode(&self) -> bool {
|
||||
T::show_completions_in_normal_mode()
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
|
||||
@@ -208,10 +208,10 @@ pub struct Diagnostic {
|
||||
/// The human-readable message associated with this diagnostic.
|
||||
pub message: String,
|
||||
/// An id that identifies the group to which this diagnostic belongs.
|
||||
/// 0 is used for diagnostics that do not come from a language server.
|
||||
///
|
||||
/// When a language server produces a diagnostic with
|
||||
/// one or more associated diagnostics, those diagnostics are all
|
||||
/// assigned a single group ID.
|
||||
/// When a language server produces a diagnostic with one or more associated diagnostics, those
|
||||
/// diagnostics are all assigned a single group ID.
|
||||
pub group_id: usize,
|
||||
/// Whether this diagnostic is the primary diagnostic for its group.
|
||||
///
|
||||
|
||||
@@ -3115,8 +3115,8 @@ fn html_lang() -> Language {
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(script_element
|
||||
(raw_text) @injection.content
|
||||
(#set! injection.language "javascript"))
|
||||
(raw_text) @content
|
||||
(#set! "language" "javascript"))
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
@@ -3138,15 +3138,15 @@ fn erb_lang() -> Language {
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(
|
||||
(code) @injection.content
|
||||
(#set! injection.language "ruby")
|
||||
(#set! injection.combined)
|
||||
(code) @content
|
||||
(#set! "language" "ruby")
|
||||
(#set! "combined")
|
||||
)
|
||||
|
||||
(
|
||||
(content) @injection.content
|
||||
(#set! injection.language "html")
|
||||
(#set! injection.combined)
|
||||
(content) @content
|
||||
(#set! "language" "html")
|
||||
(#set! "combined")
|
||||
)
|
||||
"#,
|
||||
)
|
||||
@@ -3278,11 +3278,11 @@ pub fn markdown_lang() -> Language {
|
||||
r#"
|
||||
(fenced_code_block
|
||||
(info_string
|
||||
(language) @injection.language)
|
||||
(code_fence_content) @injection.content)
|
||||
(language) @language)
|
||||
(code_fence_content) @content)
|
||||
|
||||
((inline) @injection.content
|
||||
(#set! injection.language "markdown-inline"))
|
||||
((inline) @content
|
||||
(#set! "language" "markdown-inline"))
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::{range_to_lsp, Diagnostic};
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use lsp::LanguageServerId;
|
||||
use std::{
|
||||
@@ -55,16 +54,16 @@ pub struct Summary {
|
||||
impl DiagnosticEntry<PointUtf16> {
|
||||
/// Returns a raw LSP diagnostic used to provide diagnostic context to LSP
|
||||
/// codeAction request
|
||||
pub fn to_lsp_diagnostic_stub(&self) -> Result<lsp::Diagnostic> {
|
||||
pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic {
|
||||
let code = self
|
||||
.diagnostic
|
||||
.code
|
||||
.clone()
|
||||
.map(lsp::NumberOrString::String);
|
||||
|
||||
let range = range_to_lsp(self.range.clone())?;
|
||||
let range = range_to_lsp(self.range.clone());
|
||||
|
||||
Ok(lsp::Diagnostic {
|
||||
lsp::Diagnostic {
|
||||
code,
|
||||
range,
|
||||
severity: Some(self.diagnostic.severity),
|
||||
@@ -72,7 +71,7 @@ impl DiagnosticEntry<PointUtf16> {
|
||||
message: self.diagnostic.message.clone(),
|
||||
data: self.diagnostic.data.clone(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1273,45 +1273,23 @@ impl Language {
|
||||
.ok_or_else(|| anyhow!("cannot mutate grammar"))?;
|
||||
let query = Query::new(&grammar.ts_language, source)?;
|
||||
let mut language_capture_ix = None;
|
||||
let mut injection_language_capture_ix = None;
|
||||
let mut content_capture_ix = None;
|
||||
let mut injection_content_capture_ix = None;
|
||||
get_capture_indices(
|
||||
&query,
|
||||
&mut [
|
||||
("language", &mut language_capture_ix),
|
||||
("injection.language", &mut injection_language_capture_ix),
|
||||
("content", &mut content_capture_ix),
|
||||
("injection.content", &mut injection_content_capture_ix),
|
||||
],
|
||||
);
|
||||
language_capture_ix = match (language_capture_ix, injection_language_capture_ix) {
|
||||
(None, Some(ix)) => Some(ix),
|
||||
(Some(_), Some(_)) => {
|
||||
return Err(anyhow!(
|
||||
"both language and injection.language captures are present"
|
||||
));
|
||||
}
|
||||
_ => language_capture_ix,
|
||||
};
|
||||
content_capture_ix = match (content_capture_ix, injection_content_capture_ix) {
|
||||
(None, Some(ix)) => Some(ix),
|
||||
(Some(_), Some(_)) => {
|
||||
return Err(anyhow!(
|
||||
"both content and injection.content captures are present"
|
||||
));
|
||||
}
|
||||
_ => content_capture_ix,
|
||||
};
|
||||
let patterns = (0..query.pattern_count())
|
||||
.map(|ix| {
|
||||
let mut config = InjectionPatternConfig::default();
|
||||
for setting in query.property_settings(ix) {
|
||||
match setting.key.as_ref() {
|
||||
"language" | "injection.language" => {
|
||||
"language" => {
|
||||
config.language.clone_from(&setting.value);
|
||||
}
|
||||
"combined" | "injection.combined" => {
|
||||
"combined" => {
|
||||
config.combined = true;
|
||||
}
|
||||
_ => {}
|
||||
@@ -1871,19 +1849,14 @@ pub fn point_from_lsp(point: lsp::Position) -> Unclipped<PointUtf16> {
|
||||
Unclipped(PointUtf16::new(point.line, point.character))
|
||||
}
|
||||
|
||||
pub fn range_to_lsp(range: Range<PointUtf16>) -> Result<lsp::Range> {
|
||||
if range.start > range.end {
|
||||
Err(anyhow!(
|
||||
"Inverted range provided to an LSP request: {:?}-{:?}",
|
||||
range.start,
|
||||
range.end
|
||||
))
|
||||
} else {
|
||||
Ok(lsp::Range {
|
||||
start: point_to_lsp(range.start),
|
||||
end: point_to_lsp(range.end),
|
||||
})
|
||||
pub fn range_to_lsp(range: Range<PointUtf16>) -> lsp::Range {
|
||||
let mut start = point_to_lsp(range.start);
|
||||
let mut end = point_to_lsp(range.end);
|
||||
if start > end {
|
||||
log::error!("range_to_lsp called with inverted range {start:?}-{end:?}");
|
||||
mem::swap(&mut start, &mut end);
|
||||
}
|
||||
lsp::Range { start, end }
|
||||
}
|
||||
|
||||
pub fn range_from_lsp(range: lsp::Range) -> Range<Unclipped<PointUtf16>> {
|
||||
|
||||
@@ -1193,15 +1193,15 @@ fn erb_lang() -> Language {
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(
|
||||
(code) @injection.content
|
||||
(#set! injection.language "ruby")
|
||||
(#set! injection.combined)
|
||||
(code) @content
|
||||
(#set! "language" "ruby")
|
||||
(#set! "combined")
|
||||
)
|
||||
|
||||
(
|
||||
(content) @injection.content
|
||||
(#set! injection.language "html")
|
||||
(#set! injection.combined)
|
||||
(content) @content
|
||||
(#set! "language" "html")
|
||||
(#set! "combined")
|
||||
)
|
||||
"#,
|
||||
)
|
||||
@@ -1230,8 +1230,8 @@ fn rust_lang() -> Language {
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(macro_invocation
|
||||
(token_tree) @injection.content
|
||||
(#set! injection.language "rust"))
|
||||
(token_tree) @content
|
||||
(#set! "language" "rust"))
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
@@ -1277,13 +1277,13 @@ fn heex_lang() -> Language {
|
||||
(partial_expression_value)
|
||||
(expression_value)
|
||||
(ending_expression_value)
|
||||
] @injection.content)
|
||||
(#set! injection.language "elixir")
|
||||
(#set! injection.combined)
|
||||
] @content)
|
||||
(#set! language "elixir")
|
||||
(#set! combined)
|
||||
)
|
||||
|
||||
((expression (expression_value) @injection.content)
|
||||
(#set! injection.language "elixir"))
|
||||
((expression (expression_value) @content)
|
||||
(#set! language "elixir"))
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
|
||||
@@ -88,7 +88,6 @@ impl CloudModel {
|
||||
Self::Google(model) => match model {
|
||||
google_ai::Model::Gemini15Pro
|
||||
| google_ai::Model::Gemini15Flash
|
||||
| google_ai::Model::Gemini20Flash
|
||||
| google_ai::Model::Custom { .. } => {
|
||||
LanguageModelAvailability::RequiresPlan(Plan::ZedPro)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use gpui::{
|
||||
use language::LanguageServerId;
|
||||
use lsp::{
|
||||
notification::SetTrace, IoKind, LanguageServer, LanguageServerName, MessageType,
|
||||
SetTraceParams, TraceValue,
|
||||
ServerCapabilities, SetTraceParams, TraceValue,
|
||||
};
|
||||
use project::{search::SearchQuery, Project, WorktreeId};
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
@@ -108,6 +108,7 @@ struct LanguageServerState {
|
||||
rpc_state: Option<LanguageServerRpcState>,
|
||||
trace_level: TraceValue,
|
||||
log_level: MessageType,
|
||||
capabilities: ServerCapabilities,
|
||||
io_logs_subscription: Option<lsp::Subscription>,
|
||||
}
|
||||
|
||||
@@ -177,7 +178,7 @@ pub enum LogKind {
|
||||
Trace,
|
||||
#[default]
|
||||
Logs,
|
||||
ServerInfo,
|
||||
Capabilities,
|
||||
}
|
||||
|
||||
impl LogKind {
|
||||
@@ -186,7 +187,7 @@ impl LogKind {
|
||||
LogKind::Rpc => RPC_MESSAGES,
|
||||
LogKind::Trace => SERVER_TRACE,
|
||||
LogKind::Logs => SERVER_LOGS,
|
||||
LogKind::ServerInfo => SERVER_INFO,
|
||||
LogKind::Capabilities => SERVER_CAPABILITIES,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,11 +324,7 @@ impl LogStore {
|
||||
*id,
|
||||
Some(name.clone()),
|
||||
*worktree_id,
|
||||
project
|
||||
.read(cx)
|
||||
.lsp_store()
|
||||
.read(cx)
|
||||
.language_server_for_id(*id),
|
||||
project.read(cx).language_server_for_id(*id, cx),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
@@ -381,6 +378,7 @@ impl LogStore {
|
||||
trace_level: TraceValue::Off,
|
||||
log_level: MessageType::LOG,
|
||||
io_logs_subscription: None,
|
||||
capabilities: ServerCapabilities::default(),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -404,6 +402,10 @@ impl LogStore {
|
||||
}));
|
||||
}
|
||||
|
||||
if let Some(server) = server {
|
||||
server_state.capabilities = server.capabilities();
|
||||
}
|
||||
|
||||
Some(server_state)
|
||||
}
|
||||
|
||||
@@ -488,6 +490,10 @@ impl LogStore {
|
||||
Some(&self.language_servers.get(&server_id)?.trace_messages)
|
||||
}
|
||||
|
||||
fn server_capabilities(&self, server_id: LanguageServerId) -> Option<&ServerCapabilities> {
|
||||
Some(&self.language_servers.get(&server_id)?.capabilities)
|
||||
}
|
||||
|
||||
fn server_ids_for_project<'a>(
|
||||
&'a self,
|
||||
lookup_project: &'a WeakModel<Project>,
|
||||
@@ -613,7 +619,9 @@ impl LspLogView {
|
||||
LogKind::Rpc => this.show_rpc_trace_for_server(server_id, cx),
|
||||
LogKind::Trace => this.show_trace_for_server(server_id, cx),
|
||||
LogKind::Logs => this.show_logs_for_server(server_id, cx),
|
||||
LogKind::ServerInfo => this.show_server_info(server_id, cx),
|
||||
LogKind::Capabilities => {
|
||||
this.show_capabilities_for_server(server_id, cx)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.current_server_id = None;
|
||||
@@ -630,7 +638,7 @@ impl LspLogView {
|
||||
LogKind::Rpc => this.show_rpc_trace_for_server(server_id, cx),
|
||||
LogKind::Trace => this.show_trace_for_server(server_id, cx),
|
||||
LogKind::Logs => this.show_logs_for_server(server_id, cx),
|
||||
LogKind::ServerInfo => this.show_server_info(server_id, cx),
|
||||
LogKind::Capabilities => this.show_capabilities_for_server(server_id, cx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,28 +712,14 @@ impl LspLogView {
|
||||
(editor, vec![editor_subscription, search_subscription])
|
||||
}
|
||||
|
||||
fn editor_for_server_info(
|
||||
server: &LanguageServer,
|
||||
fn editor_for_capabilities(
|
||||
capabilities: ServerCapabilities,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> (View<Editor>, Vec<Subscription>) {
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::multi_line(cx);
|
||||
let server_info = format!(
|
||||
"* Server: {NAME} (id {ID})
|
||||
|
||||
* Binary: {BINARY:#?}
|
||||
|
||||
* Running in project: {PATH:?}
|
||||
|
||||
* Capabilities: {CAPABILITIES}",
|
||||
NAME = server.name(),
|
||||
ID = server.server_id(),
|
||||
BINARY = server.binary(),
|
||||
PATH = server.root_path(),
|
||||
CAPABILITIES = serde_json::to_string_pretty(&server.capabilities())
|
||||
.unwrap_or_else(|e| format!("Failed to serialize capabilities: {e}")),
|
||||
);
|
||||
editor.set_text(server_info, cx);
|
||||
editor.set_text(serde_json::to_string_pretty(&capabilities).unwrap(), cx);
|
||||
editor.move_to_end(&MoveToEnd, cx);
|
||||
editor.set_read_only(true);
|
||||
editor.set_show_inline_completions(Some(false), cx);
|
||||
editor
|
||||
@@ -933,13 +927,7 @@ impl LspLogView {
|
||||
level: TraceValue,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let Some(server) = self
|
||||
.project
|
||||
.read(cx)
|
||||
.lsp_store()
|
||||
.read(cx)
|
||||
.language_server_for_id(server_id)
|
||||
{
|
||||
if let Some(server) = self.project.read(cx).language_server_for_id(server_id, cx) {
|
||||
self.log_store.update(cx, |this, _| {
|
||||
if let Some(state) = this.get_language_server_state(server_id) {
|
||||
state.trace_level = level;
|
||||
@@ -952,17 +940,22 @@ impl LspLogView {
|
||||
}
|
||||
}
|
||||
|
||||
fn show_server_info(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
|
||||
let lsp_store = self.project.read(cx).lsp_store();
|
||||
let Some(server) = lsp_store.read(cx).language_server_for_id(server_id) else {
|
||||
return;
|
||||
};
|
||||
self.current_server_id = Some(server_id);
|
||||
self.active_entry_kind = LogKind::ServerInfo;
|
||||
let (editor, editor_subscriptions) = Self::editor_for_server_info(&server, cx);
|
||||
self.editor = editor;
|
||||
self.editor_subscriptions = editor_subscriptions;
|
||||
cx.notify();
|
||||
fn show_capabilities_for_server(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let capabilities = self.log_store.read(cx).server_capabilities(server_id);
|
||||
|
||||
if let Some(capabilities) = capabilities {
|
||||
self.current_server_id = Some(server_id);
|
||||
self.active_entry_kind = LogKind::Capabilities;
|
||||
let (editor, editor_subscriptions) =
|
||||
Self::editor_for_capabilities(capabilities.clone(), cx);
|
||||
self.editor = editor;
|
||||
self.editor_subscriptions = editor_subscriptions;
|
||||
cx.notify();
|
||||
}
|
||||
cx.focus(&self.focus_handle);
|
||||
}
|
||||
}
|
||||
@@ -1033,7 +1026,7 @@ impl Item for LspLogView {
|
||||
LogKind::Rpc => new_view.show_rpc_trace_for_server(server_id, cx),
|
||||
LogKind::Trace => new_view.show_trace_for_server(server_id, cx),
|
||||
LogKind::Logs => new_view.show_logs_for_server(server_id, cx),
|
||||
LogKind::ServerInfo => new_view.show_server_info(server_id, cx),
|
||||
LogKind::Capabilities => new_view.show_capabilities_for_server(server_id, cx),
|
||||
}
|
||||
}
|
||||
new_view
|
||||
@@ -1194,7 +1187,9 @@ impl Render for LspLogToolbarItemView {
|
||||
}
|
||||
LogKind::Trace => view.show_trace_for_server(server_id, cx),
|
||||
LogKind::Logs => view.show_logs_for_server(server_id, cx),
|
||||
LogKind::ServerInfo => view.show_server_info(server_id, cx),
|
||||
LogKind::Capabilities => {
|
||||
view.show_capabilities_for_server(server_id, cx)
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}),
|
||||
@@ -1277,10 +1272,10 @@ impl Render for LspLogToolbarItemView {
|
||||
)
|
||||
})
|
||||
.entry(
|
||||
SERVER_INFO,
|
||||
SERVER_CAPABILITIES,
|
||||
None,
|
||||
cx.handler_for(&log_view, move |view, cx| {
|
||||
view.show_server_info(server_id, cx);
|
||||
view.show_capabilities_for_server(server_id, cx);
|
||||
}),
|
||||
)
|
||||
}))
|
||||
@@ -1439,7 +1434,7 @@ impl Render for LspLogToolbarItemView {
|
||||
const RPC_MESSAGES: &str = "RPC Messages";
|
||||
const SERVER_LOGS: &str = "Server Logs";
|
||||
const SERVER_TRACE: &str = "Server Trace";
|
||||
const SERVER_INFO: &str = "Server Info";
|
||||
const SERVER_CAPABILITIES: &str = "Server Capabilities";
|
||||
|
||||
impl Default for LspLogToolbarItemView {
|
||||
fn default() -> Self {
|
||||
|
||||
@@ -132,12 +132,14 @@ impl SyntaxTreeView {
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.snapshot(cx));
|
||||
let (excerpt, buffer, range) = editor_state.editor.update(cx, |editor, cx| {
|
||||
let selection = editor.selections.last::<usize>(cx);
|
||||
let selection_range = editor.selections.last::<usize>(cx).range();
|
||||
let selection_head = selection.head();
|
||||
let multi_buffer = editor.buffer().read(cx);
|
||||
let (excerpt, range) = snapshot
|
||||
let excerpt = snapshot
|
||||
.buffer_snapshot
|
||||
.range_to_buffer_ranges(selection_range)
|
||||
.pop()?;
|
||||
.excerpt_containing(selection_head..selection_head)?;
|
||||
let range = excerpt.map_range_to_buffer(selection_range);
|
||||
let buffer = multi_buffer.buffer(excerpt.buffer_id()).unwrap().clone();
|
||||
Some((excerpt, buffer, range))
|
||||
})?;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(preproc_def
|
||||
value: (preproc_arg) @injection.content
|
||||
(#set! injection.language "c"))
|
||||
value: (preproc_arg) @content
|
||||
(#set! "language" "c"))
|
||||
|
||||
(preproc_function_def
|
||||
value: (preproc_arg) @injection.content
|
||||
(#set! injection.language "c"))
|
||||
value: (preproc_arg) @content
|
||||
(#set! "language" "c"))
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
(preproc_def
|
||||
value: (preproc_arg) @injection.content
|
||||
(#set! injection.language "c++"))
|
||||
value: (preproc_arg) @content
|
||||
(#set! "language" "c++"))
|
||||
|
||||
(preproc_function_def
|
||||
value: (preproc_arg) @injection.content
|
||||
(#set! injection.language "c++"))
|
||||
value: (preproc_arg) @content
|
||||
(#set! "language" "c++"))
|
||||
|
||||
(raw_string_literal
|
||||
delimiter: (raw_string_delimiter) @injection.language
|
||||
(raw_string_content) @injection.content)
|
||||
delimiter: (raw_string_delimiter) @language
|
||||
(raw_string_content) @content)
|
||||
|
||||
@@ -43,12 +43,6 @@ static GO_ESCAPE_SUBTEST_NAME_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r#"[.*+?^${}()|\[\]\\]"#).expect("Failed to create GO_ESCAPE_SUBTEST_NAME_REGEX")
|
||||
});
|
||||
|
||||
const BINARY: &str = if cfg!(target_os = "windows") {
|
||||
"gopls.exe"
|
||||
} else {
|
||||
"gopls"
|
||||
};
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl super::LspAdapter for GoLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
@@ -170,7 +164,7 @@ impl super::LspAdapter for GoLspAdapter {
|
||||
return Err(anyhow!("failed to install gopls with `go install`. Is `go` installed and in the PATH? Check logs for more information."));
|
||||
}
|
||||
|
||||
let installed_binary_path = gobin_dir.join(BINARY);
|
||||
let installed_binary_path = gobin_dir.join("gopls");
|
||||
let version_output = util::command::new_smol_command(&installed_binary_path)
|
||||
.arg("version")
|
||||
.output()
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
[
|
||||
(raw_string_literal)
|
||||
(interpreted_string_literal)
|
||||
] @injection.content
|
||||
(#set! injection.language "regex")))
|
||||
] @content
|
||||
(#set! "language" "regex")))
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
(((comment) @_jsdoc_comment
|
||||
(#match? @_jsdoc_comment "(?s)^/[*][*][^*].*[*]/$")) @injection.content
|
||||
(#set! injection.language "jsdoc"))
|
||||
(#match? @_jsdoc_comment "(?s)^/[*][*][^*].*[*]/$")) @content
|
||||
(#set! "language" "jsdoc"))
|
||||
|
||||
((regex) @injection.content
|
||||
(#set! injection.language "regex"))
|
||||
((regex) @content
|
||||
(#set! "language" "regex"))
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "css")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "css"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "css"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "html")
|
||||
arguments: (template_string) @injection.content
|
||||
(#set! injection.language "html")
|
||||
arguments: (template_string) @content
|
||||
(#set! "language" "html")
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "js")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "javascript"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "javascript"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "json")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "json"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "json"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "sql")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "sql"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "sql"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "ts")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "typescript"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "typescript"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#match? @_name "^ya?ml$")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "yaml"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "yaml"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#match? @_name "^g(raph)?ql$")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "graphql"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "graphql"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#match? @_name "^g(raph)?ql$")
|
||||
arguments: (arguments (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "graphql")))
|
||||
arguments: (arguments (template_string (string_fragment) @content
|
||||
(#set! "language" "graphql")))
|
||||
)
|
||||
|
||||
@@ -76,7 +76,6 @@ impl JsonLspAdapter {
|
||||
|
||||
fn get_workspace_config(language_names: Vec<String>, cx: &mut AppContext) -> Value {
|
||||
let action_names = cx.all_action_names();
|
||||
let deprecations = cx.action_deprecations();
|
||||
|
||||
let font_names = &cx.text_system().all_font_names();
|
||||
let settings_schema = cx.global::<SettingsStore>().json_schema(
|
||||
@@ -117,7 +116,7 @@ impl JsonLspAdapter {
|
||||
},
|
||||
{
|
||||
"fileMatch": [schema_file_match(paths::keymap_file())],
|
||||
"schema": KeymapFile::generate_json_schema(action_names, deprecations),
|
||||
"schema": KeymapFile::generate_json_schema(action_names),
|
||||
},
|
||||
{
|
||||
"fileMatch": [
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
(fenced_code_block
|
||||
(info_string
|
||||
(language) @injection.language)
|
||||
(code_fence_content) @injection.content)
|
||||
(language) @language)
|
||||
(code_fence_content) @content)
|
||||
|
||||
((inline) @injection.content
|
||||
(#set! injection.language "markdown-inline"))
|
||||
((inline) @content
|
||||
(#set! "language" "markdown-inline"))
|
||||
|
||||
((html_block) @injection.content
|
||||
(#set! injection.language "html"))
|
||||
((html_block) @content
|
||||
(#set! "language" "html"))
|
||||
|
||||
((minus_metadata) @injection.content (#set! injection.language "yaml"))
|
||||
((minus_metadata) @content (#set! "language" "yaml"))
|
||||
|
||||
((plus_metadata) @injection.content (#set! injection.language "toml"))
|
||||
((plus_metadata) @content (#set! "language" "toml"))
|
||||
|
||||
@@ -542,7 +542,6 @@ static ENV_PRIORITY_LIST: &'static [PythonEnvironmentKind] = &[
|
||||
PythonEnvironmentKind::VirtualEnvWrapper,
|
||||
PythonEnvironmentKind::Venv,
|
||||
PythonEnvironmentKind::VirtualEnv,
|
||||
PythonEnvironmentKind::Pixi,
|
||||
PythonEnvironmentKind::Conda,
|
||||
PythonEnvironmentKind::Pyenv,
|
||||
PythonEnvironmentKind::GlobalPaths,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(macro_invocation
|
||||
(token_tree) @injection.content
|
||||
(#set! injection.language "rust"))
|
||||
(token_tree) @content
|
||||
(#set! "language" "rust"))
|
||||
|
||||
(macro_rule
|
||||
(token_tree) @injection.content
|
||||
(#set! injection.language "rust"))
|
||||
(token_tree) @content
|
||||
(#set! "language" "rust"))
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
.
|
||||
(attribute_item) *
|
||||
.
|
||||
[(line_comment) (block_comment)] *
|
||||
.
|
||||
(function_item
|
||||
name: (_) @run
|
||||
body: _
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
(((comment) @_jsdoc_comment
|
||||
(#match? @_jsdoc_comment "(?s)^/[*][*][^*].*[*]/$")) @injection.content
|
||||
(#set! injection.language "jsdoc"))
|
||||
(#match? @_jsdoc_comment "(?s)^/[*][*][^*].*[*]/$")) @content
|
||||
(#set! "language" "jsdoc"))
|
||||
|
||||
((regex) @injection.content
|
||||
(#set! injection.language "regex"))
|
||||
((regex) @content
|
||||
(#set! "language" "regex"))
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "css")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "css"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "css"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "html")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "html"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "html"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "js")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "javascript"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "javascript"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "json")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "json"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "json"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "sql")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "sql"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "sql"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "ts")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "typescript"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "typescript"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#match? @_name "^ya?ml$")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "yaml"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "yaml"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#match? @_name "^g(raph)?ql$")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "graphql"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "graphql"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#match? @_name "^g(raph)?ql$")
|
||||
arguments: (arguments (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "graphql")))
|
||||
arguments: (arguments (template_string (string_fragment) @content
|
||||
(#set! "language" "graphql")))
|
||||
)
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
(((comment) @_jsdoc_comment
|
||||
(#match? @_jsdoc_comment "(?s)^/[*][*][^*].*[*]/$")) @injection.content
|
||||
(#set! injection.language "jsdoc"))
|
||||
(#match? @_jsdoc_comment "(?s)^/[*][*][^*].*[*]/$")) @content
|
||||
(#set! "language" "jsdoc"))
|
||||
|
||||
(((comment) @reference
|
||||
(#match? @reference "^///\\s+<reference\\s+types=\"\\S+\"\\s*/>\\s*$")) @injection.content
|
||||
(#set! injection.language "html"))
|
||||
(#match? @reference "^///\\s+<reference\\s+types=\"\\S+\"\\s*/>\\s*$")) @content
|
||||
(#set! "language" "html"))
|
||||
|
||||
((regex) @injection.content
|
||||
(#set! injection.language "regex"))
|
||||
((regex) @content
|
||||
(#set! "language" "regex"))
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "css")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "css"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "css"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "html")
|
||||
arguments: (template_string) @injection.content
|
||||
(#set! injection.language "html")
|
||||
arguments: (template_string) @content
|
||||
(#set! "language" "html")
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "js")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "javascript"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "javascript"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "json")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "json"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "json"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "sql")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "sql"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "sql"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "ts")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "typescript"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "typescript"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#match? @_name "^ya?ml$")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "yaml"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "yaml"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#match? @_name "^g(raph)?ql$")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "graphql"))
|
||||
arguments: (template_string (string_fragment) @content
|
||||
(#set! "language" "graphql"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#match? @_name "^g(raph)?ql$")
|
||||
arguments: (arguments (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "graphql")))
|
||||
arguments: (arguments (template_string (string_fragment) @content
|
||||
(#set! "language" "graphql")))
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ path = "src/livekit_client.rs"
|
||||
doctest = false
|
||||
|
||||
[[example]]
|
||||
name = "test_app_macos"
|
||||
name = "test_app"
|
||||
|
||||
[features]
|
||||
no-webrtc = []
|
||||
|
||||
@@ -82,7 +82,6 @@ pub struct LanguageServer {
|
||||
outbound_tx: channel::Sender<String>,
|
||||
name: LanguageServerName,
|
||||
process_name: Arc<str>,
|
||||
binary: LanguageServerBinary,
|
||||
capabilities: RwLock<ServerCapabilities>,
|
||||
code_action_kinds: Option<Vec<CodeActionKind>>,
|
||||
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
||||
@@ -348,7 +347,7 @@ impl LanguageServer {
|
||||
let mut server = util::command::new_smol_command(&binary.path)
|
||||
.current_dir(working_dir)
|
||||
.args(&binary.arguments)
|
||||
.envs(binary.env.clone().unwrap_or_default())
|
||||
.envs(binary.env.unwrap_or_default())
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
@@ -364,7 +363,7 @@ impl LanguageServer {
|
||||
let stdin = server.stdin.take().unwrap();
|
||||
let stdout = server.stdout.take().unwrap();
|
||||
let stderr = server.stderr.take().unwrap();
|
||||
let server = Self::new_internal(
|
||||
let mut server = Self::new_internal(
|
||||
server_id,
|
||||
server_name,
|
||||
stdin,
|
||||
@@ -375,7 +374,6 @@ impl LanguageServer {
|
||||
root_path,
|
||||
working_dir,
|
||||
code_action_kinds,
|
||||
binary,
|
||||
cx,
|
||||
move |notification| {
|
||||
log::info!(
|
||||
@@ -387,6 +385,10 @@ impl LanguageServer {
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(name) = binary.path.file_name() {
|
||||
server.process_name = name.to_string_lossy().into();
|
||||
}
|
||||
|
||||
Ok(server)
|
||||
}
|
||||
|
||||
@@ -402,7 +404,6 @@ impl LanguageServer {
|
||||
root_path: &Path,
|
||||
working_dir: &Path,
|
||||
code_action_kinds: Option<Vec<CodeActionKind>>,
|
||||
binary: LanguageServerBinary,
|
||||
cx: AsyncAppContext,
|
||||
on_unhandled_notification: F,
|
||||
) -> Self
|
||||
@@ -465,12 +466,7 @@ impl LanguageServer {
|
||||
response_handlers,
|
||||
io_handlers,
|
||||
name: server_name,
|
||||
process_name: binary
|
||||
.path
|
||||
.file_name()
|
||||
.map(|name| Arc::from(name.to_string_lossy()))
|
||||
.unwrap_or_default(),
|
||||
binary,
|
||||
process_name: Arc::default(),
|
||||
capabilities: Default::default(),
|
||||
code_action_kinds,
|
||||
next_id: Default::default(),
|
||||
@@ -1059,11 +1055,6 @@ impl LanguageServer {
|
||||
&self.root_path
|
||||
}
|
||||
|
||||
/// Language server's binary information.
|
||||
pub fn binary(&self) -> &LanguageServerBinary {
|
||||
&self.binary
|
||||
}
|
||||
|
||||
/// Sends a RPC request to the language server.
|
||||
///
|
||||
/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage)
|
||||
@@ -1287,13 +1278,12 @@ impl FakeLanguageServer {
|
||||
root,
|
||||
root,
|
||||
None,
|
||||
binary.clone(),
|
||||
cx.clone(),
|
||||
|_| {},
|
||||
);
|
||||
server.process_name = process_name;
|
||||
let fake = FakeLanguageServer {
|
||||
binary: binary.clone(),
|
||||
binary,
|
||||
server: Arc::new({
|
||||
let mut server = LanguageServer::new_internal(
|
||||
server_id,
|
||||
@@ -1306,8 +1296,7 @@ impl FakeLanguageServer {
|
||||
root,
|
||||
root,
|
||||
None,
|
||||
binary,
|
||||
cx.clone(),
|
||||
cx,
|
||||
move |msg| {
|
||||
notifications_tx
|
||||
.try_send((
|
||||
|
||||
@@ -51,12 +51,6 @@ Links are created using the format [http://zed.dev](https://zed.dev).
|
||||
|
||||
They can also be detected automatically, for example https://zed.dev/blog.
|
||||
|
||||
They may contain dollar signs:
|
||||
|
||||
[https://svelte.dev/docs/svelte/$state](https://svelte.dev/docs/svelte/$state)
|
||||
|
||||
https://svelte.dev/docs/svelte/$state
|
||||
|
||||
## Images
|
||||
Images are like links, but with an exclamation mark `!` in front.
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ pub fn parse_markdown(text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
||||
let mut options = Options::all();
|
||||
options.remove(pulldown_cmark::Options::ENABLE_DEFINITION_LIST);
|
||||
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
|
||||
options.remove(pulldown_cmark::Options::ENABLE_MATH);
|
||||
|
||||
let mut events = Vec::new();
|
||||
let mut within_link = false;
|
||||
|
||||
@@ -3894,7 +3894,7 @@ impl MultiBufferSnapshot {
|
||||
where
|
||||
T: 'a + ToOffset,
|
||||
{
|
||||
let mut ranges = self.range_to_buffer_ranges(range);
|
||||
let mut ranges = self.range_to_buffer_ranges(range).collect::<Vec<_>>();
|
||||
if reversed {
|
||||
ranges.reverse();
|
||||
}
|
||||
@@ -4157,80 +4157,60 @@ impl MultiBufferSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn range_to_buffer_ranges<T: ToOffset>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
) -> Vec<(MultiBufferExcerpt<'_>, Range<usize>)> {
|
||||
let start = range.start.to_offset(self);
|
||||
let end = range.end.to_offset(self);
|
||||
|
||||
let mut result = Vec::new();
|
||||
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
|
||||
cursor.seek(&start, Bias::Right, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev(&());
|
||||
}
|
||||
|
||||
while let Some(excerpt) = cursor.item() {
|
||||
if cursor.start().0 > end {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut end_before_newline = cursor.end(&()).0;
|
||||
if excerpt.has_trailing_newline {
|
||||
end_before_newline -= 1;
|
||||
}
|
||||
let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
|
||||
let start = excerpt_start + (cmp::max(start, cursor.start().0) - cursor.start().0);
|
||||
let end = excerpt_start + (cmp::min(end, end_before_newline) - cursor.start().0);
|
||||
result.push((
|
||||
MultiBufferExcerpt::new(&excerpt, *cursor.start()),
|
||||
start..end,
|
||||
));
|
||||
cursor.next(&());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns excerpts overlapping the given ranges. If range spans multiple excerpts returns one range for each excerpt
|
||||
/// Returns excerpts overlapping the given range. If range spans multiple excerpts returns one
|
||||
/// range for each excerpt.
|
||||
///
|
||||
/// The ranges are specified in the coordinate space of the multibuffer, not the individual excerpted buffers.
|
||||
/// Each returned excerpt's range is in the coordinate space of its source buffer.
|
||||
pub fn excerpts_in_ranges(
|
||||
pub fn range_to_buffer_ranges<T: ToOffset>(
|
||||
&self,
|
||||
ranges: impl IntoIterator<Item = Range<Anchor>>,
|
||||
) -> impl Iterator<Item = (ExcerptId, &BufferSnapshot, Range<usize>)> {
|
||||
let mut ranges = ranges.into_iter().map(|range| range.to_offset(self));
|
||||
range: Range<T>,
|
||||
) -> impl Iterator<Item = (MultiBufferExcerpt<'_>, Range<usize>)> {
|
||||
self.disjoint_ranges_to_buffer_ranges(iter::once(range))
|
||||
}
|
||||
|
||||
/// Returns excerpts overlapping the given ranges, which must be disjoint and sorted by start position.
|
||||
/// If range spans multiple excerpts returns one range for each excerpt
|
||||
///
|
||||
/// The ranges are specified in the coordinate space of the multibuffer, not the individual excerpted buffers.
|
||||
/// Each returned excerpt's range is in the coordinate space of its source buffer.
|
||||
pub fn disjoint_ranges_to_buffer_ranges<T: ToOffset>(
|
||||
&self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
) -> impl Iterator<Item = (MultiBufferExcerpt<'_>, Range<usize>)> {
|
||||
let mut ranges = ranges.into_iter();
|
||||
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
|
||||
cursor.next(&());
|
||||
let mut current_range = ranges.next();
|
||||
iter::from_fn(move || {
|
||||
let range = current_range.clone()?;
|
||||
if range.start >= cursor.end(&()).0 {
|
||||
cursor.seek_forward(&range.start, Bias::Right, &());
|
||||
if range.start == self.len() {
|
||||
let range = current_range.as_ref()?;
|
||||
let start = range.start.to_offset(self);
|
||||
let end = range.end.to_offset(self);
|
||||
if start >= cursor.end(&()).0 {
|
||||
cursor.seek_forward(&start, Bias::Right, &());
|
||||
if start == self.len() {
|
||||
cursor.prev(&());
|
||||
}
|
||||
}
|
||||
|
||||
let excerpt = cursor.item()?;
|
||||
let range_start_in_excerpt = cmp::max(range.start, cursor.start().0);
|
||||
let range_start_in_excerpt = cmp::max(start, cursor.start().0);
|
||||
let range_end_in_excerpt = if excerpt.has_trailing_newline {
|
||||
cmp::min(range.end, cursor.end(&()).0 - 1)
|
||||
cmp::min(end, cursor.end(&()).0 - 1)
|
||||
} else {
|
||||
cmp::min(range.end, cursor.end(&()).0)
|
||||
cmp::min(end, cursor.end(&()).0)
|
||||
};
|
||||
let buffer_range = MultiBufferExcerpt::new(excerpt, *cursor.start())
|
||||
let multi_buffer_excerpt = MultiBufferExcerpt::new(excerpt, *cursor.start());
|
||||
let buffer_range = multi_buffer_excerpt
|
||||
.map_range_to_buffer(range_start_in_excerpt..range_end_in_excerpt);
|
||||
|
||||
if range.end > cursor.end(&()).0 {
|
||||
if end > cursor.end(&()).0 {
|
||||
cursor.next(&());
|
||||
} else {
|
||||
current_range = ranges.next();
|
||||
}
|
||||
|
||||
Some((excerpt.id, &excerpt.buffer, buffer_range))
|
||||
Some((multi_buffer_excerpt, buffer_range))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1235,7 +1235,9 @@ fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) {
|
||||
);
|
||||
|
||||
let snapshot = multibuffer.read(cx).snapshot(cx);
|
||||
let excerpted_buffer_ranges = snapshot.range_to_buffer_ranges(start_ix..end_ix);
|
||||
let excerpted_buffer_ranges = snapshot
|
||||
.range_to_buffer_ranges(start_ix..end_ix)
|
||||
.collect::<Vec<_>>();
|
||||
let excerpted_buffers_text = excerpted_buffer_ranges
|
||||
.iter()
|
||||
.map(|(excerpt, buffer_range)| {
|
||||
@@ -1488,7 +1490,7 @@ fn test_excerpts_in_ranges_no_ranges(cx: &mut AppContext) {
|
||||
|
||||
let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
|
||||
|
||||
let mut excerpts = snapshot.excerpts_in_ranges(iter::from_fn(|| None));
|
||||
let mut excerpts = snapshot.disjoint_ranges_to_buffer_ranges::<usize>(iter::from_fn(|| None));
|
||||
|
||||
assert!(excerpts.next().is_none());
|
||||
}
|
||||
@@ -1587,12 +1589,12 @@ fn test_excerpts_in_ranges_range_inside_the_excerpt(cx: &mut AppContext) {
|
||||
)];
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(vec![range.clone()].into_iter())
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
.disjoint_ranges_to_buffer_ranges(vec![range.clone()].into_iter())
|
||||
.map(|(excerpt, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
excerpt.id(),
|
||||
excerpt.buffer().remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
@@ -1652,12 +1654,12 @@ fn test_excerpts_in_ranges_range_crosses_excerpts_boundary(cx: &mut AppContext)
|
||||
];
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(vec![expected_range.clone()].into_iter())
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
.disjoint_ranges_to_buffer_ranges(vec![expected_range.clone()].into_iter())
|
||||
.map(|(excerpt, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
excerpt.id(),
|
||||
excerpt.buffer().remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
@@ -1728,12 +1730,12 @@ fn test_excerpts_in_ranges_range_encloses_excerpt(cx: &mut AppContext) {
|
||||
];
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(vec![expected_range.clone()].into_iter())
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
.disjoint_ranges_to_buffer_ranges(vec![expected_range.clone()].into_iter())
|
||||
.map(|(excerpt, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
excerpt.id(),
|
||||
excerpt.buffer().remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
@@ -1794,12 +1796,12 @@ fn test_excerpts_in_ranges_multiple_ranges(cx: &mut AppContext) {
|
||||
});
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(ranges)
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
.disjoint_ranges_to_buffer_ranges(ranges)
|
||||
.map(|(excerpt, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
excerpt.id(),
|
||||
excerpt.buffer().remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
@@ -1860,12 +1862,12 @@ fn test_excerpts_in_ranges_range_ends_at_excerpt_end(cx: &mut AppContext) {
|
||||
];
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(ranges.into_iter())
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
.disjoint_ranges_to_buffer_ranges(ranges.into_iter())
|
||||
.map(|(excerpt, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
excerpt.id(),
|
||||
excerpt.buffer().remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
@@ -4656,27 +4656,18 @@ impl Panel for OutlinePanel {
|
||||
.update(&mut cx, |outline_panel, cx| {
|
||||
let old_active = outline_panel.active;
|
||||
outline_panel.active = active;
|
||||
if old_active != active {
|
||||
if active {
|
||||
if let Some((active_item, active_editor)) =
|
||||
outline_panel.workspace.upgrade().and_then(|workspace| {
|
||||
workspace_active_editor(workspace.read(cx), cx)
|
||||
})
|
||||
{
|
||||
if outline_panel.should_replace_active_item(active_item.as_ref()) {
|
||||
outline_panel.replace_active_editor(
|
||||
active_item,
|
||||
active_editor,
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
outline_panel.update_fs_entries(active_editor, None, cx)
|
||||
}
|
||||
return;
|
||||
if active && old_active != active {
|
||||
if let Some((active_item, active_editor)) = outline_panel
|
||||
.workspace
|
||||
.upgrade()
|
||||
.and_then(|workspace| workspace_active_editor(workspace.read(cx), cx))
|
||||
{
|
||||
if outline_panel.should_replace_active_item(active_item.as_ref()) {
|
||||
outline_panel.replace_active_editor(active_item, active_editor, cx);
|
||||
} else {
|
||||
outline_panel.update_fs_entries(active_editor, None, cx)
|
||||
}
|
||||
}
|
||||
|
||||
if !outline_panel.pinned {
|
||||
} else if !outline_panel.pinned {
|
||||
outline_panel.clear_previous(cx);
|
||||
}
|
||||
}
|
||||
@@ -4823,11 +4814,9 @@ fn subscribe_for_editor_events(
|
||||
cx: &mut ViewContext<OutlinePanel>,
|
||||
) -> Subscription {
|
||||
let debounce = Some(UPDATE_DEBOUNCE);
|
||||
cx.subscribe(editor, move |outline_panel, editor, e: &EditorEvent, cx| {
|
||||
if !outline_panel.active {
|
||||
return;
|
||||
}
|
||||
match e {
|
||||
cx.subscribe(
|
||||
editor,
|
||||
move |outline_panel, editor, e: &EditorEvent, cx| match e {
|
||||
EditorEvent::SelectionsChanged { local: true } => {
|
||||
outline_panel.reveal_entry_for_selection(editor, cx);
|
||||
cx.notify();
|
||||
@@ -4932,8 +4921,8 @@ fn subscribe_for_editor_events(
|
||||
outline_panel.update_non_fs_items(cx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn empty_icon() -> AnyElement {
|
||||
@@ -6043,8 +6032,8 @@ mod tests {
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(macro_invocation
|
||||
(token_tree) @injection.content
|
||||
(#set! injection.language "rust"))
|
||||
(token_tree) @content
|
||||
(#set! "language" "rust"))
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
|
||||
@@ -404,7 +404,7 @@ impl LocalBufferStore {
|
||||
cx: &AppContext,
|
||||
) -> Task<Result<Option<String>>> {
|
||||
let Some(file) = buffer.read(cx).file() else {
|
||||
return Task::ready(Ok(None));
|
||||
return Task::ready(Err(anyhow!("buffer has no file")));
|
||||
};
|
||||
let worktree_id = file.worktree_id(cx);
|
||||
let path = file.path().clone();
|
||||
|
||||
@@ -45,31 +45,6 @@ pub fn lsp_formatting_options(settings: &LanguageSettings) -> lsp::FormattingOpt
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn file_path_to_lsp_url(path: &Path) -> Result<lsp::Url> {
|
||||
match lsp::Url::from_file_path(path) {
|
||||
Ok(url) => Ok(url),
|
||||
Err(()) => Err(anyhow!(
|
||||
"Invalid file path provided to LSP request: {path:?}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn make_text_document_identifier(path: &Path) -> Result<lsp::TextDocumentIdentifier> {
|
||||
Ok(lsp::TextDocumentIdentifier {
|
||||
uri: file_path_to_lsp_url(path)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn make_lsp_text_document_position(
|
||||
path: &Path,
|
||||
position: PointUtf16,
|
||||
) -> Result<lsp::TextDocumentPositionParams> {
|
||||
Ok(lsp::TextDocumentPositionParams {
|
||||
text_document: make_text_document_identifier(path)?,
|
||||
position: point_to_lsp(position),
|
||||
})
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait LspCommand: 'static + Sized + Send + std::fmt::Debug {
|
||||
type Response: 'static + Default + Send + std::fmt::Debug;
|
||||
@@ -90,7 +65,7 @@ pub trait LspCommand: 'static + Sized + Send + std::fmt::Debug {
|
||||
buffer: &Buffer,
|
||||
language_server: &Arc<LanguageServer>,
|
||||
cx: &AppContext,
|
||||
) -> Result<<Self::LspRequest as lsp::request::Request>::Params>;
|
||||
) -> <Self::LspRequest as lsp::request::Request>::Params;
|
||||
|
||||
async fn response_from_lsp(
|
||||
self,
|
||||
@@ -227,8 +202,13 @@ impl LspCommand for PrepareRename {
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::TextDocumentPositionParams> {
|
||||
make_lsp_text_document_position(path, self.position)
|
||||
) -> lsp::TextDocumentPositionParams {
|
||||
lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -345,12 +325,17 @@ impl LspCommand for PerformRename {
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::RenameParams> {
|
||||
Ok(lsp::RenameParams {
|
||||
text_document_position: make_lsp_text_document_position(path, self.position)?,
|
||||
) -> lsp::RenameParams {
|
||||
lsp::RenameParams {
|
||||
text_document_position: lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
},
|
||||
new_name: self.new_name.clone(),
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -470,12 +455,17 @@ impl LspCommand for GetDefinition {
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::GotoDefinitionParams> {
|
||||
Ok(lsp::GotoDefinitionParams {
|
||||
text_document_position_params: make_lsp_text_document_position(path, self.position)?,
|
||||
) -> lsp::GotoDefinitionParams {
|
||||
lsp::GotoDefinitionParams {
|
||||
text_document_position_params: lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -565,12 +555,17 @@ impl LspCommand for GetDeclaration {
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::GotoDeclarationParams> {
|
||||
Ok(lsp::GotoDeclarationParams {
|
||||
text_document_position_params: make_lsp_text_document_position(path, self.position)?,
|
||||
) -> lsp::GotoDeclarationParams {
|
||||
lsp::GotoDeclarationParams {
|
||||
text_document_position_params: lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -653,12 +648,17 @@ impl LspCommand for GetImplementation {
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::GotoImplementationParams> {
|
||||
Ok(lsp::GotoImplementationParams {
|
||||
text_document_position_params: make_lsp_text_document_position(path, self.position)?,
|
||||
) -> lsp::GotoImplementationParams {
|
||||
lsp::GotoImplementationParams {
|
||||
text_document_position_params: lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -748,12 +748,17 @@ impl LspCommand for GetTypeDefinition {
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::GotoTypeDefinitionParams> {
|
||||
Ok(lsp::GotoTypeDefinitionParams {
|
||||
text_document_position_params: make_lsp_text_document_position(path, self.position)?,
|
||||
) -> lsp::GotoTypeDefinitionParams {
|
||||
lsp::GotoTypeDefinitionParams {
|
||||
text_document_position_params: lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -1056,15 +1061,20 @@ impl LspCommand for GetReferences {
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::ReferenceParams> {
|
||||
Ok(lsp::ReferenceParams {
|
||||
text_document_position: make_lsp_text_document_position(path, self.position)?,
|
||||
) -> lsp::ReferenceParams {
|
||||
lsp::ReferenceParams {
|
||||
text_document_position: lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
context: lsp::ReferenceContext {
|
||||
include_declaration: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -1227,12 +1237,17 @@ impl LspCommand for GetDocumentHighlights {
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::DocumentHighlightParams> {
|
||||
Ok(lsp::DocumentHighlightParams {
|
||||
text_document_position_params: make_lsp_text_document_position(path, self.position)?,
|
||||
) -> lsp::DocumentHighlightParams {
|
||||
lsp::DocumentHighlightParams {
|
||||
text_document_position_params: lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -1376,12 +1391,22 @@ impl LspCommand for GetSignatureHelp {
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_cx: &AppContext,
|
||||
) -> Result<lsp::SignatureHelpParams> {
|
||||
Ok(lsp::SignatureHelpParams {
|
||||
text_document_position_params: make_lsp_text_document_position(path, self.position)?,
|
||||
) -> lsp::SignatureHelpParams {
|
||||
let url_result = lsp::Url::from_file_path(path);
|
||||
if url_result.is_err() {
|
||||
log::error!("an invalid file path has been specified");
|
||||
}
|
||||
|
||||
lsp::SignatureHelpParams {
|
||||
text_document_position_params: lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: url_result.expect("invalid file path"),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
},
|
||||
context: None,
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -1480,11 +1505,16 @@ impl LspCommand for GetHover {
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::HoverParams> {
|
||||
Ok(lsp::HoverParams {
|
||||
text_document_position_params: make_lsp_text_document_position(path, self.position)?,
|
||||
) -> lsp::HoverParams {
|
||||
lsp::HoverParams {
|
||||
text_document_position_params: lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -1698,13 +1728,16 @@ impl LspCommand for GetCompletions {
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::CompletionParams> {
|
||||
Ok(lsp::CompletionParams {
|
||||
text_document_position: make_lsp_text_document_position(path, self.position)?,
|
||||
) -> lsp::CompletionParams {
|
||||
lsp::CompletionParams {
|
||||
text_document_position: lsp::TextDocumentPositionParams::new(
|
||||
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
|
||||
point_to_lsp(self.position),
|
||||
),
|
||||
context: Some(self.context.clone()),
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -2042,14 +2075,12 @@ impl LspCommand for GetCodeActions {
|
||||
buffer: &Buffer,
|
||||
language_server: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::CodeActionParams> {
|
||||
let mut relevant_diagnostics = Vec::new();
|
||||
for entry in buffer
|
||||
) -> lsp::CodeActionParams {
|
||||
let relevant_diagnostics = buffer
|
||||
.snapshot()
|
||||
.diagnostics_in_range::<_, language::PointUtf16>(self.range.clone(), false)
|
||||
{
|
||||
relevant_diagnostics.push(entry.to_lsp_diagnostic_stub()?);
|
||||
}
|
||||
.map(|entry| entry.to_lsp_diagnostic_stub())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let supported =
|
||||
Self::supported_code_action_kinds(language_server.adapter_server_capabilities());
|
||||
@@ -2071,9 +2102,11 @@ impl LspCommand for GetCodeActions {
|
||||
supported
|
||||
};
|
||||
|
||||
Ok(lsp::CodeActionParams {
|
||||
text_document: make_text_document_identifier(path)?,
|
||||
range: range_to_lsp(self.range.to_point_utf16(buffer))?,
|
||||
lsp::CodeActionParams {
|
||||
text_document: lsp::TextDocumentIdentifier::new(
|
||||
lsp::Url::from_file_path(path).unwrap(),
|
||||
),
|
||||
range: range_to_lsp(self.range.to_point_utf16(buffer)),
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
context: lsp::CodeActionContext {
|
||||
@@ -2081,7 +2114,7 @@ impl LspCommand for GetCodeActions {
|
||||
only,
|
||||
..lsp::CodeActionContext::default()
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -2253,12 +2286,15 @@ impl LspCommand for OnTypeFormatting {
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::DocumentOnTypeFormattingParams> {
|
||||
Ok(lsp::DocumentOnTypeFormattingParams {
|
||||
text_document_position: make_lsp_text_document_position(path, self.position)?,
|
||||
) -> lsp::DocumentOnTypeFormattingParams {
|
||||
lsp::DocumentOnTypeFormattingParams {
|
||||
text_document_position: lsp::TextDocumentPositionParams::new(
|
||||
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
|
||||
point_to_lsp(self.position),
|
||||
),
|
||||
ch: self.trigger.clone(),
|
||||
options: self.options.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -2756,14 +2792,14 @@ impl LspCommand for InlayHints {
|
||||
buffer: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::InlayHintParams> {
|
||||
Ok(lsp::InlayHintParams {
|
||||
) -> lsp::InlayHintParams {
|
||||
lsp::InlayHintParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: file_path_to_lsp_url(path)?,
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
range: range_to_lsp(self.range.to_point_utf16(buffer))?,
|
||||
range: range_to_lsp(self.range.to_point_utf16(buffer)),
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
@@ -2913,12 +2949,15 @@ impl LspCommand for LinkedEditingRange {
|
||||
buffer: &Buffer,
|
||||
_server: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> Result<lsp::LinkedEditingRangeParams> {
|
||||
) -> lsp::LinkedEditingRangeParams {
|
||||
let position = self.position.to_point_utf16(&buffer.snapshot());
|
||||
Ok(lsp::LinkedEditingRangeParams {
|
||||
text_document_position_params: make_lsp_text_document_position(path, position)?,
|
||||
lsp::LinkedEditingRangeParams {
|
||||
text_document_position_params: lsp::TextDocumentPositionParams::new(
|
||||
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
|
||||
point_to_lsp(position),
|
||||
),
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user