Compare commits

..

2 Commits

Author SHA1 Message Date
Conrad Irwin
2cbedcd2cb one more mutex -> parking_lot 2025-06-16 19:16:21 -06:00
Conrad Irwin
09660b165f debugger: remove unused current_requests 2025-06-16 17:06:23 -06:00
293 changed files with 4584 additions and 12114 deletions

View File

@@ -10,8 +10,8 @@ inputs:
runs:
using: "composite"
steps:
- name: Install test runner
shell: powershell
- name: Install Rust
shell: pwsh
working-directory: ${{ inputs.working-directory }}
run: cargo install cargo-nextest --locked
@@ -21,6 +21,6 @@ runs:
node-version: "18"
- name: Run tests
shell: powershell
shell: pwsh
working-directory: ${{ inputs.working-directory }}
run: cargo nextest run --workspace --no-fail-fast
run: cargo nextest run --workspace --no-fail-fast --config='profile.dev.debug="limited"'

View File

@@ -373,6 +373,64 @@ jobs:
if: always()
run: rm -rf ./../.cargo
windows_clippy:
timeout-minutes: 60
name: (Windows) Run Clippy
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on: windows-2025-16
steps:
# more info here:- https://github.com/rust-lang/cargo/issues/13020
- name: Enable longer pathnames for git
run: git config --system core.longpaths true
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Create Dev Drive using ReFS
run: ./script/setup-dev-driver.ps1
# actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
- name: Copy Git Repo to Dev Drive
run: |
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.ZED_WORKSPACE }}" -Recurse
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
workspaces: ${{ env.ZED_WORKSPACE }}
cache-provider: "github"
- name: Configure CI
run: |
mkdir -p ${{ env.CARGO_HOME }} -ErrorAction Ignore
cp ./.cargo/ci-config.toml ${{ env.CARGO_HOME }}/config.toml
- name: cargo clippy
working-directory: ${{ env.ZED_WORKSPACE }}
run: ./script/clippy.ps1
- name: Check dev drive space
working-directory: ${{ env.ZED_WORKSPACE }}
# `setup-dev-driver.ps1` creates a 100GB drive, with CI taking up ~45GB of the drive.
run: ./script/exit-ci-if-dev-drive-is-full.ps1 95
# Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug.
- name: Clean CI config file
if: always()
run: |
if (Test-Path "${{ env.CARGO_HOME }}/config.toml") {
Remove-Item -Path "${{ env.CARGO_HOME }}/config.toml" -Force
}
# Windows CI takes twice as long as our other platforms and fast github hosted runners are expensive.
# But we still want to do CI, so let's only run tests on main and come back to this when we're
# ready to self host our Windows CI (e.g. during the push for full Windows support)
windows_tests:
timeout-minutes: 60
name: (Windows) Run Tests
@@ -380,45 +438,51 @@ jobs:
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on: [self-hosted, Windows, X64]
# Use bigger runners for PRs (speed); smaller for async (cost)
runs-on: ${{ github.event_name == 'pull_request' && 'windows-2025-32' || 'windows-2025-16' }}
steps:
- name: Environment Setup
run: |
$RunnerDir = Split-Path -Parent $env:RUNNER_WORKSPACE
Write-Output `
"RUSTUP_HOME=$RunnerDir\.rustup" `
"CARGO_HOME=$RunnerDir\.cargo" `
"PATH=$RunnerDir\.cargo\bin;$env:PATH" `
>> $env:GITHUB_ENV
# more info here:- https://github.com/rust-lang/cargo/issues/13020
- name: Enable longer pathnames for git
run: git config --system core.longpaths true
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Setup Cargo and Rustup
- name: Create Dev Drive using ReFS
run: ./script/setup-dev-driver.ps1
# actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
- name: Copy Git Repo to Dev Drive
run: |
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.ZED_WORKSPACE }}" -Recurse
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
workspaces: ${{ env.ZED_WORKSPACE }}
cache-provider: "github"
- name: Configure CI
run: |
mkdir -p ${{ env.CARGO_HOME }} -ErrorAction Ignore
cp ./.cargo/ci-config.toml ${{ env.CARGO_HOME }}/config.toml
.\script\install-rustup.ps1
- name: cargo clippy
run: |
.\script\clippy.ps1
- name: Run tests
uses: ./.github/actions/run_tests_windows
with:
working-directory: ${{ env.ZED_WORKSPACE }}
- name: Build Zed
working-directory: ${{ env.ZED_WORKSPACE }}
run: cargo build
- name: Limit target directory size
run: ./script/clear-target-dir-if-larger-than.ps1 250
# - name: Check dev drive space
# working-directory: ${{ env.ZED_WORKSPACE }}
# # `setup-dev-driver.ps1` creates a 100GB drive, with CI taking up ~45GB of the drive.
# run: ./script/exit-ci-if-dev-drive-is-full.ps1 95
- name: Check dev drive space
working-directory: ${{ env.ZED_WORKSPACE }}
# `setup-dev-driver.ps1` creates a 100GB drive, with CI taking up ~45GB of the drive.
run: ./script/exit-ci-if-dev-drive-is-full.ps1 95
# Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug.
- name: Clean CI config file
@@ -441,6 +505,7 @@ jobs:
- linux_tests
- build_remote_server
- macos_tests
- windows_clippy
- windows_tests
if: |
github.repository_owner == 'zed-industries' &&
@@ -460,6 +525,7 @@ jobs:
[[ "${{ needs.macos_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "macOS tests failed"; }
[[ "${{ needs.linux_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "Linux tests failed"; }
[[ "${{ needs.windows_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "Windows tests failed"; }
[[ "${{ needs.windows_clippy.result }}" != 'success' ]] && { RET_CODE=1; echo "Windows clippy failed"; }
[[ "${{ needs.build_remote_server.result }}" != 'success' ]] && { RET_CODE=1; echo "Remote server build failed"; }
# This check is intentionally disabled. See: https://github.com/zed-industries/zed/pull/28431
# [[ "${{ needs.migration_checks.result }}" != 'success' ]] && { RET_CODE=1; echo "Migration Checks failed"; }

135
Cargo.lock generated
View File

@@ -2041,7 +2041,7 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.6.0"
source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5"
source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
dependencies = [
"ash",
"ash-window",
@@ -2074,7 +2074,7 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.3.0"
source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5"
source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
dependencies = [
"proc-macro2",
"quote",
@@ -2084,7 +2084,7 @@ dependencies = [
[[package]]
name = "blade-util"
version = "0.2.0"
source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5"
source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
dependencies = [
"blade-graphics",
"bytemuck",
@@ -2826,6 +2826,7 @@ dependencies = [
"futures 0.3.31",
"gpui",
"gpui_tokio",
"hickory-resolver",
"http_client",
"http_client_tls",
"httparse",
@@ -3346,7 +3347,6 @@ dependencies = [
"collections",
"command_palette_hooks",
"ctor",
"dirs 4.0.0",
"editor",
"fs",
"futures 0.3.31",
@@ -4117,7 +4117,6 @@ dependencies = [
"paths",
"serde",
"serde_json",
"shlex",
"task",
"util",
"workspace-hack",
@@ -4269,7 +4268,6 @@ dependencies = [
name = "debugger_ui"
version = "0.1.0"
dependencies = [
"alacritty_terminal",
"anyhow",
"client",
"collections",
@@ -4279,6 +4277,7 @@ dependencies = [
"db",
"debugger_tools",
"editor",
"feature_flags",
"file_icons",
"futures 0.3.31",
"fuzzy",
@@ -4295,17 +4294,13 @@ dependencies = [
"rpc",
"serde",
"serde_json",
"serde_json_lenient",
"settings",
"shlex",
"sysinfo",
"task",
"tasks_ui",
"telemetry",
"terminal_view",
"theme",
"tree-sitter",
"tree-sitter-json",
"ui",
"unindent",
"util",
@@ -4750,6 +4745,7 @@ dependencies = [
"dap",
"db",
"emojis",
"feature_flags",
"file_icons",
"fs",
"futures 0.3.31",
@@ -4912,6 +4908,18 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
[[package]]
name = "enum-as-inner"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
name = "enumflags2"
version = "0.7.11"
@@ -6208,7 +6216,6 @@ dependencies = [
"ui",
"unindent",
"util",
"watch",
"windows 0.61.1",
"workspace",
"workspace-hack",
@@ -7491,6 +7498,51 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "hickory-proto"
version = "0.24.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248"
dependencies = [
"async-trait",
"cfg-if",
"data-encoding",
"enum-as-inner",
"futures-channel",
"futures-io",
"futures-util",
"idna",
"ipnet",
"once_cell",
"rand 0.8.5",
"thiserror 1.0.69",
"tinyvec",
"tokio",
"tracing",
"url",
]
[[package]]
name = "hickory-resolver"
version = "0.24.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e"
dependencies = [
"cfg-if",
"futures-util",
"hickory-proto",
"ipconfig",
"lru-cache",
"once_cell",
"parking_lot",
"rand 0.8.5",
"resolv-conf",
"smallvec",
"thiserror 1.0.69",
"tokio",
"tracing",
]
[[package]]
name = "hidden-trait"
version = "0.1.2"
@@ -8360,6 +8412,18 @@ dependencies = [
"windows 0.58.0",
]
[[package]]
name = "ipconfig"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
dependencies = [
"socket2",
"widestring",
"windows-sys 0.48.0",
"winreg 0.50.0",
]
[[package]]
name = "ipnet"
version = "2.11.0"
@@ -8880,7 +8944,6 @@ dependencies = [
"http_client",
"icons",
"image",
"log",
"parking_lot",
"proto",
"schemars",
@@ -8916,7 +8979,6 @@ dependencies = [
"gpui",
"gpui_tokio",
"http_client",
"language",
"language_model",
"lmstudio",
"log",
@@ -8940,9 +9002,7 @@ dependencies = [
"tiktoken-rs",
"tokio",
"ui",
"ui_input",
"util",
"vercel",
"workspace-hack",
"zed_llm_client",
]
@@ -9248,6 +9308,12 @@ dependencies = [
"cc",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linkify"
version = "0.10.0"
@@ -9508,6 +9574,15 @@ dependencies = [
"hashbrown 0.15.3",
]
[[package]]
name = "lru-cache"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "lsp"
version = "0.1.0"
@@ -13144,7 +13219,6 @@ dependencies = [
"dap",
"dap_adapters",
"debug_adapter_extension",
"editor",
"env_logger 0.11.8",
"extension",
"extension_host",
@@ -13183,7 +13257,6 @@ dependencies = [
"unindent",
"util",
"watch",
"workspace",
"worktree",
"zlog",
]
@@ -13341,6 +13414,7 @@ dependencies = [
"futures-core",
"futures-util",
"h2 0.4.9",
"hickory-resolver",
"http 1.3.1",
"http-body 1.0.1",
"http-body-util",
@@ -13397,6 +13471,12 @@ dependencies = [
"workspace-hack",
]
[[package]]
name = "resolv-conf"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3"
[[package]]
name = "resvg"
version = "0.45.1"
@@ -15394,7 +15474,6 @@ dependencies = [
"serde",
"serde_json",
"smol",
"util",
"workspace-hack",
]
@@ -17381,17 +17460,6 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vercel"
version = "0.1.0"
dependencies = [
"anyhow",
"schemars",
"serde",
"strum 0.27.1",
"workspace-hack",
]
[[package]]
name = "version-compare"
version = "0.2.0"
@@ -18333,6 +18401,12 @@ dependencies = [
"wasite",
]
[[package]]
name = "widestring"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
[[package]]
name = "wiggle"
version = "29.0.1"
@@ -19874,7 +19948,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.192.8"
version = "0.192.0"
dependencies = [
"activity_indicator",
"agent",
@@ -19914,6 +19988,7 @@ dependencies = [
"extension",
"extension_host",
"extensions_ui",
"feature_flags",
"feedback",
"file_finder",
"fs",

View File

@@ -163,7 +163,6 @@ members = [
"crates/ui_prompt",
"crates/util",
"crates/util_macros",
"crates/vercel",
"crates/vim",
"crates/vim_mode_setting",
"crates/watch",
@@ -373,7 +372,6 @@ ui_macros = { path = "crates/ui_macros" }
ui_prompt = { path = "crates/ui_prompt" }
util = { path = "crates/util" }
util_macros = { path = "crates/util_macros" }
vercel = { path = "crates/vercel" }
vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" }
watch = { path = "crates/watch" }
@@ -419,9 +417,9 @@ aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
blade-util = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blade-util = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.19"
@@ -526,6 +524,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c77
"rustls-tls-native-roots",
"socks",
"stream",
"hickory-dns",
] }
rsa = "0.9.6"
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [

View File

@@ -1,16 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2639_570)">
<g clip-path="url(#clip1_2639_570)">
<path d="M9.85676 4H13.6675C15.2128 4 16.4654 5.25266 16.4654 6.7979V10.4322H14.9002V6.7979C14.9002 6.76067 14.8988 6.7237 14.8959 6.68706L11.0851 10.4316C11.098 10.432 11.1109 10.4322 11.1238 10.4322H14.9002V11.9105H11.1238C9.57856 11.9105 8.29152 10.6456 8.29152 9.10032V5.47569H9.85676V9.10032C9.85676 9.17012 9.86216 9.23908 9.87264 9.30672L13.7673 5.4798C13.7344 5.47708 13.7012 5.47569 13.6675 5.47569H9.85676V4Z" fill="black"/>
<path d="M6.00752 11.6382L0.5 5.47504H2.71573L5.94924 9.09348V5.47504H7.6014V11.0298C7.6014 11.8682 6.56616 12.2634 6.00752 11.6382Z" fill="black"/>
</g>
</g>
<defs>
<clipPath id="clip0_2639_570">
<rect width="16" height="16" fill="white" transform="translate(0.5)"/>
</clipPath>
<clipPath id="clip1_2639_570">
<rect width="16" height="8" fill="white" transform="translate(0.5 4)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1015 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-blocks-icon lucide-blocks"><rect width="7" height="7" x="14" y="3" rx="1"/><path d="M10 21V8a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H3"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-blocks"><rect width="7" height="7" x="14" y="3" rx="1"/><path d="M10 21V8a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H3"/></svg>

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 368 B

View File

@@ -1,890 +0,0 @@
<svg width="400" height="92" viewBox="0 0 400 92" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_532_2318)">
<mask id="mask0_532_2318" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="400" height="92">
<path d="M400 0H0V92H400V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_532_2318)">
<g clip-path="url(#clip1_532_2318)">
<path d="M62.1878 1.24874C60.4044 1.12767 59.0444 -0.430533 59.1654 -2.21393L59.2755 -3.8352C59.3144 -4.40851 59.5795 -4.94289 60.0124 -5.32076C60.4453 -5.69863 61.0106 -5.88906 61.5839 -5.85013L63.7456 -5.70338C64.3189 -5.66446 64.8533 -5.39938 65.2312 -4.96647C65.6091 -4.53355 65.7995 -3.96825 65.7606 -3.39494L65.6505 -1.77367C65.5294 0.00972748 63.9712 1.36981 62.1878 1.24874Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M62.1879 1.24869L62.5181 -3.61511" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M57.2873 1.45893C57.3644 0.324045 58.3491 -0.586346 59.4877 -0.563343" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M65.108 -0.181763C66.2393 -0.0506748 67.0919 0.984456 67.0149 2.11934" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip2_532_2318)">
<path d="M87.1305 2.94204C85.3471 2.82097 83.987 1.26277 84.108 -0.52063L84.2181 -2.1419C84.257 -2.71521 84.5221 -3.24959 84.955 -3.62746C85.3879 -4.00534 85.9532 -4.19576 86.5266 -4.15684L88.6883 -4.01008C89.2616 -3.97116 89.7959 -3.70608 90.1738 -3.27317C90.5517 -2.84025 90.7421 -2.27495 90.7032 -1.70164L90.5931 -0.0803691C90.4721 1.70303 88.9139 3.06311 87.1305 2.94204Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M87.1305 2.94199L87.4607 -1.92181" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M82.23 3.15223C82.307 2.01734 83.2918 1.10695 84.4303 1.12996" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M92.7915 -0.474035L90.6298 -0.620789" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M90.0507 1.51154C91.1819 1.64262 92.0346 2.67775 91.9575 3.81264" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip3_532_2318)">
<path d="M112.073 4.63534C110.29 4.51426 108.929 2.95606 109.051 1.17267L109.161 -0.4486C109.2 -1.02192 109.465 -1.55629 109.898 -1.93416C110.33 -2.31204 110.896 -2.50246 111.469 -2.46354L113.631 -2.31678C114.204 -2.27786 114.738 -2.01279 115.116 -1.57987C115.494 -1.14695 115.685 -0.581655 115.646 -0.00833894L115.536 1.61293C115.415 3.39632 113.856 4.75641 112.073 4.63534Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M112.073 4.63529L112.403 -0.228516" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M109.087 0.632227L106.926 0.485474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M107.172 4.84553C107.25 3.71064 108.234 2.80025 109.373 2.82325" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M117.734 1.21926L115.572 1.07251" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M114.993 3.20483C116.124 3.33592 116.977 4.37105 116.9 5.50594" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip4_532_2318)">
<path d="M135.867 -0.736649L135.903 -1.27707C135.908 -1.49674 135.958 -1.71311 136.049 -1.91312C136.14 -2.11313 136.27 -2.29262 136.433 -2.44078C136.595 -2.58894 136.786 -2.70268 136.993 -2.77515C137.2 -2.84762 137.42 -2.8773 137.64 -2.86242C137.859 -2.84754 138.073 -2.78839 138.269 -2.68855C138.464 -2.58872 138.638 -2.45025 138.779 -2.28153C138.919 -2.1128 139.024 -1.9173 139.087 -1.70683C139.151 -1.49636 139.17 -1.27528 139.146 -1.05694L139.109 -0.516519" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M137.016 6.32869C135.232 6.20762 133.872 4.64942 133.993 2.86603L134.103 1.24476C134.142 0.671444 134.407 0.13707 134.84 -0.240804C135.273 -0.618678 135.838 -0.809099 136.412 -0.770178L138.573 -0.623424C139.147 -0.584503 139.681 -0.319426 140.059 0.113491C140.437 0.546408 140.627 1.1117 140.588 1.68502L140.478 3.30629C140.357 5.08968 138.799 6.44977 137.016 6.32869Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M137.016 6.32865L137.346 1.46484" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M134.463 0.183352C133.427 0.00445886 132.625 -0.972961 132.702 -2.10785" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M134.03 2.32559L131.868 2.17883" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M132.115 6.53889C132.192 5.404 133.177 4.49361 134.315 4.51661" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M142.413 -1.44856C142.336 -0.313668 141.409 0.546349 140.375 0.584726" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M142.677 2.91262L140.515 2.76587" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M139.936 4.89819C141.067 5.02928 141.92 6.06441 141.843 7.1993" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip5_532_2318)">
<path d="M160.457 -1.85242L161.404 -0.767448" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M163.695 -0.611874L164.78 -1.55889" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M160.809 0.956649L160.846 0.416226C160.851 0.196553 160.9 -0.0198148 160.991 -0.219821C161.082 -0.419827 161.213 -0.599325 161.375 -0.747482C161.538 -0.895638 161.728 -1.00938 161.936 -1.08185C162.143 -1.15432 162.363 -1.184 162.582 -1.16912C162.801 -1.15424 163.015 -1.09509 163.211 -0.995255C163.407 -0.895417 163.58 -0.756956 163.721 -0.588227C163.862 -0.419498 163.967 -0.224001 164.03 -0.0135316C164.093 0.196937 164.113 0.418014 164.088 0.636357L164.052 1.17678" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M161.958 8.02199C160.175 7.90092 158.815 6.34272 158.936 4.55933L159.046 2.93806C159.085 2.36474 159.35 1.83037 159.783 1.45249C160.216 1.07462 160.781 0.884199 161.354 0.923121L163.516 1.06987C164.089 1.1088 164.624 1.37387 165.002 1.80679C165.379 2.23971 165.57 2.805 165.531 3.37832L165.421 4.99959C165.3 6.78298 163.742 8.14306 161.958 8.02199Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M161.958 8.02195L162.288 3.15814" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M159.406 1.87665C158.37 1.69776 157.568 0.720337 157.645 -0.414551" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M158.972 4.01888L156.811 3.87213" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.058 8.23219C157.135 7.0973 158.12 6.18691 159.258 6.20991" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M167.356 0.244742C167.279 1.37963 166.352 2.23965 165.318 2.27802" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M167.619 4.60592L165.458 4.45917" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M164.878 6.59149C166.01 6.72258 166.862 7.75771 166.785 8.8926" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip6_532_2318)">
<path d="M185.399 -0.159119L186.346 0.92585" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M188.638 1.08142L189.723 0.134404" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M185.752 2.64995L185.788 2.10952C185.793 1.88985 185.843 1.67348 185.934 1.47348C186.025 1.27347 186.156 1.09397 186.318 0.945817C186.48 0.79766 186.671 0.683916 186.878 0.611449C187.086 0.538982 187.306 0.509294 187.525 0.524177C187.744 0.53906 187.958 0.598205 188.154 0.698043C188.349 0.797881 188.523 0.936343 188.664 1.10507C188.804 1.2738 188.91 1.4693 188.973 1.67977C189.036 1.89024 189.056 2.11131 189.031 2.32966L188.994 2.87008" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M186.901 9.71529C185.117 9.59422 183.757 8.03602 183.878 6.25262L183.988 4.63136C184.027 4.05804 184.292 3.52367 184.725 3.14579C185.158 2.76792 185.724 2.5775 186.297 2.61642L188.459 2.76317C189.032 2.80209 189.566 3.06717 189.944 3.50009C190.322 3.933 190.512 4.4983 190.474 5.07162L190.363 6.69289C190.242 8.47628 188.684 9.83636 186.901 9.71529Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M186.901 9.71525L187.231 4.85144" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M184.348 3.56995C183.313 3.39106 182.51 2.41364 182.587 1.27875" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M183.915 5.71218L181.753 5.56543" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182 9.92549C182.077 8.7906 183.062 7.88021 184.201 7.90321" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M192.299 1.93804C192.222 3.07293 191.295 3.93295 190.26 3.97132" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M192.562 6.29922L190.4 6.15247" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M189.821 8.28479C190.952 8.41588 191.805 9.45101 191.728 10.5859" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip7_532_2318)">
<path d="M210.342 1.53418L211.289 2.61915" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M213.58 2.77472L214.665 1.8277" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M210.694 4.34325L210.731 3.80282C210.736 3.58315 210.786 3.36678 210.877 3.16678C210.968 2.96677 211.098 2.78727 211.26 2.63911C211.423 2.49096 211.613 2.37721 211.821 2.30475C212.028 2.23228 212.248 2.20259 212.467 2.21748C212.687 2.23236 212.9 2.2915 213.096 2.39134C213.292 2.49118 213.465 2.62964 213.606 2.79837C213.747 2.9671 213.852 3.1626 213.915 3.37307C213.978 3.58353 213.998 3.80461 213.973 4.02295L213.937 4.56338" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M211.843 11.4086C210.06 11.2875 208.7 9.72932 208.821 7.94592L208.931 6.32465C208.97 5.75134 209.235 5.21697 209.668 4.83909C210.101 4.46122 210.666 4.2708 211.239 4.30972L213.401 4.45647C213.974 4.49539 214.509 4.76047 214.887 5.19339C215.265 5.6263 215.455 6.1916 215.416 6.76492L215.306 8.38618C215.185 10.1696 213.627 11.5297 211.843 11.4086Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M211.843 11.4085L212.174 6.54474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M209.291 5.26325C208.255 5.08435 207.453 4.10693 207.53 2.97205" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M208.858 7.40548L206.696 7.25873" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M206.943 11.6188C207.02 10.4839 208.005 9.5735 209.143 9.59651" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M217.241 3.63134C217.164 4.76623 216.237 5.62624 215.203 5.66462" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M217.504 7.99252L215.343 7.84576" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M214.764 9.97809C215.895 10.1092 216.748 11.1443 216.67 12.2792" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip8_532_2318)">
<path d="M235.285 3.22748L236.232 4.31245" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M238.523 4.46802L239.608 3.521" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M235.637 6.03654L235.674 5.49612C235.679 5.27645 235.728 5.06008 235.819 4.86007C235.91 4.66007 236.041 4.48057 236.203 4.33241C236.365 4.18426 236.556 4.07051 236.763 3.99805C236.971 3.92558 237.191 3.89589 237.41 3.91077C237.629 3.92566 237.843 3.9848 238.039 4.08464C238.235 4.18448 238.408 4.32294 238.549 4.49167C238.69 4.6604 238.795 4.85589 238.858 5.06636C238.921 5.27683 238.941 5.49791 238.916 5.71625L238.879 6.25667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M236.786 13.1019C235.003 12.9808 233.642 11.4226 233.764 9.63922L233.874 8.01795C233.913 7.44464 234.178 6.91026 234.611 6.53239C235.043 6.15452 235.609 5.96409 236.182 6.00302L238.344 6.14977C238.917 6.18869 239.451 6.45377 239.829 6.88668C240.207 7.3196 240.398 7.8849 240.359 8.45821L240.249 10.0795C240.128 11.8629 238.569 13.223 236.786 13.1019Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M236.786 13.1018L237.116 8.23804" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M234.233 6.95655C233.198 6.77765 232.395 5.80023 232.472 4.66534" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M233.8 9.09878L231.639 8.95203" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M231.885 13.3121C231.963 12.1772 232.947 11.2668 234.086 11.2898" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M242.184 5.32464C242.107 6.45953 241.18 7.31954 240.146 7.35792" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M242.447 9.68582L240.285 9.53906" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M239.706 11.6714C240.837 11.8025 241.69 12.8376 241.613 13.9725" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip9_532_2318)">
<path d="M260.227 4.92084L261.174 6.00581" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M263.466 6.16138L264.551 5.21436" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M260.579 7.7299L260.616 7.18948C260.621 6.96981 260.671 6.75344 260.762 6.55343C260.853 6.35343 260.983 6.17393 261.146 6.02577C261.308 5.87762 261.498 5.76387 261.706 5.6914C261.913 5.61894 262.133 5.58925 262.353 5.60413C262.572 5.61902 262.786 5.67816 262.981 5.778C263.177 5.87784 263.351 6.0163 263.491 6.18503C263.632 6.35376 263.737 6.54925 263.8 6.75972C263.864 6.97019 263.883 7.19127 263.859 7.40961L263.822 7.95003" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M261.729 14.7952C259.945 14.6742 258.585 13.116 258.706 11.3326L258.816 9.71131C258.855 9.138 259.12 8.60362 259.553 8.22575C259.986 7.84788 260.551 7.65745 261.125 7.69638L263.286 7.84313C263.86 7.88205 264.394 8.14713 264.772 8.58004C265.15 9.01296 265.34 9.57826 265.301 10.1516L265.191 11.7728C265.07 13.5562 263.512 14.9163 261.729 14.7952Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M261.729 14.7952L262.059 9.9314" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M259.176 8.6499C258.14 8.47101 257.338 7.49359 257.415 6.3587" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M258.743 10.7921L256.581 10.6454" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M256.828 15.0054C256.905 13.8706 257.89 12.9602 259.028 12.9832" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M267.126 7.018C267.049 8.15288 266.122 9.0129 265.088 9.05128" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M267.39 11.3792L265.228 11.2324" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M264.649 13.3647C265.78 13.4958 266.633 14.531 266.556 15.6659" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip10_532_2318)">
<path d="M285.17 6.61414L286.117 7.6991" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M288.408 7.85468L289.493 6.90766" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M285.522 9.4232L285.559 8.88278C285.564 8.66311 285.613 8.44674 285.704 8.24673C285.795 8.04673 285.926 7.86723 286.088 7.71907C286.25 7.57091 286.441 7.45717 286.648 7.3847C286.856 7.31224 287.076 7.28255 287.295 7.29743C287.514 7.31231 287.728 7.37146 287.924 7.4713C288.12 7.57114 288.293 7.7096 288.434 7.87833C288.575 8.04705 288.68 8.24255 288.743 8.45302C288.806 8.66349 288.826 8.88457 288.801 9.10291L288.765 9.64333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M286.671 16.4885C284.888 16.3675 283.528 14.8093 283.649 13.0259L283.759 11.4046C283.798 10.8313 284.063 10.2969 284.496 9.91905C284.929 9.54117 285.494 9.35075 286.067 9.38967L288.229 9.53643C288.802 9.57535 289.337 9.84042 289.714 10.2733C290.092 10.7063 290.283 11.2716 290.244 11.8449L290.134 13.4661C290.013 15.2495 288.455 16.6096 286.671 16.4885Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M286.671 16.4885L287.001 11.6247" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M284.119 10.3432C283.083 10.1643 282.281 9.18689 282.358 8.052" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M283.685 12.4854L281.524 12.3387" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M281.771 16.6987C281.848 15.5639 282.832 14.6535 283.971 14.6765" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M292.069 8.7113C291.992 9.84618 291.065 10.7062 290.031 10.7446" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M292.332 13.0725L290.17 12.9257" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M289.591 15.058C290.723 15.1891 291.575 16.2243 291.498 17.3592" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip11_532_2318)">
<path d="M310.112 8.30743L311.059 9.3924" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M313.351 9.54798L314.436 8.60096" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M310.465 11.1165L310.501 10.5761C310.506 10.3564 310.556 10.14 310.647 9.94003C310.738 9.74002 310.868 9.56053 311.031 9.41237C311.193 9.26421 311.384 9.15047 311.591 9.078C311.799 9.00553 312.018 8.97585 312.238 8.99073C312.457 9.00561 312.671 9.06476 312.867 9.1646C313.062 9.26443 313.236 9.4029 313.377 9.57162C313.517 9.74035 313.622 9.93585 313.686 10.1463C313.749 10.3568 313.769 10.5779 313.744 10.7962L313.707 11.3366" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M311.614 18.1818C309.83 18.0608 308.47 16.5026 308.591 14.7192L308.701 13.0979C308.74 12.5246 309.005 11.9902 309.438 11.6123C309.871 11.2345 310.437 11.0441 311.01 11.083L313.172 11.2297C313.745 11.2686 314.279 11.5337 314.657 11.9666C315.035 12.3996 315.225 12.9649 315.186 13.5382L315.076 15.1594C314.955 16.9428 313.397 18.3029 311.614 18.1818Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M311.614 18.1818L311.944 13.318" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M309.061 12.0365C308.025 11.8576 307.223 10.8802 307.3 9.7453" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M308.628 14.1787L306.466 14.032" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M306.713 18.392C306.79 17.2572 307.775 16.3468 308.914 16.3698" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M317.012 10.4046C316.935 11.5395 316.008 12.3995 314.973 12.4379" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M317.275 14.7658L315.113 14.619" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M314.534 16.7513C315.665 16.8824 316.518 17.9176 316.441 19.0524" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip12_532_2318)">
<path d="M335.055 10.0007L336.002 11.0857" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M338.293 11.2413L339.378 10.2943" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M335.407 12.8098L335.444 12.2694C335.449 12.0497 335.498 11.8333 335.589 11.6333C335.68 11.4333 335.811 11.2538 335.973 11.1057C336.136 10.9575 336.326 10.8438 336.534 10.7713C336.741 10.6988 336.961 10.6691 337.18 10.684C337.399 10.6989 337.613 10.7581 337.809 10.8579C338.005 10.9577 338.178 11.0962 338.319 11.2649C338.46 11.4337 338.565 11.6291 338.628 11.8396C338.691 12.0501 338.711 12.2712 338.686 12.4895L338.65 13.0299" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M336.556 19.8751C334.773 19.7541 333.413 18.1959 333.534 16.4125L333.644 14.7912C333.683 14.2179 333.948 13.6835 334.381 13.3056C334.814 12.9278 335.379 12.7373 335.952 12.7763L338.114 12.923C338.687 12.9619 339.222 13.227 339.6 13.6599C339.978 14.0929 340.168 14.6582 340.129 15.2315L340.019 16.8527C339.898 18.6361 338.34 19.9962 336.556 19.8751Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M336.556 19.8751L336.886 15.0113" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M334.004 13.7298C332.968 13.5509 332.166 12.5735 332.243 11.4386" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M333.571 15.872L331.409 15.7253" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M331.656 20.0853C331.733 18.9504 332.718 18.0401 333.856 18.0631" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M341.954 12.0979C341.877 13.2328 340.95 14.0928 339.916 14.1312" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M342.217 16.4591L340.056 16.3123" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M339.477 18.4446C340.608 18.5757 341.46 19.6109 341.383 20.7457" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip13_532_2318)">
<path d="M359.998 11.694L360.945 12.779" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M363.236 12.9346L364.321 11.9876" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M360.35 14.5031L360.386 13.9627C360.392 13.743 360.441 13.5266 360.532 13.3266C360.623 13.1266 360.754 12.9471 360.916 12.799C361.078 12.6508 361.269 12.5371 361.476 12.4646C361.684 12.3921 361.904 12.3624 362.123 12.3773C362.342 12.3922 362.556 12.4514 362.752 12.5512C362.947 12.651 363.121 12.7895 363.262 12.9582C363.402 13.1269 363.508 13.3224 363.571 13.5329C363.634 13.7434 363.654 13.9645 363.629 14.1828L363.592 14.7232" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M361.499 21.5684C359.715 21.4474 358.355 19.8892 358.476 18.1058L358.587 16.4845C358.625 15.9112 358.891 15.3768 359.323 14.9989C359.756 14.6211 360.322 14.4306 360.895 14.4696L363.057 14.6163C363.63 14.6552 364.164 14.9203 364.542 15.3532C364.92 15.7862 365.111 16.3515 365.072 16.9248L364.962 18.546C364.84 20.3294 363.282 21.6895 361.499 21.5684Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M361.499 21.5684L361.829 16.7046" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M358.946 15.4231C357.911 15.2442 357.108 14.2668 357.185 13.1319" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M358.513 17.5653L356.351 17.4186" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M356.598 21.7786C356.675 20.6437 357.66 19.7334 358.799 19.7564" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M366.897 13.7912C366.82 14.9261 365.893 15.7861 364.859 15.8245" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M367.16 18.1524L364.998 18.0056" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M364.419 20.1379C365.55 20.269 366.403 21.3042 366.326 22.439" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip14_532_2318)">
<path d="M384.94 13.3874L385.887 14.4724" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M388.179 14.6279L389.264 13.6809" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M385.292 16.1965L385.329 15.656C385.334 15.4364 385.384 15.22 385.475 15.02C385.566 14.82 385.696 14.6405 385.859 14.4923C386.021 14.3442 386.211 14.2304 386.419 14.158C386.626 14.0855 386.846 14.0558 387.065 14.0707C387.285 14.0856 387.499 14.1447 387.694 14.2446C387.89 14.3444 388.064 14.4829 388.204 14.6516C388.345 14.8203 388.45 15.0158 388.513 15.2263C388.576 15.4367 388.596 15.6578 388.572 15.8762L388.535 16.4166" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M386.441 23.2618C384.658 23.1407 383.298 21.5825 383.419 19.7991L383.529 18.1779C383.568 17.6045 383.833 17.0702 384.266 16.6923C384.699 16.3144 385.264 16.124 385.838 16.1629L387.999 16.3097C388.573 16.3486 389.107 16.6137 389.485 17.0466C389.863 17.4795 390.053 18.0448 390.014 18.6181L389.904 20.2394C389.783 22.0228 388.225 23.3829 386.441 23.2618Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M386.441 23.2618L386.772 18.3979" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.889 17.1165C382.853 16.9376 382.051 15.9601 382.128 14.8253" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.456 19.2587L381.294 19.1119" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M381.541 23.472C381.618 22.3371 382.603 21.4267 383.741 21.4497" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M391.839 15.4845C391.762 16.6194 390.835 17.4795 389.801 17.5178" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M392.103 19.8457L389.941 19.699" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M389.362 21.8313C390.493 21.9624 391.346 22.9975 391.269 24.1324" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip15_532_2318)">
<path d="M59.2642 12.3261L60.2112 13.4111" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M62.5026 13.5667L63.5875 12.6196" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M59.6164 15.1352L59.6531 14.5948C59.6582 14.3751 59.7077 14.1587 59.7987 13.9587C59.8897 13.7587 60.0203 13.5792 60.1825 13.431C60.3448 13.2829 60.5354 13.1691 60.7429 13.0967C60.9503 13.0242 61.1703 12.9945 61.3895 13.0094C61.6087 13.0243 61.8227 13.0834 62.0184 13.1833C62.2141 13.2831 62.3876 13.4216 62.5284 13.5903C62.6691 13.759 62.7742 13.9545 62.8374 14.165C62.9005 14.3755 62.9203 14.5965 62.8957 14.8149L62.859 15.3553" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M60.7655 22.2005C58.9821 22.0794 57.622 20.5212 57.7431 18.7379L57.8531 17.1166C57.892 16.5433 58.1571 16.0089 58.59 15.631C59.023 15.2531 59.5883 15.0627 60.1616 15.1016L62.3233 15.2484C62.8966 15.2873 63.4309 15.5524 63.8088 15.9853C64.1867 16.4182 64.3771 16.9835 64.3382 17.5568L64.2281 19.1781C64.1071 20.9615 62.5489 22.3216 60.7655 22.2005Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M60.7655 22.2005L61.0957 17.3367" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M58.213 16.0552C57.1773 15.8763 56.375 14.8989 56.452 13.764" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M57.7797 18.1974L55.618 18.0507" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M55.865 22.4107C55.942 21.2758 56.9268 20.3654 58.0653 20.3884" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M66.1633 14.4233C66.0863 15.5582 65.1592 16.4182 64.1251 16.4566" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M66.4265 18.7844L64.2648 18.6377" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M63.6857 20.77C64.8169 20.9011 65.6696 21.9362 65.5925 23.0711" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip16_532_2318)">
<path d="M84.2068 14.0194L85.1538 15.1044" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M87.4452 15.26L88.5302 14.3129" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M84.5591 16.8285L84.5958 16.2881C84.6008 16.0684 84.6503 15.852 84.7413 15.652C84.8323 15.452 84.9629 15.2725 85.1252 15.1243C85.2874 14.9762 85.478 14.8624 85.6855 14.79C85.8929 14.7175 86.1129 14.6878 86.3321 14.7027C86.5513 14.7176 86.7653 14.7767 86.961 14.8766C87.1568 14.9764 87.3302 15.1149 87.471 15.2836C87.6118 15.4523 87.7169 15.6478 87.78 15.8583C87.8431 16.0688 87.863 16.2898 87.8383 16.5082L87.8016 17.0486" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M85.7081 23.8938C83.9247 23.7727 82.5646 22.2145 82.6857 20.4312L82.7958 18.8099C82.8347 18.2366 83.0997 17.7022 83.5327 17.3243C83.9656 16.9464 84.5309 16.756 85.1042 16.7949L87.2659 16.9417C87.8392 16.9806 88.3736 17.2457 88.7515 17.6786C89.1293 18.1115 89.3197 18.6768 89.2808 19.2501L89.1708 20.8714C89.0497 22.6548 87.4915 24.0149 85.7081 23.8938Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M85.7081 23.8938L86.0383 19.03" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M83.1556 17.7485C82.1199 17.5696 81.3176 16.5922 81.3947 15.4573" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M82.7224 19.8907L80.5607 19.744" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M80.8076 24.104C80.8846 22.9691 81.8694 22.0587 83.008 22.0817" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M91.106 16.1166C91.0289 17.2515 90.1019 18.1115 89.0677 18.1499" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M91.3691 20.4777L89.2074 20.331" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M88.6283 22.4633C89.7595 22.5944 90.6122 23.6295 90.5351 24.7644" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip17_532_2318)">
<path d="M109.149 15.7127L110.096 16.7977" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M112.388 16.9533L113.473 16.0062" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M109.502 18.5218L109.538 17.9814C109.543 17.7617 109.593 17.5453 109.684 17.3453C109.775 17.1453 109.905 16.9658 110.068 16.8176C110.23 16.6695 110.421 16.5557 110.628 16.4833C110.835 16.4108 111.055 16.3811 111.275 16.396C111.494 16.4109 111.708 16.47 111.904 16.5699C112.099 16.6697 112.273 16.8082 112.414 16.9769C112.554 17.1456 112.659 17.3411 112.723 17.5516C112.786 17.7621 112.805 17.9831 112.781 18.2015L112.744 18.7419" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M110.651 25.5871C108.867 25.466 107.507 23.9078 107.628 22.1245L107.738 20.5032C107.777 19.9299 108.042 19.3955 108.475 19.0176C108.908 18.6397 109.473 18.4493 110.047 18.4882L112.208 18.635C112.782 18.6739 113.316 18.939 113.694 19.3719C114.072 19.8048 114.262 20.3701 114.223 20.9434L114.113 22.5647C113.992 24.3481 112.434 25.7082 110.651 25.5871Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M110.651 25.5871L110.981 20.7233" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M108.098 19.4418C107.062 19.2629 106.26 18.2855 106.337 17.1506" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M107.665 21.584L105.503 21.4373" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M105.75 25.7973C105.827 24.6624 106.812 23.752 107.95 23.775" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M116.048 17.8099C115.971 18.9448 115.044 19.8048 114.01 19.8431" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M116.312 22.171L114.15 22.0243" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M113.571 24.1566C114.702 24.2877 115.555 25.3228 115.478 26.4577" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip18_532_2318)">
<path d="M134.092 17.4061L135.039 18.491" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M137.33 18.6466L138.415 17.6996" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M134.444 20.2151L134.481 19.6747C134.486 19.455 134.535 19.2387 134.626 19.0387C134.717 18.8387 134.848 18.6592 135.01 18.511C135.173 18.3628 135.363 18.2491 135.571 18.1766C135.778 18.1042 135.998 18.0745 136.217 18.0894C136.436 18.1042 136.65 18.1634 136.846 18.2632C137.042 18.3631 137.215 18.5015 137.356 18.6703C137.497 18.839 137.602 19.0345 137.665 19.245C137.728 19.4554 137.748 19.6765 137.723 19.8948L137.687 20.4353" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M135.593 27.2805C133.81 27.1594 132.45 25.6012 132.571 23.8178L132.681 22.1965C132.72 21.6232 132.985 21.0889 133.418 20.711C133.851 20.3331 134.416 20.1427 134.989 20.1816L137.151 20.3284C137.724 20.3673 138.259 20.6324 138.637 21.0653C139.014 21.4982 139.205 22.0635 139.166 22.6368L139.056 24.2581C138.935 26.0415 137.377 27.4015 135.593 27.2805Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M135.593 27.2804L135.923 22.4166" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M133.041 21.1351C132.005 20.9562 131.203 19.9788 131.28 18.8439" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M132.607 23.2774L130.446 23.1306" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M130.693 27.4907C130.77 26.3558 131.755 25.4454 132.893 25.4684" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M140.991 19.5032C140.914 20.6381 139.987 21.4981 138.953 21.5365" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M141.254 23.8644L139.093 23.7177" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M138.513 25.85C139.645 25.9811 140.497 27.0162 140.42 28.1511" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip19_532_2318)">
<path d="M159.035 19.0994L159.982 20.1843" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M162.273 20.3399L163.358 19.3929" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M159.387 21.9084L159.424 21.368C159.429 21.1483 159.478 20.932 159.569 20.732C159.66 20.532 159.791 20.3525 159.953 20.2043C160.115 20.0561 160.306 19.9424 160.513 19.8699C160.721 19.7975 160.941 19.7678 161.16 19.7827C161.379 19.7975 161.593 19.8567 161.789 19.9565C161.985 20.0564 162.158 20.1948 162.299 20.3636C162.44 20.5323 162.545 20.7278 162.608 20.9383C162.671 21.1487 162.691 21.3698 162.666 21.5881L162.629 22.1286" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M160.536 28.9738C158.752 28.8527 157.392 27.2945 157.513 25.5111L157.624 23.8898C157.662 23.3165 157.928 22.7822 158.36 22.4043C158.793 22.0264 159.359 21.836 159.932 21.8749L162.094 22.0217C162.667 22.0606 163.201 22.3257 163.579 22.7586C163.957 23.1915 164.148 23.7568 164.109 24.3301L163.999 25.9514C163.877 27.7348 162.319 29.0948 160.536 28.9738Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M160.536 28.9737L160.866 24.1099" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.983 22.8284C156.948 22.6495 156.145 21.6721 156.222 20.5372" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.55 24.9707L155.388 24.8239" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M155.635 29.184C155.712 28.0491 156.697 27.1387 157.836 27.1617" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M165.934 21.1965C165.857 22.3314 164.93 23.1914 163.896 23.2298" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M166.197 25.5577L164.035 25.4109" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M163.456 27.5433C164.587 27.6744 165.44 28.7095 165.363 29.8444" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip20_532_2318)">
<path d="M183.977 20.7927L184.924 21.8776" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M187.216 22.0332L188.3 21.0862" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M184.329 23.6017L184.366 23.0613C184.371 22.8416 184.421 22.6253 184.512 22.4253C184.603 22.2253 184.733 22.0458 184.895 21.8976C185.058 21.7494 185.248 21.6357 185.456 21.5632C185.663 21.4908 185.883 21.4611 186.102 21.476C186.322 21.4908 186.536 21.55 186.731 21.6498C186.927 21.7497 187.101 21.8881 187.241 22.0569C187.382 22.2256 187.487 22.4211 187.55 22.6315C187.613 22.842 187.633 23.0631 187.609 23.2814L187.572 23.8219" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M185.478 30.6671C183.695 30.546 182.335 28.9878 182.456 27.2044L182.566 25.5831C182.605 25.0098 182.87 24.4754 183.303 24.0976C183.736 23.7197 184.301 23.5293 184.875 23.5682L187.036 23.715C187.61 23.7539 188.144 24.019 188.522 24.4519C188.9 24.8848 189.09 25.4501 189.051 26.0234L188.941 27.6447C188.82 29.4281 187.262 30.7881 185.478 30.6671Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M185.478 30.667L185.809 25.8032" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182.926 24.5217C181.89 24.3428 181.088 23.3654 181.165 22.2305" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182.493 26.664L180.331 26.5172" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M180.578 30.8773C180.655 29.7424 181.64 28.832 182.778 28.855" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M190.876 22.8898C190.799 24.0247 189.872 24.8847 188.838 24.9231" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M191.139 27.251L188.978 27.1042" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M188.399 29.2366C189.53 29.3677 190.383 30.4028 190.306 31.5377" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip21_532_2318)">
<path d="M208.92 22.486L209.867 23.5709" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M212.158 23.7265L213.243 22.7795" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M209.272 25.295L209.309 24.7546C209.314 24.5349 209.363 24.3186 209.454 24.1186C209.545 23.9186 209.676 23.7391 209.838 23.5909C210 23.4427 210.191 23.329 210.398 23.2565C210.606 23.1841 210.826 23.1544 211.045 23.1693C211.264 23.1841 211.478 23.2433 211.674 23.3431C211.87 23.443 212.043 23.5814 212.184 23.7502C212.325 23.9189 212.43 24.1144 212.493 24.3248C212.556 24.5353 212.576 24.7564 212.551 24.9747L212.514 25.5152" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M210.421 32.3604C208.638 32.2393 207.278 30.6811 207.399 28.8977L207.509 27.2764C207.548 26.7031 207.813 26.1687 208.246 25.7909C208.678 25.413 209.244 25.2226 209.817 25.2615L211.979 25.4083C212.552 25.4472 213.086 25.7123 213.464 26.1452C213.842 26.5781 214.033 27.1434 213.994 27.7167L213.884 29.338C213.763 31.1214 212.204 32.4814 210.421 32.3604Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M210.421 32.3603L210.751 27.4965" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.868 26.215C206.833 26.0361 206.03 25.0587 206.107 23.9238" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.435 28.3573L205.274 28.2105" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M205.52 32.5706C205.598 31.4357 206.582 30.5253 207.721 30.5483" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M215.819 24.5831C215.742 25.718 214.815 26.578 213.781 26.6164" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M216.082 28.9443L213.92 28.7975" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M213.341 30.9299C214.472 31.061 215.325 32.0961 215.248 33.231" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip22_532_2318)">
<path d="M233.862 24.1793L234.809 25.2642" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M237.101 25.4198L238.186 24.4728" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M234.215 26.9883L234.251 26.4479C234.256 26.2282 234.306 26.0119 234.397 25.8119C234.488 25.6118 234.618 25.4324 234.781 25.2842C234.943 25.136 235.134 25.0223 235.341 24.9498C235.548 24.8774 235.768 24.8477 235.988 24.8626C236.207 24.8774 236.421 24.9366 236.616 25.0364C236.812 25.1363 236.986 25.2747 237.126 25.4435C237.267 25.6122 237.372 25.8077 237.435 26.0181C237.499 26.2286 237.518 26.4497 237.494 26.668L237.457 27.2085" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M235.364 34.0537C233.58 33.9326 232.22 32.3744 232.341 30.591L232.451 28.9697C232.49 28.3964 232.755 27.862 233.188 27.4842C233.621 27.1063 234.186 26.9159 234.76 26.9548L236.921 27.1016C237.495 27.1405 238.029 27.4055 238.407 27.8385C238.785 28.2714 238.975 28.8367 238.936 29.41L238.826 31.0313C238.705 32.8147 237.147 34.1747 235.364 34.0537Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M235.364 34.0536L235.694 29.1898" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M232.811 27.9083C231.775 27.7294 230.973 26.752 231.05 25.6171" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M232.378 30.0506L230.216 29.9038" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M230.463 34.2639C230.54 33.129 231.525 32.2186 232.663 32.2416" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M240.762 26.2764C240.684 27.4113 239.757 28.2713 238.723 28.3097" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M241.025 30.6376L238.863 30.4908" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M238.284 32.6232C239.415 32.7543 240.268 33.7894 240.191 34.9243" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip23_532_2318)">
<path d="M258.805 25.8726L259.752 26.9576" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M262.043 27.1132L263.128 26.1661" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M259.157 28.6817L259.194 28.1413C259.199 27.9216 259.248 27.7052 259.339 27.5052C259.43 27.3052 259.561 27.1257 259.723 26.9776C259.885 26.8294 260.076 26.7157 260.284 26.6432C260.491 26.5707 260.711 26.541 260.93 26.5559C261.149 26.5708 261.363 26.6299 261.559 26.7298C261.755 26.8296 261.928 26.9681 262.069 27.1368C262.21 27.3055 262.315 27.501 262.378 27.7115C262.441 27.922 262.461 28.1431 262.436 28.3614L262.4 28.9018" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M260.306 35.747C258.523 35.626 257.163 34.0678 257.284 32.2844L257.394 30.6631C257.433 30.0898 257.698 29.5554 258.131 29.1775C258.564 28.7997 259.129 28.6092 259.702 28.6482L261.864 28.7949C262.437 28.8338 262.972 29.0989 263.35 29.5318C263.727 29.9647 263.918 30.53 263.879 31.1034L263.769 32.7246C263.648 34.508 262.09 35.8681 260.306 35.747Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M260.306 35.747L260.636 30.8832" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M257.754 29.6017C256.718 29.4228 255.916 28.4454 255.993 27.3105" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M257.32 31.7439L255.159 31.5972" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M255.406 35.9572C255.483 34.8223 256.467 33.9119 257.606 33.9349" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M265.704 27.9698C265.627 29.1047 264.7 29.9647 263.666 30.0031" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M265.967 32.331L263.806 32.1842" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M263.226 34.3165C264.358 34.4476 265.21 35.4827 265.133 36.6176" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip24_532_2318)">
<path d="M283.747 27.5659L284.694 28.6509" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M286.986 28.8065L288.071 27.8594" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M284.1 30.375L284.136 29.8346C284.141 29.6149 284.191 29.3985 284.282 29.1985C284.373 28.9985 284.503 28.819 284.666 28.6709C284.828 28.5227 285.019 28.409 285.226 28.3365C285.434 28.264 285.653 28.2343 285.873 28.2492C286.092 28.2641 286.306 28.3232 286.502 28.4231C286.697 28.5229 286.871 28.6614 287.012 28.8301C287.152 28.9988 287.257 29.1943 287.321 29.4048C287.384 29.6153 287.404 29.8363 287.379 30.0547L287.342 30.5951" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M285.249 37.4403C283.465 37.3193 282.105 35.7611 282.226 33.9777L282.336 32.3564C282.375 31.7831 282.64 31.2487 283.073 30.8708C283.506 30.493 284.072 30.3025 284.645 30.3415L286.807 30.4882C287.38 30.5271 287.914 30.7922 288.292 31.2251C288.67 31.658 288.86 32.2233 288.821 32.7967L288.711 34.4179C288.59 36.2013 287.032 37.5614 285.249 37.4403Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M285.249 37.4403L285.579 32.5765" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M282.696 31.295C281.661 31.1161 280.858 30.1387 280.935 29.0038" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M282.263 33.4372L280.101 33.2905" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M280.348 37.6505C280.425 36.5156 281.41 35.6052 282.549 35.6282" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M290.647 29.6631C290.57 30.798 289.643 31.658 288.608 31.6964" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M290.91 34.0243L288.748 33.8775" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M288.169 36.0098C289.3 36.1409 290.153 37.176 290.076 38.3109" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip25_532_2318)">
<path d="M308.69 29.2592L309.637 30.3442" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M311.928 30.4998L313.013 29.5527" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M309.042 32.0683L309.079 31.5279C309.084 31.3082 309.134 31.0918 309.225 30.8918C309.316 30.6918 309.446 30.5123 309.608 30.3642C309.771 30.216 309.961 30.1023 310.169 30.0298C310.376 29.9573 310.596 29.9276 310.815 29.9425C311.035 29.9574 311.248 30.0165 311.444 30.1164C311.64 30.2162 311.813 30.3547 311.954 30.5234C312.095 30.6921 312.2 30.8876 312.263 31.0981C312.326 31.3086 312.346 31.5296 312.322 31.748L312.285 32.2884" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M310.191 39.1336C308.408 39.0126 307.048 37.4544 307.169 35.671L307.279 34.0497C307.318 33.4764 307.583 32.942 308.016 32.5641C308.449 32.1863 309.014 31.9958 309.587 32.0348L311.749 32.1815C312.322 32.2204 312.857 32.4855 313.235 32.9184C313.613 33.3513 313.803 33.9166 313.764 34.49L313.654 36.1112C313.533 37.8946 311.975 39.2547 310.191 39.1336Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M310.191 39.1336L310.522 34.2698" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M307.639 32.9883C306.603 32.8094 305.801 31.832 305.878 30.6971" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M307.206 35.1305L305.044 34.9838" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M305.291 39.3438C305.368 38.2089 306.353 37.2985 307.491 37.3215" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M315.589 31.3564C315.512 32.4913 314.585 33.3513 313.551 33.3897" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M315.852 35.7176L313.691 35.5708" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M313.112 37.7031C314.243 37.8342 315.096 38.8693 315.018 40.0042" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip26_532_2318)">
<path d="M333.633 30.9525L334.58 32.0375" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M336.871 32.1931L337.956 31.246" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M333.985 33.7616L334.022 33.2212C334.027 33.0015 334.076 32.7851 334.167 32.5851C334.258 32.3851 334.389 32.2056 334.551 32.0574C334.713 31.9093 334.904 31.7955 335.111 31.7231C335.319 31.6506 335.539 31.6209 335.758 31.6358C335.977 31.6507 336.191 31.7098 336.387 31.8097C336.583 31.9095 336.756 32.048 336.897 32.2167C337.038 32.3854 337.143 32.5809 337.206 32.7914C337.269 33.0019 337.289 33.2229 337.264 33.4413L337.227 33.9817" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M335.134 40.8269C333.351 40.7059 331.99 39.1477 332.112 37.3643L332.222 35.743C332.261 35.1697 332.526 34.6353 332.959 34.2574C333.391 33.8796 333.957 33.6891 334.53 33.7281L336.692 33.8748C337.265 33.9137 337.799 34.1788 338.177 34.6117C338.555 35.0446 338.746 35.6099 338.707 36.1833L338.597 37.8045C338.476 39.5879 336.917 40.948 335.134 40.8269Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M335.134 40.8269L335.464 35.9631" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M332.581 34.6816C331.546 34.5027 330.743 33.5253 330.82 32.3904" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M332.148 36.8238L329.987 36.6771" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M330.233 41.0371C330.31 39.9022 331.295 38.9918 332.434 39.0148" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M340.532 33.0497C340.455 34.1846 339.528 35.0446 338.494 35.083" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M340.795 37.4109L338.633 37.2641" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M338.054 39.3964C339.185 39.5275 340.038 40.5626 339.961 41.6975" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip27_532_2318)">
<path d="M358.575 32.6458L359.522 33.7308" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M361.814 33.8864L362.899 32.9393" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M358.927 35.4549L358.964 34.9145C358.969 34.6948 359.019 34.4784 359.11 34.2784C359.201 34.0784 359.331 33.8989 359.494 33.7507C359.656 33.6026 359.846 33.4888 360.054 33.4164C360.261 33.3439 360.481 33.3142 360.7 33.3291C360.92 33.344 361.134 33.4031 361.329 33.503C361.525 33.6028 361.699 33.7413 361.839 33.91C361.98 34.0787 362.085 34.2742 362.148 34.4847C362.211 34.6952 362.231 34.9162 362.207 35.1346L362.17 35.675" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M360.077 42.5202C358.293 42.3992 356.933 40.841 357.054 39.0576L357.164 37.4363C357.203 36.863 357.468 36.3286 357.901 35.9507C358.334 35.5729 358.899 35.3824 359.473 35.4214L361.634 35.5681C362.208 35.607 362.742 35.8721 363.12 36.305C363.498 36.7379 363.688 37.3032 363.649 37.8765L363.539 39.4978C363.418 41.2812 361.86 42.6413 360.077 42.5202Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M360.076 42.5202L360.407 37.6564" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M357.524 36.3749C356.488 36.196 355.686 35.2186 355.763 34.0837" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M357.091 38.5171L354.929 38.3704" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M355.176 42.7304C355.253 41.5955 356.238 40.6851 357.376 40.7081" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M365.474 34.743C365.397 35.8779 364.47 36.7379 363.436 36.7763" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M365.738 39.1042L363.576 38.9574" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M362.997 41.0897C364.128 41.2208 364.981 42.2559 364.904 43.3908" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip28_532_2318)">
<path d="M383.518 34.3392L384.465 35.4241" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M386.756 35.5797L387.841 34.6327" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.87 37.1482L383.907 36.6078C383.912 36.3881 383.961 36.1718 384.052 35.9718C384.143 35.7718 384.274 35.5923 384.436 35.4441C384.598 35.296 384.789 35.1822 384.996 35.1097C385.204 35.0373 385.424 35.0076 385.643 35.0225C385.862 35.0374 386.076 35.0965 386.272 35.1963C386.468 35.2962 386.641 35.4346 386.782 35.6034C386.923 35.7721 387.028 35.9676 387.091 36.1781C387.154 36.3885 387.174 36.6096 387.149 36.8279L387.113 37.3684" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M385.019 44.2136C383.236 44.0925 381.876 42.5343 381.997 40.7509L382.107 39.1296C382.146 38.5563 382.411 38.022 382.844 37.6441C383.277 37.2662 383.842 37.0758 384.415 37.1147L386.577 37.2615C387.15 37.3004 387.685 37.5655 388.062 37.9984C388.44 38.4313 388.631 38.9966 388.592 39.5699L388.482 41.1912C388.361 42.9746 386.803 44.3347 385.019 44.2136Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M385.019 44.2135L385.349 39.3497" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M382.467 38.0682C381.431 37.8893 380.629 36.9119 380.706 35.777" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M382.033 40.2105L379.872 40.0637" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M380.119 44.4238C380.196 43.2889 381.18 42.3785 382.319 42.4015" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M390.417 36.4363C390.34 37.5712 389.413 38.4312 388.379 38.4696" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M390.68 40.7975L388.518 40.6508" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M387.939 42.7831C389.071 42.9142 389.923 43.9493 389.846 45.0842" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip29_532_2318)">
<path d="M57.8418 33.2779L58.7888 34.3629" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M61.0802 34.5184L62.1652 33.5714" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M58.1941 36.087L58.2308 35.5465C58.2358 35.3269 58.2854 35.1105 58.3763 34.9105C58.4673 34.7105 58.5979 34.531 58.7602 34.3828C58.9225 34.2347 59.1131 34.1209 59.3205 34.0485C59.5279 33.976 59.7479 33.9463 59.9671 33.9612C60.1864 33.9761 60.4003 34.0352 60.596 34.1351C60.7918 34.2349 60.9653 34.3734 61.106 34.5421C61.2468 34.7108 61.3519 34.9063 61.415 35.1168C61.4781 35.3272 61.498 35.5483 61.4733 35.7667L61.4366 36.3071" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M59.3431 43.1523C57.5597 43.0312 56.1996 41.473 56.3207 39.6896L56.4308 38.0684C56.4697 37.4951 56.7348 36.9607 57.1677 36.5828C57.6006 36.2049 58.1659 36.0145 58.7392 36.0534L60.9009 36.2002C61.4742 36.2391 62.0086 36.5042 62.3865 36.9371C62.7643 37.37 62.9548 37.9353 62.9158 38.5086L62.8058 40.1299C62.6847 41.9133 61.1265 43.2734 59.3431 43.1523Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M59.3431 43.1523L59.6733 38.2885" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M56.7906 37.007C55.7549 36.8281 54.9526 35.8506 55.0297 34.7158" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M56.3574 39.1492L54.1957 39.0024" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M54.4426 43.3625C54.5196 42.2276 55.5044 41.3172 56.643 41.3402" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M64.741 35.3751C64.6639 36.5099 63.7369 37.37 62.7027 37.4083" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M65.0041 39.7362L62.8424 39.5895" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M62.2633 41.7218C63.3945 41.8529 64.2472 42.888 64.1702 44.0229" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip30_532_2318)">
<path d="M82.7844 34.9712L83.7314 36.0562" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M86.0228 36.2117L87.1078 35.2647" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M83.1367 37.7803L83.1734 37.2398C83.1785 37.0202 83.228 36.8038 83.319 36.6038C83.41 36.4038 83.5405 36.2243 83.7028 36.0761C83.8651 35.928 84.0557 35.8142 84.2631 35.7418C84.4706 35.6693 84.6905 35.6396 84.9098 35.6545C85.129 35.6694 85.3429 35.7285 85.5387 35.8284C85.7344 35.9282 85.9079 36.0667 86.0486 36.2354C86.1894 36.4041 86.2945 36.5996 86.3576 36.8101C86.4208 37.0205 86.4406 37.2416 86.4159 37.46L86.3792 38.0004" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M84.2857 44.8456C82.5023 44.7245 81.1423 43.1663 81.2633 41.3829L81.3734 39.7617C81.4123 39.1884 81.6774 38.654 82.1103 38.2761C82.5432 37.8982 83.1085 37.7078 83.6818 37.7467L85.8435 37.8935C86.4168 37.9324 86.9512 38.1975 87.3291 38.6304C87.707 39.0633 87.8974 39.6286 87.8585 40.2019L87.7484 41.8232C87.6273 43.6066 86.0691 44.9667 84.2857 44.8456Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M84.2858 44.8456L84.616 39.9818" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M81.7332 38.7003C80.6976 38.5214 79.8952 37.5439 79.9723 36.4091" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M81.3 40.8425L79.1383 40.6957" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M79.3852 45.0558C79.4623 43.9209 80.447 43.0105 81.5856 43.0335" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M89.6836 37.0684C89.6065 38.2032 88.6795 39.0633 87.6454 39.1016" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M89.9467 41.4295L87.785 41.2828" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M87.2059 43.4151C88.3372 43.5462 89.1898 44.5813 89.1128 45.7162" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip31_532_2318)">
<path d="M107.727 36.6645L108.674 37.7495" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M110.965 37.905L112.05 36.958" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M108.079 39.4736L108.116 38.9331C108.121 38.7135 108.17 38.4971 108.261 38.2971C108.352 38.0971 108.483 37.9176 108.645 37.7694C108.808 37.6213 108.998 37.5075 109.206 37.4351C109.413 37.3626 109.633 37.3329 109.852 37.3478C110.071 37.3627 110.285 37.4218 110.481 37.5217C110.677 37.6215 110.85 37.76 110.991 37.9287C111.132 38.0974 111.237 38.2929 111.3 38.5034C111.363 38.7138 111.383 38.9349 111.358 39.1533L111.322 39.6937" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M109.228 46.5389C107.445 46.4178 106.085 44.8596 106.206 43.0762L106.316 41.455C106.355 40.8816 106.62 40.3473 107.053 39.9694C107.486 39.5915 108.051 39.4011 108.624 39.44L110.786 39.5868C111.359 39.6257 111.894 39.8908 112.272 40.3237C112.649 40.7566 112.84 41.3219 112.801 41.8952L112.691 43.5165C112.57 45.2999 111.012 46.66 109.228 46.5389Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M109.228 46.5389L109.558 41.675" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M106.676 40.3936C105.64 40.2147 104.838 39.2372 104.915 38.1024" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M106.243 42.5358L104.081 42.389" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M104.328 46.7491C104.405 45.6142 105.39 44.7038 106.528 44.7268" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M114.626 38.7616C114.549 39.8965 113.622 40.7566 112.588 40.7949" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M114.889 43.1228L112.728 42.9761" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M112.148 45.1084C113.28 45.2395 114.132 46.2746 114.055 47.4095" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip32_532_2318)">
<path d="M132.67 38.3578L133.617 39.4428" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M135.908 39.5984L136.993 38.6514" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M133.022 41.1669L133.059 40.6265C133.064 40.4068 133.113 40.1905 133.204 39.9904C133.295 39.7904 133.426 39.6109 133.588 39.4628C133.75 39.3146 133.941 39.2009 134.148 39.1284C134.356 39.0559 134.576 39.0263 134.795 39.0411C135.014 39.056 135.228 39.1152 135.424 39.215C135.62 39.3148 135.793 39.4533 135.934 39.622C136.075 39.7908 136.18 39.9863 136.243 40.1967C136.306 40.4072 136.326 40.6283 136.301 40.8466L136.264 41.387" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M134.171 48.2323C132.387 48.1112 131.027 46.553 131.148 44.7696L131.259 43.1483C131.297 42.575 131.563 42.0406 131.995 41.6628C132.428 41.2849 132.994 41.0945 133.567 41.1334L135.729 41.2801C136.302 41.3191 136.836 41.5841 137.214 42.0171C137.592 42.45 137.783 43.0153 137.744 43.5886L137.634 45.2099C137.512 46.9932 135.954 48.3533 134.171 48.2323Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M134.171 48.2322L134.501 43.3684" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M131.618 42.0869C130.583 41.908 129.78 40.9306 129.857 39.7957" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M131.185 44.2292L129.023 44.0824" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M129.27 48.4425C129.347 47.3076 130.332 46.3972 131.471 46.4202" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M139.569 40.455C139.492 41.5899 138.565 42.4499 137.53 42.4883" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M139.832 44.8162L137.67 44.6694" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M137.091 46.8018C138.222 46.9328 139.075 47.968 138.998 49.1029" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip33_532_2318)">
<path d="M157.612 40.0511L158.559 41.1361" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M160.851 41.2917L161.936 40.3447" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.964 42.8602L158.001 42.3198C158.006 42.1001 158.056 41.8837 158.147 41.6837C158.238 41.4837 158.368 41.3042 158.531 41.1561C158.693 41.0079 158.883 40.8942 159.091 40.8217C159.298 40.7492 159.518 40.7196 159.738 40.7344C159.957 40.7493 160.171 40.8085 160.366 40.9083C160.562 41.0081 160.736 41.1466 160.876 41.3153C161.017 41.4841 161.122 41.6796 161.185 41.89C161.249 42.1005 161.268 42.3216 161.244 42.5399L161.207 43.0803" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M159.113 49.9256C157.33 49.8045 155.97 48.2463 156.091 46.4629L156.201 44.8416C156.24 44.2683 156.505 43.7339 156.938 43.3561C157.371 42.9782 157.936 42.7878 158.51 42.8267L160.671 42.9734C161.245 43.0124 161.779 43.2774 162.157 43.7104C162.535 44.1433 162.725 44.7086 162.686 45.2819L162.576 46.9032C162.455 48.6865 160.897 50.0466 159.113 49.9256Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M159.114 49.9255L159.444 45.0617" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M156.561 43.7802C155.525 43.6013 154.723 42.6239 154.8 41.489" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M156.128 45.9224L153.966 45.7757" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M154.213 50.1358C154.29 49.0009 155.275 48.0905 156.413 48.1135" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M164.511 42.1483C164.434 43.2832 163.507 44.1432 162.473 44.1816" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M164.775 46.5095L162.613 46.3627" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M162.034 48.4951C163.165 48.6261 164.018 49.6613 163.941 50.7962" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip34_532_2318)">
<path d="M182.555 41.7444L183.502 42.8294" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M185.793 42.985L186.878 42.038" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182.907 44.5535L182.944 44.0131C182.949 43.7934 182.998 43.577 183.089 43.377C183.18 43.177 183.311 42.9975 183.473 42.8494C183.635 42.7012 183.826 42.5875 184.033 42.515C184.241 42.4425 184.461 42.4129 184.68 42.4277C184.899 42.4426 185.113 42.5018 185.309 42.6016C185.505 42.7014 185.678 42.8399 185.819 43.0086C185.96 43.1774 186.065 43.3729 186.128 43.5833C186.191 43.7938 186.211 44.0149 186.186 44.2332L186.149 44.7736" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M184.056 51.6189C182.273 51.4978 180.913 49.9396 181.034 48.1562L181.144 46.5349C181.183 45.9616 181.448 45.4272 181.881 45.0494C182.314 44.6715 182.879 44.4811 183.452 44.52L185.614 44.6667C186.187 44.7057 186.722 44.9707 187.099 45.4037C187.477 45.8366 187.668 46.4019 187.629 46.9752L187.519 48.5964C187.398 50.3798 185.839 51.7399 184.056 51.6189Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M184.056 51.6188L184.386 46.755" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M181.503 45.4735C180.468 45.2946 179.666 44.3172 179.743 43.1823" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M181.07 47.6157L178.909 47.469" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M179.156 51.829C179.233 50.6942 180.217 49.7838 181.356 49.8068" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M189.454 43.8416C189.377 44.9765 188.45 45.8365 187.416 45.8749" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M189.717 48.2028L187.555 48.056" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M186.976 50.1884C188.108 50.3194 188.96 51.3546 188.883 52.4895" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip35_532_2318)">
<path d="M207.497 43.4377L208.444 44.5227" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M210.736 44.6783L211.821 43.7313" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.85 46.2468L207.886 45.7064C207.891 45.4867 207.941 45.2703 208.032 45.0703C208.123 44.8703 208.253 44.6908 208.416 44.5427C208.578 44.3945 208.769 44.2808 208.976 44.2083C209.183 44.1358 209.403 44.1062 209.623 44.121C209.842 44.1359 210.056 44.1951 210.251 44.2949C210.447 44.3947 210.621 44.5332 210.761 44.7019C210.902 44.8707 211.007 45.0662 211.07 45.2766C211.134 45.4871 211.153 45.7082 211.129 45.9265L211.092 46.4669" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M208.999 53.3122C207.215 53.1911 205.855 51.6329 205.976 49.8495L206.086 48.2282C206.125 47.6549 206.39 47.1205 206.823 46.7427C207.256 46.3648 207.821 46.1744 208.395 46.2133L210.556 46.36C211.13 46.399 211.664 46.664 212.042 47.097C212.42 47.5299 212.61 48.0952 212.571 48.6685L212.461 50.2897C212.34 52.0731 210.782 53.4332 208.999 53.3122Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M208.999 53.3121L209.329 48.4483" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M206.446 47.1668C205.41 46.9879 204.608 46.0105 204.685 44.8756" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M206.013 49.309L203.851 49.1623" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M204.098 53.5223C204.175 52.3875 205.16 51.4771 206.298 51.5001" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M214.397 45.5349C214.32 46.6698 213.392 47.5298 212.358 47.5682" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M214.66 49.8961L212.498 49.7493" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M211.919 51.8817C213.05 52.0127 213.903 53.0479 213.826 54.1828" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip36_532_2318)">
<path d="M232.44 45.131L233.387 46.216" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M235.678 46.3716L236.763 45.4246" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M232.792 47.9401L232.829 47.3997C232.834 47.18 232.883 46.9636 232.974 46.7636C233.065 46.5636 233.196 46.3841 233.358 46.236C233.521 46.0878 233.711 45.9741 233.919 45.9016C234.126 45.8291 234.346 45.7995 234.565 45.8143C234.784 45.8292 234.998 45.8884 235.194 45.9882C235.39 46.088 235.563 46.2265 235.704 46.3952C235.845 46.564 235.95 46.7595 236.013 46.9699C236.076 47.1804 236.096 47.4015 236.071 47.6198L236.035 48.1602" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M233.941 55.0054C232.158 54.8844 230.798 53.3262 230.919 51.5428L231.029 49.9215C231.068 49.3482 231.333 48.8138 231.766 48.4359C232.199 48.058 232.764 47.8676 233.337 47.9065L235.499 48.0533C236.072 48.0922 236.607 48.3573 236.985 48.7902C237.362 49.2231 237.553 49.7884 237.514 50.3617L237.404 51.983C237.283 53.7664 235.725 55.1265 233.941 55.0054Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M233.941 55.0054L234.271 50.1416" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M231.389 48.8601C230.353 48.6812 229.551 47.7038 229.628 46.5689" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M230.956 51.0023L228.794 50.8556" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M229.041 55.2156C229.118 54.0808 230.103 53.1704 231.241 53.1934" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M239.339 47.2282C239.262 48.3631 238.335 49.2231 237.301 49.2615" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M239.602 51.5894L237.441 51.4426" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M236.862 53.575C237.993 53.706 238.845 54.7412 238.768 55.8761" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip37_532_2318)">
<path d="M257.383 46.8244L258.33 47.9094" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M260.621 48.0649L261.706 47.1179" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M257.735 49.6335L257.771 49.093C257.776 48.8734 257.826 48.657 257.917 48.457C258.008 48.257 258.139 48.0775 258.301 47.9293C258.463 47.7812 258.654 47.6674 258.861 47.595C259.069 47.5225 259.289 47.4928 259.508 47.5077C259.727 47.5226 259.941 47.5817 260.137 47.6816C260.332 47.7814 260.506 47.9199 260.647 48.0886C260.787 48.2573 260.893 48.4528 260.956 48.6633C261.019 48.8738 261.039 49.0948 261.014 49.3132L260.977 49.8536" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M258.884 56.6988C257.1 56.5777 255.74 55.0195 255.861 53.2361L255.971 51.6148C256.01 51.0415 256.275 50.5072 256.708 50.1293C257.141 49.7514 257.707 49.561 258.28 49.5999L260.442 49.7467C261.015 49.7856 261.549 50.0507 261.927 50.4836C262.305 50.9165 262.495 51.4818 262.457 52.0551L262.346 53.6764C262.225 55.4598 260.667 56.8199 258.884 56.6988Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M258.884 56.6988L259.214 51.835" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M256.331 50.5534C255.296 50.3745 254.493 49.3971 254.57 48.2622" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M255.898 52.6957L253.736 52.549" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M253.983 56.909C254.06 55.7741 255.045 54.8637 256.184 54.8867" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M264.282 48.9215C264.205 50.0564 263.278 50.9164 262.244 50.9548" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M264.545 53.2827L262.383 53.136" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M261.804 55.2683C262.935 55.3994 263.788 56.4345 263.711 57.5694" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip38_532_2318)">
<path d="M282.325 48.5177L283.272 49.6027" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M285.563 49.7582L286.648 48.8112" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M282.677 51.3268L282.714 50.7863C282.719 50.5667 282.769 50.3503 282.86 50.1503C282.951 49.9503 283.081 49.7708 283.243 49.6226C283.406 49.4745 283.596 49.3607 283.804 49.2883C284.011 49.2158 284.231 49.1861 284.45 49.201C284.67 49.2159 284.884 49.275 285.079 49.3749C285.275 49.4747 285.448 49.6132 285.589 49.7819C285.73 49.9506 285.835 50.1461 285.898 50.3566C285.961 50.5671 285.981 50.7881 285.957 51.0065L285.92 51.5469" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M283.826 58.3921C282.043 58.271 280.683 56.7128 280.804 54.9294L280.914 53.3081C280.953 52.7348 281.218 52.2005 281.651 51.8226C282.084 51.4447 282.649 51.2543 283.222 51.2932L285.384 51.44C285.957 51.4789 286.492 51.744 286.87 52.1769C287.248 52.6098 287.438 53.1751 287.399 53.7484L287.289 55.3697C287.168 57.1531 285.61 58.5132 283.826 58.3921Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M283.826 58.3921L284.157 53.5283" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M281.274 52.2467C280.238 52.0678 279.436 51.0904 279.513 49.9555" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M280.841 54.389L278.679 54.2422" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M278.926 58.6023C279.003 57.4674 279.988 56.557 281.126 56.58" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M289.224 50.6148C289.147 51.7497 288.22 52.6097 287.186 52.6481" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M289.487 54.976L287.326 54.8293" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M286.747 56.9616C287.878 57.0927 288.731 58.1278 288.653 59.2627" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip39_532_2318)">
<path d="M307.268 50.211L308.215 51.296" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M310.506 51.4515L311.591 50.5045" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M307.62 53.0201L307.657 52.4796C307.662 52.26 307.711 52.0436 307.802 51.8436C307.893 51.6436 308.024 51.4641 308.186 51.3159C308.348 51.1678 308.539 51.054 308.746 50.9816C308.954 50.9091 309.174 50.8794 309.393 50.8943C309.612 50.9092 309.826 50.9683 310.022 51.0682C310.218 51.168 310.391 51.3065 310.532 51.4752C310.673 51.6439 310.778 51.8394 310.841 52.0499C310.904 52.2604 310.924 52.4814 310.899 52.6998L310.862 53.2402" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M308.769 60.0854C306.986 59.9643 305.626 58.4061 305.747 56.6227L305.857 55.0014C305.896 54.4281 306.161 53.8938 306.594 53.5159C307.026 53.138 307.592 52.9476 308.165 52.9865L310.327 53.1333C310.9 53.1722 311.434 53.4373 311.812 53.8702C312.19 54.3031 312.381 54.8684 312.342 55.4417L312.232 57.063C312.111 58.8464 310.552 60.2064 308.769 60.0854Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M308.769 60.0854L309.099 55.2216" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M306.216 53.94C305.181 53.7611 304.378 52.7837 304.456 51.6488" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M305.783 56.0823L303.622 55.9355" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M303.869 60.2956C303.946 59.1607 304.93 58.2503 306.069 58.2733" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M314.167 52.3081C314.09 53.443 313.163 54.303 312.129 54.3414" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M314.43 56.6693L312.268 56.5226" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M311.689 58.6549C312.82 58.786 313.673 59.8211 313.596 60.956" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip40_532_2318)">
<path d="M332.21 51.9043L333.157 52.9893" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M335.449 53.1448L336.534 52.1978" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M332.562 54.7134L332.599 54.1729C332.604 53.9533 332.654 53.7369 332.745 53.5369C332.836 53.3369 332.966 53.1574 333.129 53.0092C333.291 52.8611 333.481 52.7473 333.689 52.6749C333.896 52.6024 334.116 52.5727 334.336 52.5876C334.555 52.6025 334.769 52.6616 334.964 52.7615C335.16 52.8613 335.334 52.9998 335.474 53.1685C335.615 53.3372 335.72 53.5327 335.783 53.7432C335.847 53.9537 335.866 54.1747 335.842 54.3931L335.805 54.9335" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M333.712 61.7787C331.928 61.6576 330.568 60.0994 330.689 58.316L330.799 56.6947C330.838 56.1214 331.103 55.5871 331.536 55.2092C331.969 54.8313 332.534 54.6409 333.108 54.6798L335.269 54.8266C335.843 54.8655 336.377 55.1306 336.755 55.5635C337.133 55.9964 337.323 56.5617 337.284 57.135L337.174 58.7563C337.053 60.5397 335.495 61.8997 333.712 61.7787Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M333.712 61.7787L334.042 56.9149" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M331.159 55.6333C330.123 55.4544 329.321 54.477 329.398 53.3421" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M330.726 57.7756L328.564 57.6288" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M328.811 61.9889C328.888 60.854 329.873 59.9436 331.011 59.9666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M339.109 54.0014C339.032 55.1363 338.105 55.9963 337.071 56.0347" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M339.373 58.3626L337.211 58.2159" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M336.632 60.3482C337.763 60.4793 338.616 61.5144 338.539 62.6493" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip41_532_2318)">
<path d="M357.153 53.5977L358.1 54.6826" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M360.391 54.8382L361.476 53.8911" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M357.505 56.4067L357.542 55.8663C357.547 55.6466 357.596 55.4302 357.687 55.2302C357.778 55.0302 357.909 54.8507 358.071 54.7026C358.233 54.5544 358.424 54.4407 358.631 54.3682C358.839 54.2957 359.059 54.266 359.278 54.2809C359.497 54.2958 359.711 54.3549 359.907 54.4548C360.103 54.5546 360.276 54.6931 360.417 54.8618C360.558 55.0305 360.663 55.226 360.726 55.4365C360.789 55.647 360.809 55.8681 360.784 56.0864L360.748 56.6268" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M358.654 63.472C356.871 63.3509 355.511 61.7927 355.632 60.0093L355.742 58.3881C355.781 57.8148 356.046 57.2804 356.479 56.9025C356.912 56.5246 357.477 56.3342 358.05 56.3731L360.212 56.5199C360.785 56.5588 361.32 56.8239 361.697 57.2568C362.075 57.6897 362.266 58.255 362.227 58.8283L362.117 60.4496C361.996 62.233 360.438 63.5931 358.654 63.472Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M358.654 63.472L358.984 58.6082" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M356.102 57.3267C355.066 57.1478 354.264 56.1703 354.341 55.0355" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M355.668 59.4689L353.507 59.3222" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M353.754 63.6822C353.831 62.5473 354.815 61.637 355.954 61.66" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M364.052 55.6948C363.975 56.8296 363.048 57.6897 362.014 57.728" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M364.315 60.0559L362.154 59.9092" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M361.574 62.0415C362.706 62.1726 363.558 63.2078 363.481 64.3426" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip42_532_2318)">
<path d="M382.095 55.291L383.042 56.3759" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M385.334 56.5315L386.419 55.5844" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M382.448 58.1L382.484 57.5596C382.489 57.3399 382.539 57.1235 382.63 56.9235C382.721 56.7235 382.852 56.544 383.014 56.3959C383.176 56.2477 383.367 56.134 383.574 56.0615C383.782 55.989 384.002 55.9593 384.221 55.9742C384.44 55.9891 384.654 56.0482 384.85 56.1481C385.045 56.2479 385.219 56.3864 385.36 56.5551C385.5 56.7238 385.605 56.9193 385.669 57.1298C385.732 57.3403 385.752 57.5614 385.727 57.7797L385.69 58.3201" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.597 65.1653C381.813 65.0442 380.453 63.486 380.574 61.7026L380.684 60.0814C380.723 59.5081 380.988 58.9737 381.421 58.5958C381.854 58.2179 382.42 58.0275 382.993 58.0664L385.155 58.2132C385.728 58.2521 386.262 58.5172 386.64 58.9501C387.018 59.383 387.208 59.9483 387.169 60.5216L387.059 62.1429C386.938 63.9263 385.38 65.2864 383.597 65.1653Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.597 65.1653L383.927 60.3015" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M381.044 59.02C380.009 58.8411 379.206 57.8636 379.283 56.7288" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M380.611 61.1622L378.449 61.0155" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M378.696 65.3755C378.773 64.2406 379.758 63.3302 380.897 63.3533" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M388.995 57.3881C388.918 58.5229 387.991 59.383 386.956 59.4213" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M389.258 61.7492L387.096 61.6025" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M386.517 63.7348C387.648 63.8659 388.501 64.9011 388.424 66.0359" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip43_532_2318)">
<path d="M56.4194 54.2297L57.3665 55.3146" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M59.6578 55.4702L60.7428 54.5232" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M56.7717 57.0387L56.8084 56.4983C56.8135 56.2786 56.863 56.0622 56.954 55.8622C57.045 55.6622 57.1755 55.4827 57.3378 55.3346C57.5001 55.1864 57.6907 55.0727 57.8981 55.0002C58.1056 54.9277 58.3255 54.8981 58.5448 54.9129C58.764 54.9278 58.9779 54.987 59.1737 55.0868C59.3694 55.1866 59.5429 55.3251 59.6837 55.4938C59.8244 55.6626 59.9295 55.8581 59.9926 56.0685C60.0558 56.279 60.0756 56.5001 60.0509 56.7184L60.0142 57.2588" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M57.9207 64.104C56.1373 63.983 54.7773 62.4248 54.8983 60.6414L55.0084 59.0201C55.0473 58.4468 55.3124 57.9124 55.7453 57.5345C56.1782 57.1567 56.7435 56.9662 57.3168 57.0052L59.4785 57.1519C60.0518 57.1908 60.5862 57.4559 60.9641 57.8888C61.342 58.3217 61.5324 58.887 61.4935 59.4604L61.3834 61.0816C61.2623 62.865 59.7041 64.2251 57.9207 64.104Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M57.9208 64.104L58.251 59.2402" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M55.3682 57.9587C54.3326 57.7798 53.5303 56.8024 53.6073 55.6675" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M54.935 60.1009L52.7733 59.9542" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M53.0202 64.3142C53.0973 63.1794 54.082 62.269 55.2206 62.292" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M63.3186 56.3268C63.2416 57.4617 62.3145 58.3217 61.2804 58.3601" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M63.5817 60.688L61.42 60.5412" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M60.8409 62.6736C61.9722 62.8046 62.8248 63.8398 62.7478 64.9747" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip44_532_2318)">
<path d="M81.3621 55.923L82.3091 57.0079" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M84.6005 57.1635L85.6854 56.2165" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M81.7143 58.732L81.751 58.1916C81.7561 57.9719 81.8056 57.7555 81.8966 57.5555C81.9876 57.3555 82.1182 57.176 82.2804 57.0279C82.4427 56.8797 82.6333 56.766 82.8408 56.6935C83.0482 56.621 83.2682 56.5914 83.4874 56.6062C83.7066 56.6211 83.9206 56.6803 84.1163 56.7801C84.312 56.8799 84.4855 57.0184 84.6263 57.1871C84.767 57.3559 84.8721 57.5514 84.9353 57.7618C84.9984 57.9723 85.0182 58.1934 84.9936 58.4117L84.9569 58.9521" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M82.8634 65.7973C81.08 65.6763 79.7199 64.1181 79.841 62.3347L79.951 60.7134C79.9899 60.1401 80.255 59.6057 80.6879 59.2278C81.1209 58.85 81.6862 58.6595 82.2595 58.6985L84.4212 58.8452C84.9945 58.8841 85.5288 59.1492 85.9067 59.5821C86.2846 60.015 86.475 60.5803 86.4361 61.1536L86.326 62.7749C86.205 64.5583 84.6468 65.9184 82.8634 65.7973Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M82.8634 65.7973L83.1936 60.9335" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M80.3109 59.652C79.2752 59.4731 78.4729 58.4957 78.5499 57.3608" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M79.8776 61.7942L77.7159 61.6475" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M77.9629 66.0075C78.0399 64.8727 79.0247 63.9623 80.1632 63.9853" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M88.2612 58.0201C88.1842 59.155 87.2571 60.015 86.223 60.0534" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M88.5244 62.3813L86.3627 62.2345" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M85.7836 64.3669C86.9148 64.4979 87.7675 65.5331 87.6904 66.668" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip45_532_2318)">
<path d="M106.305 57.6163L107.252 58.7013" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M109.543 58.8568L110.628 57.9098" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M106.657 60.4253L106.694 59.8849C106.699 59.6652 106.748 59.4489 106.839 59.2489C106.93 59.0489 107.061 58.8694 107.223 58.7212C107.385 58.5731 107.576 58.4593 107.783 58.3868C107.991 58.3144 108.211 58.2847 108.43 58.2996C108.649 58.3145 108.863 58.3736 109.059 58.4734C109.255 58.5733 109.428 58.7117 109.569 58.8805C109.71 59.0492 109.815 59.2447 109.878 59.4552C109.941 59.6656 109.961 59.8867 109.936 60.105L109.899 60.6455" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M107.806 67.4907C106.022 67.3696 104.662 65.8114 104.783 64.028L104.894 62.4067C104.932 61.8334 105.198 61.299 105.63 60.9212C106.063 60.5433 106.629 60.3529 107.202 60.3918L109.364 60.5385C109.937 60.5775 110.471 60.8425 110.849 61.2754C111.227 61.7084 111.418 62.2737 111.379 62.847L111.269 64.4682C111.147 66.2516 109.589 67.6117 107.806 67.4907Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M107.806 67.4907L108.136 62.6269" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M105.253 61.3453C104.218 61.1664 103.415 60.189 103.492 59.0541" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M104.82 63.4876L102.658 63.3408" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M102.905 67.7009C102.982 66.566 103.967 65.6556 105.106 65.6786" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M113.204 59.7134C113.127 60.8483 112.2 61.7083 111.166 61.7467" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M113.467 64.0746L111.305 63.9278" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M110.726 66.0602C111.857 66.1913 112.71 67.2264 112.633 68.3613" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip46_532_2318)">
<path d="M131.247 59.3096L132.194 60.3946" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M134.486 60.5501L135.571 59.6031" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M131.599 62.1186L131.636 61.5782C131.641 61.3585 131.691 61.1422 131.782 60.9422C131.873 60.7422 132.003 60.5627 132.166 60.4145C132.328 60.2663 132.518 60.1526 132.726 60.0801C132.933 60.0077 133.153 59.978 133.373 59.9929C133.592 60.0077 133.806 60.0669 134.001 60.1667C134.197 60.2666 134.371 60.405 134.511 60.5738C134.652 60.7425 134.757 60.938 134.82 61.1485C134.884 61.3589 134.903 61.58 134.879 61.7983L134.842 62.3388" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M132.748 69.1839C130.965 69.0629 129.605 67.5047 129.726 65.7213L129.836 64.1C129.875 63.5267 130.14 62.9923 130.573 62.6145C131.006 62.2366 131.571 62.0462 132.145 62.0851L134.306 62.2318C134.88 62.2708 135.414 62.5358 135.792 62.9687C136.17 63.4017 136.36 63.967 136.321 64.5403L136.211 66.1615C136.09 67.9449 134.532 69.305 132.748 69.1839Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M132.749 69.184L133.079 64.3202" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M130.196 63.0386C129.16 62.8597 128.358 61.8823 128.435 60.7474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M129.763 65.1809L127.601 65.0341" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M127.848 69.3942C127.925 68.2593 128.91 67.3489 130.048 67.3719" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M138.146 61.4067C138.069 62.5416 137.142 63.4016 136.108 63.44" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M138.409 65.7679L136.248 65.6211" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M135.669 67.7535C136.8 67.8846 137.653 68.9197 137.576 70.0546" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip47_532_2318)">
<path d="M156.19 61.0029L157.137 62.0879" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M159.428 62.2434L160.513 61.2964" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M156.542 63.812L156.579 63.2715C156.584 63.0519 156.633 62.8355 156.724 62.6355C156.815 62.4355 156.946 62.256 157.108 62.1078C157.27 61.9597 157.461 61.8459 157.669 61.7735C157.876 61.701 158.096 61.6713 158.315 61.6862C158.534 61.7011 158.748 61.7602 158.944 61.8601C159.14 61.9599 159.313 62.0984 159.454 62.2671C159.595 62.4358 159.7 62.6313 159.763 62.8418C159.826 63.0523 159.846 63.2733 159.821 63.4917L159.785 64.0321" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.691 70.8773C155.908 70.7562 154.548 69.198 154.669 67.4146L154.779 65.7933C154.818 65.22 155.083 64.6857 155.516 64.3078C155.949 63.9299 156.514 63.7395 157.087 63.7784L159.249 63.9252C159.822 63.9641 160.357 64.2292 160.734 64.6621C161.112 65.095 161.303 65.6603 161.264 66.2336L161.154 67.8549C161.033 69.6383 159.475 70.9984 157.691 70.8773Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.691 70.8773L158.021 66.0135" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M155.139 64.7319C154.103 64.553 153.301 63.5756 153.378 62.4407" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M154.705 66.8742L152.544 66.7274" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M152.791 71.0875C152.868 69.9526 153.852 69.0422 154.991 69.0652" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M163.089 63.1C163.012 64.2349 162.085 65.0949 161.051 65.1333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M163.352 67.4612L161.19 67.3145" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M160.611 69.4468C161.743 69.5779 162.595 70.613 162.518 71.7479" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip48_532_2318)">
<path d="M181.132 62.6962L182.079 63.7812" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M184.371 63.9367L185.456 62.9897" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M181.485 65.5053L181.521 64.9648C181.526 64.7452 181.576 64.5288 181.667 64.3288C181.758 64.1288 181.888 63.9493 182.051 63.8011C182.213 63.653 182.404 63.5392 182.611 63.4668C182.818 63.3943 183.038 63.3646 183.258 63.3795C183.477 63.3944 183.691 63.4535 183.887 63.5534C184.082 63.6532 184.256 63.7917 184.397 63.9604C184.537 64.1291 184.642 64.3246 184.706 64.5351C184.769 64.7456 184.788 64.9666 184.764 65.185L184.727 65.7254" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182.634 72.5706C180.85 72.4495 179.49 70.8913 179.611 69.1079L179.721 67.4866C179.76 66.9133 180.025 66.379 180.458 66.0011C180.891 65.6232 181.456 65.4328 182.03 65.4717L184.191 65.6185C184.765 65.6574 185.299 65.9225 185.677 66.3554C186.055 66.7883 186.245 67.3536 186.206 67.9269L186.096 69.5482C185.975 71.3316 184.417 72.6916 182.634 72.5706Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182.634 72.5706L182.964 67.7068" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M180.081 66.4252C179.045 66.2463 178.243 65.2689 178.32 64.134" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M179.648 68.5675L177.486 68.4207" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M177.733 72.7808C177.81 71.6459 178.795 70.7355 179.934 70.7585" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M188.032 64.7933C187.955 65.9282 187.028 66.7882 185.993 66.8266" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M188.295 69.1545L186.133 69.0078" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M185.554 71.1401C186.685 71.2712 187.538 72.3063 187.461 73.4412" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip49_532_2318)">
<path d="M206.075 64.3895L207.022 65.4745" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M209.313 65.63L210.398 64.683" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M206.427 67.1986L206.464 66.6581C206.469 66.4385 206.518 66.2221 206.609 66.0221C206.7 65.8221 206.831 65.6426 206.993 65.4944C207.156 65.3463 207.346 65.2325 207.554 65.1601C207.761 65.0876 207.981 65.0579 208.2 65.0728C208.419 65.0877 208.633 65.1468 208.829 65.2467C209.025 65.3465 209.198 65.485 209.339 65.6537C209.48 65.8224 209.585 66.0179 209.648 66.2284C209.711 66.4389 209.731 66.6599 209.706 66.8783L209.67 67.4187" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.576 74.2639C205.793 74.1428 204.433 72.5846 204.554 70.8012L204.664 69.1799C204.703 68.6066 204.968 68.0723 205.401 67.6944C205.834 67.3165 206.399 67.1261 206.972 67.165L209.134 67.3118C209.707 67.3507 210.242 67.6158 210.62 68.0487C210.997 68.4816 211.188 69.0469 211.149 69.6202L211.039 71.2415C210.918 73.0249 209.36 74.3849 207.576 74.2639Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.576 74.2639L207.906 69.4001" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M205.024 68.1185C203.988 67.9396 203.186 66.9622 203.263 65.8273" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M204.591 70.2608L202.429 70.114" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M202.676 74.4741C202.753 73.3392 203.738 72.4288 204.876 72.4518" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M212.974 66.4866C212.897 67.6215 211.97 68.4815 210.936 68.5199" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M213.237 70.8478L211.076 70.701" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M210.497 72.8334C211.628 72.9645 212.48 73.9996 212.403 75.1345" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip50_532_2318)">
<path d="M231.018 66.0829L231.965 67.1678" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M234.256 67.3234L235.341 66.3763" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M231.37 68.8919L231.406 68.3515C231.412 68.1318 231.461 67.9154 231.552 67.7154C231.643 67.5154 231.774 67.3359 231.936 67.1878C232.098 67.0396 232.289 66.9259 232.496 66.8534C232.704 66.7809 232.924 66.7512 233.143 66.7661C233.362 66.781 233.576 66.8401 233.772 66.94C233.967 67.0398 234.141 67.1783 234.282 67.347C234.422 67.5157 234.528 67.7112 234.591 67.9217C234.654 68.1322 234.674 68.3533 234.649 68.5716L234.612 69.112" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M232.519 75.9572C230.735 75.8361 229.375 74.2779 229.496 72.4945L229.607 70.8733C229.645 70.3 229.911 69.7656 230.343 69.3877C230.776 69.0098 231.342 68.8194 231.915 68.8583L234.077 69.0051C234.65 69.044 235.184 69.3091 235.562 69.742C235.94 70.1749 236.131 70.7402 236.092 71.3135L235.982 72.9348C235.86 74.7182 234.302 76.0783 232.519 75.9572Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M232.519 75.9572L232.849 71.0934" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M229.966 69.8119C228.931 69.633 228.128 68.6555 228.205 67.5207" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M229.533 71.9541L227.371 71.8074" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M227.618 76.1674C227.695 75.0325 228.68 74.1221 229.819 74.1452" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M237.917 68.18C237.84 69.3148 236.913 70.1749 235.879 70.2132" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M238.18 72.5411L236.018 72.3944" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M235.439 74.5267C236.57 74.6578 237.423 75.693 237.346 76.8278" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip51_532_2318)">
<path d="M255.96 67.7762L256.907 68.8611" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M259.199 69.0167L260.284 68.0696" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M256.312 70.5852L256.349 70.0448C256.354 69.8251 256.404 69.6087 256.495 69.4087C256.586 69.2087 256.716 69.0292 256.878 68.8811C257.041 68.7329 257.231 68.6192 257.439 68.5467C257.646 68.4742 257.866 68.4445 258.085 68.4594C258.305 68.4743 258.519 68.5334 258.714 68.6333C258.91 68.7331 259.084 68.8716 259.224 69.0403C259.365 69.209 259.47 69.4045 259.533 69.615C259.596 69.8255 259.616 70.0466 259.592 70.2649L259.555 70.8053" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M257.461 77.6505C255.678 77.5294 254.318 75.9712 254.439 74.1878L254.549 72.5666C254.588 71.9933 254.853 71.4589 255.286 71.081C255.719 70.7031 256.284 70.5127 256.858 70.5516L259.019 70.6984C259.593 70.7373 260.127 71.0024 260.505 71.4353C260.883 71.8682 261.073 72.4335 261.034 73.0068L260.924 74.6281C260.803 76.4115 259.245 77.7716 257.461 77.6505Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M257.461 77.6505L257.792 72.7867" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M254.909 71.5052C253.873 71.3263 253.071 70.3488 253.148 69.214" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M254.476 73.6474L252.314 73.5007" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M252.561 77.8607C252.638 76.7258 253.623 75.8154 254.761 75.8385" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M262.859 69.8733C262.782 71.0081 261.855 71.8682 260.821 71.9065" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M263.123 74.2344L260.961 74.0877" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M260.382 76.22C261.513 76.3511 262.366 77.3863 262.289 78.5211" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip52_532_2318)">
<path d="M280.903 69.4695L281.85 70.5545" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M284.141 70.71L285.226 69.763" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M281.255 72.2785L281.292 71.7381C281.297 71.5184 281.346 71.3021 281.437 71.102C281.528 70.902 281.659 70.7225 281.821 70.5744C281.983 70.4262 282.174 70.3125 282.381 70.24C282.589 70.1676 282.809 70.1379 283.028 70.1527C283.247 70.1676 283.461 70.2268 283.657 70.3266C283.853 70.4265 284.026 70.5649 284.167 70.7336C284.308 70.9024 284.413 71.0979 284.476 71.3083C284.539 71.5188 284.559 71.7399 284.534 71.9582L284.497 72.4986" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M282.404 79.3438C280.621 79.2228 279.261 77.6646 279.382 75.8812L279.492 74.2599C279.531 73.6866 279.796 73.1522 280.229 72.7743C280.662 72.3965 281.227 72.206 281.8 72.245L283.962 72.3917C284.535 72.4306 285.069 72.6957 285.447 73.1286C285.825 73.5615 286.016 74.1268 285.977 74.7002L285.867 76.3214C285.746 78.1048 284.187 79.4649 282.404 79.3438Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M282.404 79.3438L282.734 74.48" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M279.851 73.1985C278.816 73.0196 278.013 72.0422 278.091 70.9073" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M279.418 75.3408L277.257 75.194" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M277.504 79.5541C277.581 78.4192 278.565 77.5088 279.704 77.5318" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M287.802 71.5666C287.725 72.7015 286.798 73.5615 285.764 73.5999" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M288.065 75.9278L285.903 75.781" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M285.324 77.9134C286.455 78.0444 287.308 79.0796 287.231 80.2145" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip53_532_2318)">
<path d="M305.845 71.1628L306.792 72.2477" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M309.084 72.4033L310.169 71.4563" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M306.198 73.9718L306.234 73.4314C306.239 73.2117 306.289 72.9954 306.38 72.7953C306.471 72.5953 306.601 72.4158 306.764 72.2677C306.926 72.1195 307.117 72.0058 307.324 71.9333C307.531 71.8609 307.751 71.8312 307.971 71.846C308.19 71.8609 308.404 71.9201 308.6 72.0199C308.795 72.1198 308.969 72.2582 309.109 72.4269C309.25 72.5957 309.355 72.7912 309.418 73.0016C309.482 73.2121 309.501 73.4332 309.477 73.6515L309.44 74.1919" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M307.347 81.0371C305.563 80.9161 304.203 79.3579 304.324 77.5745L304.434 75.9532C304.473 75.3799 304.738 74.8455 305.171 74.4676C305.604 74.0898 306.169 73.8993 306.743 73.9383L308.904 74.085C309.478 74.1239 310.012 74.389 310.39 74.8219C310.768 75.2548 310.958 75.8201 310.919 76.3935L310.809 78.0147C310.688 79.7981 309.13 81.1582 307.347 81.0371Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M307.347 81.0371L307.677 76.1733" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M304.794 74.8918C303.758 74.7129 302.956 73.7355 303.033 72.6006" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M304.361 77.0341L302.199 76.8873" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M302.446 81.2474C302.523 80.1125 303.508 79.2021 304.647 79.2251" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M312.745 73.2599C312.668 74.3948 311.74 75.2548 310.706 75.2932" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M313.008 77.6211L310.846 77.4743" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M310.267 79.6067C311.398 79.7377 312.251 80.7729 312.174 81.9078" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip54_532_2318)">
<path d="M330.788 72.8561L331.735 73.941" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M334.026 74.0966L335.111 73.1496" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M331.14 75.6651L331.177 75.1247C331.182 74.905 331.231 74.6887 331.322 74.4886C331.413 74.2886 331.544 74.1091 331.706 73.961C331.868 73.8128 332.059 73.6991 332.267 73.6266C332.474 73.5541 332.694 73.5245 332.913 73.5393C333.132 73.5542 333.346 73.6134 333.542 73.7132C333.738 73.813 333.911 73.9515 334.052 74.1202C334.193 74.289 334.298 74.4845 334.361 74.6949C334.424 74.9054 334.444 75.1265 334.419 75.3448L334.383 75.8852" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M332.289 82.7304C330.506 82.6094 329.146 81.0512 329.267 79.2678L329.377 77.6465C329.416 77.0732 329.681 76.5388 330.114 76.1609C330.547 75.7831 331.112 75.5926 331.685 75.6316L333.847 75.7783C334.42 75.8172 334.955 76.0823 335.333 76.5152C335.71 76.9481 335.901 77.5134 335.862 78.0868L335.752 79.708C335.631 81.4914 334.073 82.8515 332.289 82.7304Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M332.289 82.7304L332.619 77.8666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M329.737 76.5851C328.701 76.4062 327.899 75.4288 327.976 74.2939" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M329.303 78.7274L327.142 78.5806" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M327.389 82.9407C327.466 81.8058 328.451 80.8954 329.589 80.9184" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M337.687 74.9532C337.61 76.0881 336.683 76.9481 335.649 76.9865" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M337.95 79.3144L335.789 79.1676" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M335.209 81.3C336.341 81.431 337.193 82.4662 337.116 83.6011" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip55_532_2318)">
<path d="M355.73 74.5494L356.677 75.6344" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M358.969 75.7899L360.054 74.8429" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M356.083 77.3584L356.119 76.818C356.124 76.5983 356.174 76.382 356.265 76.182C356.356 75.982 356.487 75.8025 356.649 75.6543C356.811 75.5062 357.002 75.3924 357.209 75.3199C357.417 75.2475 357.637 75.2178 357.856 75.2327C358.075 75.2476 358.289 75.3067 358.485 75.4065C358.68 75.5064 358.854 75.6448 358.995 75.8136C359.135 75.9823 359.24 76.1778 359.304 76.3883C359.367 76.5987 359.387 76.8198 359.362 77.0382L359.325 77.5786" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M357.232 84.4238C355.448 84.3027 354.088 82.7445 354.209 80.9611L354.319 79.3398C354.358 78.7665 354.623 78.2321 355.056 77.8543C355.489 77.4764 356.055 77.286 356.628 77.3249L358.79 77.4716C359.363 77.5106 359.897 77.7756 360.275 78.2086C360.653 78.6415 360.843 79.2068 360.805 79.7801L360.694 81.4014C360.573 83.1847 359.015 84.5448 357.232 84.4238Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M357.232 84.4238L357.562 79.56" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M354.679 78.2784C353.644 78.0995 352.841 77.1221 352.918 75.9872" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M354.246 80.4207L352.084 80.2739" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M352.331 84.634C352.408 83.4991 353.393 82.5887 354.532 82.6117" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M362.63 76.6465C362.553 77.7814 361.626 78.6414 360.591 78.6798" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M362.893 81.0077L360.731 80.8609" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M360.152 82.9933C361.283 83.1244 362.136 84.1595 362.059 85.2944" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip56_532_2318)">
<path d="M380.673 76.2427L381.62 77.3277" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.911 77.4832L384.996 76.5362" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M381.025 79.0517L381.062 78.5113C381.067 78.2916 381.117 78.0753 381.208 77.8753C381.299 77.6753 381.429 77.4958 381.591 77.3476C381.754 77.1995 381.944 77.0857 382.152 77.0132C382.359 76.9408 382.579 76.9111 382.798 76.926C383.018 76.9409 383.232 77 383.427 77.0998C383.623 77.1997 383.796 77.3381 383.937 77.5069C384.078 77.6756 384.183 77.8711 384.246 78.0816C384.309 78.292 384.329 78.5131 384.305 78.7314L384.268 79.2719" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M382.174 86.1171C380.391 85.996 379.031 84.4378 379.152 82.6544L379.262 81.0331C379.301 80.4598 379.566 79.9254 379.999 79.5476C380.432 79.1697 380.997 78.9793 381.571 79.0182L383.732 79.1649C384.306 79.2039 384.84 79.4689 385.218 79.9019C385.596 80.3348 385.786 80.9001 385.747 81.4734L385.637 83.0946C385.516 84.878 383.958 86.2381 382.174 86.1171Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M382.174 86.1171L382.505 81.2533" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M379.622 79.9717C378.586 79.7928 377.784 78.8154 377.861 77.6805" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M379.189 82.114L377.027 81.9672" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M377.274 86.3273C377.351 85.1924 378.336 84.282 379.474 84.305" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M387.572 78.3398C387.495 79.4747 386.568 80.3347 385.534 80.3731" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M387.835 82.701L385.674 82.5542" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M385.095 84.6866C386.226 84.8177 387.079 85.8528 387.002 86.9877" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</g>
</g>
<defs>
<clipPath id="clip0_532_2318">
<rect width="400" height="92" fill="white"/>
</clipPath>
<clipPath id="clip1_532_2318">
<rect width="13" height="13" fill="white" transform="translate(56.4365 -10) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip2_532_2318">
<rect width="13" height="13" fill="white" transform="translate(81.3792 -8.3067) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip3_532_2318">
<rect width="13" height="13" fill="white" transform="translate(106.322 -6.6134) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip4_532_2318">
<rect width="13" height="13" fill="white" transform="translate(131.264 -4.92004) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip5_532_2318">
<rect width="13" height="13" fill="white" transform="translate(156.207 -3.22675) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip6_532_2318">
<rect width="13" height="13" fill="white" transform="translate(181.149 -1.53345) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip7_532_2318">
<rect width="13" height="13" fill="white" transform="translate(206.092 0.159851) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip8_532_2318">
<rect width="13" height="13" fill="white" transform="translate(231.035 1.85315) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip9_532_2318">
<rect width="13" height="13" fill="white" transform="translate(255.977 3.54651) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip10_532_2318">
<rect width="13" height="13" fill="white" transform="translate(280.92 5.23981) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip11_532_2318">
<rect width="13" height="13" fill="white" transform="translate(305.862 6.93311) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip12_532_2318">
<rect width="13" height="13" fill="white" transform="translate(330.805 8.6264) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip13_532_2318">
<rect width="13" height="13" fill="white" transform="translate(355.748 10.3197) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip14_532_2318">
<rect width="13" height="13" fill="white" transform="translate(380.69 12.0131) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip15_532_2318">
<rect width="13" height="13" fill="white" transform="translate(55.0142 10.9518) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip16_532_2318">
<rect width="13" height="13" fill="white" transform="translate(79.9568 12.6451) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip17_532_2318">
<rect width="13" height="13" fill="white" transform="translate(104.899 14.3384) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip18_532_2318">
<rect width="13" height="13" fill="white" transform="translate(129.842 16.0317) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip19_532_2318">
<rect width="13" height="13" fill="white" transform="translate(154.785 17.725) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip20_532_2318">
<rect width="13" height="13" fill="white" transform="translate(179.727 19.4183) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip21_532_2318">
<rect width="13" height="13" fill="white" transform="translate(204.67 21.1116) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip22_532_2318">
<rect width="13" height="13" fill="white" transform="translate(229.612 22.8049) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip23_532_2318">
<rect width="13" height="13" fill="white" transform="translate(254.555 24.4983) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip24_532_2318">
<rect width="13" height="13" fill="white" transform="translate(279.497 26.1916) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip25_532_2318">
<rect width="13" height="13" fill="white" transform="translate(304.44 27.8849) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip26_532_2318">
<rect width="13" height="13" fill="white" transform="translate(329.383 29.5782) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip27_532_2318">
<rect width="13" height="13" fill="white" transform="translate(354.325 31.2715) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip28_532_2318">
<rect width="13" height="13" fill="white" transform="translate(379.268 32.9648) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip29_532_2318">
<rect width="13" height="13" fill="white" transform="translate(53.5918 31.9036) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip30_532_2318">
<rect width="13" height="13" fill="white" transform="translate(78.5344 33.5969) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip31_532_2318">
<rect width="13" height="13" fill="white" transform="translate(103.477 35.2902) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip32_532_2318">
<rect width="13" height="13" fill="white" transform="translate(128.42 36.9835) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip33_532_2318">
<rect width="13" height="13" fill="white" transform="translate(153.362 38.6768) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip34_532_2318">
<rect width="13" height="13" fill="white" transform="translate(178.305 40.3701) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip35_532_2318">
<rect width="13" height="13" fill="white" transform="translate(203.247 42.0634) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip36_532_2318">
<rect width="13" height="13" fill="white" transform="translate(228.19 43.7567) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip37_532_2318">
<rect width="13" height="13" fill="white" transform="translate(253.133 45.4501) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip38_532_2318">
<rect width="13" height="13" fill="white" transform="translate(278.075 47.1434) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip39_532_2318">
<rect width="13" height="13" fill="white" transform="translate(303.018 48.8367) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip40_532_2318">
<rect width="13" height="13" fill="white" transform="translate(327.96 50.53) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip41_532_2318">
<rect width="13" height="13" fill="white" transform="translate(352.903 52.2233) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip42_532_2318">
<rect width="13" height="13" fill="white" transform="translate(377.845 53.9166) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip43_532_2318">
<rect width="13" height="13" fill="white" transform="translate(52.1694 52.8553) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip44_532_2318">
<rect width="13" height="13" fill="white" transform="translate(77.1121 54.5486) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip45_532_2318">
<rect width="13" height="13" fill="white" transform="translate(102.055 56.2419) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip46_532_2318">
<rect width="13" height="13" fill="white" transform="translate(126.997 57.9352) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip47_532_2318">
<rect width="13" height="13" fill="white" transform="translate(151.94 59.6286) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip48_532_2318">
<rect width="13" height="13" fill="white" transform="translate(176.882 61.3219) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip49_532_2318">
<rect width="13" height="13" fill="white" transform="translate(201.825 63.0152) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip50_532_2318">
<rect width="13" height="13" fill="white" transform="translate(226.768 64.7085) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip51_532_2318">
<rect width="13" height="13" fill="white" transform="translate(251.71 66.4018) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip52_532_2318">
<rect width="13" height="13" fill="white" transform="translate(276.653 68.0951) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip53_532_2318">
<rect width="13" height="13" fill="white" transform="translate(301.595 69.7884) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip54_532_2318">
<rect width="13" height="13" fill="white" transform="translate(326.538 71.4817) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip55_532_2318">
<rect width="13" height="13" fill="white" transform="translate(351.48 73.175) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip56_532_2318">
<rect width="13" height="13" fill="white" transform="translate(376.423 74.8683) rotate(3.88375)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 129 KiB

View File

@@ -10,10 +10,8 @@
"pagedown": "menu::SelectLast",
"ctrl-n": "menu::SelectNext",
"tab": "menu::SelectNext",
"down": "menu::SelectNext",
"ctrl-p": "menu::SelectPrevious",
"shift-tab": "menu::SelectPrevious",
"up": "menu::SelectPrevious",
"enter": "menu::Confirm",
"ctrl-enter": "menu::SecondaryConfirm",
"ctrl-escape": "menu::Cancel",

View File

@@ -56,9 +56,6 @@
"[ shift-b": ["pane::ActivateItem", 0],
"] space": "vim::InsertEmptyLineBelow",
"[ space": "vim::InsertEmptyLineAbove",
"[ e": "editor::MoveLineUp",
"] e": "editor::MoveLineDown",
// Word motions
"w": "vim::NextWordStart",
"e": "vim::NextWordEnd",

View File

@@ -400,13 +400,6 @@
// 3. Never show the minimap:
// "never" (default)
"show": "never",
// Where to show the minimap in the editor.
// This setting can take two values:
// 1. Show the minimap on the focused editor only:
// "active_editor" (default)
// 2. Show the minimap on all open editors:
// "all_editors"
"display_in": "active_editor",
// When to show the minimap thumb.
// This setting can take two values:
// 1. Show the minimap thumb if the mouse is over the minimap:
@@ -1045,19 +1038,6 @@
// Automatically update Zed. This setting may be ignored on Linux if
// installed through a package manager.
"auto_update": true,
// How to render LSP `textDocument/documentColor` colors in the editor.
//
// Possible values:
//
// 1. Do not query and render document colors.
// "lsp_document_colors": "none",
// 2. Render document colors as inlay hints near the color text (default).
// "lsp_document_colors": "inlay",
// 3. Draw a border around the color text.
// "lsp_document_colors": "border",
// 4. Draw a background behind the color text..
// "lsp_document_colors": "background",
"lsp_document_colors": "inlay",
// Diagnostics configuration.
"diagnostics": {
// Whether to show the project diagnostics button in the status bar.

View File

@@ -750,7 +750,7 @@ struct EditingMessageState {
editor: Entity<Editor>,
context_strip: Entity<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
last_estimated_token_count: Option<u64>,
last_estimated_token_count: Option<usize>,
_subscriptions: [Subscription; 2],
_update_token_count_task: Option<Task<()>>,
}
@@ -857,7 +857,7 @@ impl ActiveThread {
}
/// Returns the editing message id and the estimated token count in the content
pub fn editing_message_id(&self) -> Option<(MessageId, u64)> {
pub fn editing_message_id(&self) -> Option<(MessageId, usize)> {
self.editing_message
.as_ref()
.map(|(id, state)| (*id, state.last_estimated_token_count.unwrap_or(0)))

View File

@@ -520,15 +520,10 @@ impl AgentPanel {
});
let message_editor_subscription =
cx.subscribe(&message_editor, |this, _, event, cx| match event {
cx.subscribe(&message_editor, |_, _, event, cx| match event {
MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
cx.notify();
}
MessageEditorEvent::ScrollThreadToBottom => {
this.thread.update(cx, |thread, cx| {
thread.scroll_to_bottom(cx);
});
}
});
let thread_id = thread.read(cx).id().clone();
@@ -808,15 +803,10 @@ impl AgentPanel {
self.message_editor.focus_handle(cx).focus(window);
let message_editor_subscription =
cx.subscribe(&self.message_editor, |this, _, event, cx| match event {
cx.subscribe(&self.message_editor, |_, _, event, cx| match event {
MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
cx.notify();
}
MessageEditorEvent::ScrollThreadToBottom => {
this.thread.update(cx, |thread, cx| {
thread.scroll_to_bottom(cx);
});
}
});
self._active_thread_subscriptions = vec![
@@ -1028,15 +1018,10 @@ impl AgentPanel {
self.message_editor.focus_handle(cx).focus(window);
let message_editor_subscription =
cx.subscribe(&self.message_editor, |this, _, event, cx| match event {
cx.subscribe(&self.message_editor, |_, _, event, cx| match event {
MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
cx.notify();
}
MessageEditorEvent::ScrollThreadToBottom => {
this.thread.update(cx, |thread, cx| {
thread.scroll_to_bottom(cx);
});
}
});
self._active_thread_subscriptions = vec![

View File

@@ -214,7 +214,6 @@ fn search(
&entry_candidates,
&query,
false,
true,
100,
&Arc::new(AtomicBool::default()),
executor,

View File

@@ -307,7 +307,6 @@ pub(crate) fn search_symbols(
&visible_match_candidates,
&query,
false,
true,
MAX_MATCHES,
&cancellation_flag,
cx.background_executor().clone(),
@@ -316,7 +315,6 @@ pub(crate) fn search_symbols(
&external_match_candidates,
&query,
false,
true,
MAX_MATCHES - visible_matches.len().min(MAX_MATCHES),
&cancellation_flag,
cx.background_executor().clone(),

View File

@@ -342,7 +342,6 @@ pub(crate) fn search_threads(
&candidates,
&query,
false,
true,
100,
&cancellation_flag,
executor,

View File

@@ -76,7 +76,7 @@ pub struct MessageEditor {
profile_selector: Entity<ProfileSelector>,
edits_expanded: bool,
editor_is_expanded: bool,
last_estimated_token_count: Option<u64>,
last_estimated_token_count: Option<usize>,
update_token_count_task: Option<Task<()>>,
_subscriptions: Vec<Subscription>,
}
@@ -301,7 +301,6 @@ impl MessageEditor {
self.set_editor_is_expanded(false, cx);
self.send_to_model(window, cx);
cx.emit(MessageEditorEvent::ScrollThreadToBottom);
cx.notify();
}
@@ -907,7 +906,7 @@ impl MessageEditor {
)
}
fn render_edits_bar(
fn render_changed_buffers(
&self,
changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
window: &mut Window,
@@ -1335,7 +1334,7 @@ impl MessageEditor {
)
}
pub fn last_estimated_token_count(&self) -> Option<u64> {
pub fn last_estimated_token_count(&self) -> Option<usize> {
self.last_estimated_token_count
}
@@ -1511,7 +1510,6 @@ impl EventEmitter<MessageEditorEvent> for MessageEditor {}
pub enum MessageEditorEvent {
EstimatedTokenCount,
Changed,
ScrollThreadToBottom,
}
impl Focusable for MessageEditor {
@@ -1539,7 +1537,7 @@ impl Render for MessageEditor {
v_flex()
.size_full()
.when(changed_buffers.len() > 0, |parent| {
parent.child(self.render_edits_bar(&changed_buffers, window, cx))
parent.child(self.render_changed_buffers(&changed_buffers, window, cx))
})
.child(self.render_editor(window, cx))
.children({

View File

@@ -0,0 +1,3 @@
[The following is an auto-generated notification; do not reply]
These files have changed since the last read:

View File

@@ -1,3 +1,4 @@
use std::fmt::Write as _;
use std::io::Write;
use std::ops::Range;
use std::sync::Arc;
@@ -7,7 +8,7 @@ use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};
use chrono::{DateTime, Utc};
use collections::{HashMap, HashSet};
use collections::HashMap;
use editor::display_map::CreaseMetadata;
use feature_flags::{self, FeatureFlagAppExt};
use futures::future::Shared;
@@ -36,7 +37,6 @@ use settings::Settings;
use thiserror::Error;
use ui::Window;
use util::{ResultExt as _, post_inc};
use uuid::Uuid;
use zed_llm_client::{CompletionIntent, CompletionRequestStatus};
@@ -144,10 +144,6 @@ impl Message {
}
}
pub fn push_redacted_thinking(&mut self, data: String) {
self.segments.push(MessageSegment::RedactedThinking(data));
}
pub fn push_text(&mut self, text: &str) {
if let Some(MessageSegment::Text(segment)) = self.segments.last_mut() {
segment.push_str(text);
@@ -186,7 +182,7 @@ pub enum MessageSegment {
text: String,
signature: Option<String>,
},
RedactedThinking(String),
RedactedThinking(Vec<u8>),
}
impl MessageSegment {
@@ -276,8 +272,8 @@ impl DetailedSummaryState {
#[derive(Default, Debug)]
pub struct TotalTokenUsage {
pub total: u64,
pub max: u64,
pub total: usize,
pub max: usize,
}
impl TotalTokenUsage {
@@ -303,7 +299,7 @@ impl TotalTokenUsage {
}
}
pub fn add(&self, tokens: u64) -> TotalTokenUsage {
pub fn add(&self, tokens: usize) -> TotalTokenUsage {
TotalTokenUsage {
total: self.total + tokens,
max: self.max,
@@ -400,7 +396,7 @@ pub struct ExceededWindowError {
/// Model used when last message exceeded context window
model_id: LanguageModelId,
/// Token count including last message
token_count: u64,
token_count: usize,
}
impl Thread {
@@ -942,13 +938,14 @@ impl Thread {
model: Arc<dyn LanguageModel>,
) -> Vec<LanguageModelRequestTool> {
if model.supports_tools() {
resolve_tool_name_conflicts(self.profile.enabled_tools(cx).as_slice())
self.profile
.enabled_tools(cx)
.into_iter()
.filter_map(|(name, tool)| {
.filter_map(|tool| {
// Skip tools that cannot be supported
let input_schema = tool.input_schema(model.tool_input_format()).ok()?;
Some(LanguageModelRequestTool {
name,
name: tool.name(),
description: tool.description(),
input_schema,
})
@@ -1392,6 +1389,8 @@ impl Thread {
request.messages[message_ix_to_cache].cache = true;
}
self.attach_tracked_files_state(&mut request.messages, cx);
request.tools = available_tools;
request.mode = if model.supports_max_mode() {
Some(self.completion_mode.into())
@@ -1454,6 +1453,60 @@ impl Thread {
request
}
fn attach_tracked_files_state(
&self,
messages: &mut Vec<LanguageModelRequestMessage>,
cx: &App,
) {
let mut stale_files = String::new();
let action_log = self.action_log.read(cx);
for stale_file in action_log.stale_buffers(cx) {
if let Some(file) = stale_file.read(cx).file() {
writeln!(&mut stale_files, "- {}", file.path().display()).ok();
}
}
if stale_files.is_empty() {
return;
}
// NOTE: Changes to this prompt require a symmetric update in the LLM Worker
const STALE_FILES_HEADER: &str = include_str!("./prompts/stale_files_prompt_header.txt");
let content = MessageContent::Text(
format!("{STALE_FILES_HEADER}{stale_files}").replace("\r\n", "\n"),
);
// Insert our message before the last Assistant message.
// Inserting it to the tail distracts the agent too much
let insert_position = messages
.iter()
.enumerate()
.rfind(|(_, message)| message.role == Role::Assistant)
.map_or(messages.len(), |(i, _)| i);
let request_message = LanguageModelRequestMessage {
role: Role::User,
content: vec![content],
cache: false,
};
messages.insert(insert_position, request_message);
// It makes no sense to cache messages after this one because
// the cache is invalidated when this message is gone.
// Move the cache marker before this message.
let has_cached_messages_after = messages
.iter()
.skip(insert_position + 1)
.any(|message| message.cache);
if has_cached_messages_after {
messages[insert_position - 1].cache = true;
}
}
pub fn stream_completion(
&mut self,
request: LanguageModelRequest,
@@ -1606,25 +1659,6 @@ impl Thread {
};
}
}
LanguageModelCompletionEvent::RedactedThinking {
data
} => {
thread.received_chunk();
if let Some(last_message) = thread.messages.last_mut() {
if last_message.role == Role::Assistant
&& !thread.tool_use.has_tool_results(last_message.id)
{
last_message.push_redacted_thinking(data);
} else {
request_assistant_message_id =
Some(thread.insert_assistant_message(
vec![MessageSegment::RedactedThinking(data)],
cx,
));
};
}
}
LanguageModelCompletionEvent::ToolUse(tool_use) => {
let last_assistant_message_id = request_assistant_message_id
.unwrap_or_else(|| {
@@ -2735,7 +2769,7 @@ impl Thread {
.unwrap_or_default();
TotalTokenUsage {
total: token_usage.total_tokens(),
total: token_usage.total_tokens() as usize,
max,
}
}
@@ -2757,7 +2791,7 @@ impl Thread {
let total = self
.token_usage_at_last_message()
.unwrap_or_default()
.total_tokens();
.total_tokens() as usize;
Some(TotalTokenUsage { total, max })
}
@@ -2866,85 +2900,6 @@ struct PendingCompletion {
_task: Task<()>,
}
/// Resolves tool name conflicts by ensuring all tool names are unique.
///
/// When multiple tools have the same name, this function applies the following rules:
/// 1. Native tools always keep their original name
/// 2. Context server tools get prefixed with their server ID and an underscore
/// 3. All tool names are truncated to MAX_TOOL_NAME_LENGTH (64 characters)
/// 4. If conflicts still exist after prefixing, the conflicting tools are filtered out
///
/// Note: This function assumes that built-in tools occur before MCP tools in the tools list.
fn resolve_tool_name_conflicts(tools: &[Arc<dyn Tool>]) -> Vec<(String, Arc<dyn Tool>)> {
fn resolve_tool_name(tool: &Arc<dyn Tool>) -> String {
let mut tool_name = tool.name();
tool_name.truncate(MAX_TOOL_NAME_LENGTH);
tool_name
}
const MAX_TOOL_NAME_LENGTH: usize = 64;
let mut duplicated_tool_names = HashSet::default();
let mut seen_tool_names = HashSet::default();
for tool in tools {
let tool_name = resolve_tool_name(tool);
if seen_tool_names.contains(&tool_name) {
debug_assert!(
tool.source() != assistant_tool::ToolSource::Native,
"There are two built-in tools with the same name: {}",
tool_name
);
duplicated_tool_names.insert(tool_name);
} else {
seen_tool_names.insert(tool_name);
}
}
if duplicated_tool_names.is_empty() {
return tools
.into_iter()
.map(|tool| (resolve_tool_name(tool), tool.clone()))
.collect();
}
tools
.into_iter()
.filter_map(|tool| {
let mut tool_name = resolve_tool_name(tool);
if !duplicated_tool_names.contains(&tool_name) {
return Some((tool_name, tool.clone()));
}
match tool.source() {
assistant_tool::ToolSource::Native => {
// Built-in tools always keep their original name
Some((tool_name, tool.clone()))
}
assistant_tool::ToolSource::ContextServer { id } => {
// Context server tools are prefixed with the context server ID, and truncated if necessary
tool_name.insert(0, '_');
if tool_name.len() + id.len() > MAX_TOOL_NAME_LENGTH {
let len = MAX_TOOL_NAME_LENGTH - tool_name.len();
let mut id = id.to_string();
id.truncate(len);
tool_name.insert_str(0, &id);
} else {
tool_name.insert_str(0, &id);
}
tool_name.truncate(MAX_TOOL_NAME_LENGTH);
if seen_tool_names.contains(&tool_name) {
log::error!("Cannot resolve tool name conflict for tool {}", tool.name());
None
} else {
Some((tool_name, tool.clone()))
}
}
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -2960,7 +2915,6 @@ mod tests {
use settings::{Settings, SettingsStore};
use std::sync::Arc;
use theme::ThemeSettings;
use ui::IconName;
use util::path;
use workspace::Workspace;
@@ -3275,6 +3229,106 @@ fn main() {{
);
}
#[gpui::test]
async fn test_stale_buffer_notification(cx: &mut TestAppContext) {
init_test_settings(cx);
let project = create_test_project(
cx,
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
)
.await;
let (_workspace, _thread_store, thread, context_store, model) =
setup_test_environment(cx, project.clone()).await;
// Open buffer and add it to context
let buffer = add_file_to_context(&project, &context_store, "test/code.rs", cx)
.await
.unwrap();
let context =
context_store.read_with(cx, |store, _| store.context().next().cloned().unwrap());
let loaded_context = cx
.update(|cx| load_context(vec![context], &project, &None, cx))
.await;
// Insert user message with the buffer as context
thread.update(cx, |thread, cx| {
thread.insert_user_message("Explain this code", loaded_context, None, Vec::new(), cx)
});
// Create a request and check that it doesn't have a stale buffer warning yet
let initial_request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
// Make sure we don't have a stale file warning yet
let has_stale_warning = initial_request.messages.iter().any(|msg| {
msg.string_contents()
.contains("These files changed since last read:")
});
assert!(
!has_stale_warning,
"Should not have stale buffer warning before buffer is modified"
);
// Modify the buffer
buffer.update(cx, |buffer, cx| {
// Find a position at the end of line 1
buffer.edit(
[(1..1, "\n println!(\"Added a new line\");\n")],
None,
cx,
);
});
// Insert another user message without context
thread.update(cx, |thread, cx| {
thread.insert_user_message(
"What does the code do now?",
ContextLoadResult::default(),
None,
Vec::new(),
cx,
)
});
// Create a new request and check for the stale buffer warning
let new_request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
// We should have a stale file warning as the last message
let last_message = new_request
.messages
.last()
.expect("Request should have messages");
// The last message should be the stale buffer notification
assert_eq!(last_message.role, Role::User);
// Check the exact content of the message
let expected_content = "[The following is an auto-generated notification; do not reply]
These files have changed since the last read:
- code.rs
";
assert_eq!(
last_message.string_contents(),
expected_content,
"Last message should be exactly the stale buffer notification"
);
// The message before the notification should be cached
let index = new_request.messages.len() - 2;
let previous_message = new_request.messages.get(index).unwrap();
assert!(
previous_message.cache,
"Message before the stale buffer notification should be cached"
);
}
#[gpui::test]
async fn test_storing_profile_setting_per_thread(cx: &mut TestAppContext) {
init_test_settings(cx);
@@ -3592,148 +3646,6 @@ fn main() {{
});
}
#[gpui::test]
fn test_resolve_tool_name_conflicts() {
use assistant_tool::{Tool, ToolSource};
assert_resolve_tool_name_conflicts(
vec![
TestTool::new("tool1", ToolSource::Native),
TestTool::new("tool2", ToolSource::Native),
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-1".into() }),
],
vec!["tool1", "tool2", "tool3"],
);
assert_resolve_tool_name_conflicts(
vec![
TestTool::new("tool1", ToolSource::Native),
TestTool::new("tool2", ToolSource::Native),
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-1".into() }),
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-2".into() }),
],
vec!["tool1", "tool2", "mcp-1_tool3", "mcp-2_tool3"],
);
assert_resolve_tool_name_conflicts(
vec![
TestTool::new("tool1", ToolSource::Native),
TestTool::new("tool2", ToolSource::Native),
TestTool::new("tool3", ToolSource::Native),
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-1".into() }),
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-2".into() }),
],
vec!["tool1", "tool2", "tool3", "mcp-1_tool3", "mcp-2_tool3"],
);
// Test that tool with very long name is always truncated
assert_resolve_tool_name_conflicts(
vec![TestTool::new(
"tool-with-more-then-64-characters-blah-blah-blah-blah-blah-blah-blah-blah",
ToolSource::Native,
)],
vec!["tool-with-more-then-64-characters-blah-blah-blah-blah-blah-blah-"],
);
// Test deduplication of tools with very long names, in this case the mcp server name should be truncated
assert_resolve_tool_name_conflicts(
vec![
TestTool::new("tool-with-very-very-very-long-name", ToolSource::Native),
TestTool::new(
"tool-with-very-very-very-long-name",
ToolSource::ContextServer {
id: "mcp-with-very-very-very-long-name".into(),
},
),
],
vec![
"tool-with-very-very-very-long-name",
"mcp-with-very-very-very-long-_tool-with-very-very-very-long-name",
],
);
fn assert_resolve_tool_name_conflicts(
tools: Vec<TestTool>,
expected: Vec<impl Into<String>>,
) {
let tools: Vec<Arc<dyn Tool>> = tools
.into_iter()
.map(|t| Arc::new(t) as Arc<dyn Tool>)
.collect();
let tools = resolve_tool_name_conflicts(&tools);
assert_eq!(tools.len(), expected.len());
for (i, expected_name) in expected.into_iter().enumerate() {
let expected_name = expected_name.into();
let actual_name = &tools[i].0;
assert_eq!(
actual_name, &expected_name,
"Expected '{}' got '{}' at index {}",
expected_name, actual_name, i
);
}
}
struct TestTool {
name: String,
source: ToolSource,
}
impl TestTool {
fn new(name: impl Into<String>, source: ToolSource) -> Self {
Self {
name: name.into(),
source,
}
}
}
impl Tool for TestTool {
fn name(&self) -> String {
self.name.clone()
}
fn icon(&self) -> IconName {
IconName::Ai
}
fn may_perform_edits(&self) -> bool {
false
}
fn needs_confirmation(&self, _input: &serde_json::Value, _cx: &App) -> bool {
true
}
fn source(&self) -> ToolSource {
self.source.clone()
}
fn description(&self) -> String {
"Test tool".to_string()
}
fn ui_text(&self, _input: &serde_json::Value) -> String {
"Test tool".to_string()
}
fn run(
self: Arc<Self>,
_input: serde_json::Value,
_request: Arc<LanguageModelRequest>,
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_model: Arc<dyn LanguageModel>,
_window: Option<AnyWindowHandle>,
_cx: &mut App,
) -> assistant_tool::ToolResult {
assistant_tool::ToolResult {
output: Task::ready(Err(anyhow::anyhow!("No content"))),
card: None,
}
}
}
}
fn test_summarize_error(
model: &Arc<dyn LanguageModel>,
thread: &Entity<Thread>,

View File

@@ -224,7 +224,6 @@ impl ThreadHistory {
&candidates,
&query,
false,
true,
MAX_MATCHES,
&Default::default(),
executor,

View File

@@ -729,7 +729,7 @@ pub enum SerializedMessageSegment {
signature: Option<String>,
},
RedactedThinking {
data: String,
data: Vec<u8>,
},
}

View File

@@ -427,7 +427,7 @@ impl ToolUseState {
// Protect from overly large output
let tool_output_limit = configured_model
.map(|model| model.model.max_token_count() as usize * BYTES_PER_TOKEN_ESTIMATE)
.map(|model| model.model.max_token_count() * BYTES_PER_TOKEN_ESTIMATE)
.unwrap_or(usize::MAX);
let content = match tool_result {

View File

@@ -386,9 +386,7 @@ impl AgentSettingsContent {
_ => None,
};
settings.provider = Some(AgentProviderContentV1::LmStudio {
default_model: Some(lmstudio::Model::new(
&model, None, None, false, false,
)),
default_model: Some(lmstudio::Model::new(&model, None, None, false)),
api_url,
});
}
@@ -734,7 +732,6 @@ impl JsonSchema for LanguageModelProviderSetting {
"deepseek".into(),
"openrouter".into(),
"mistral".into(),
"vercel".into(),
]),
..Default::default()
}

View File

@@ -15,7 +15,7 @@ pub const ANTHROPIC_API_URL: &str = "https://api.anthropic.com";
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct AnthropicModelCacheConfiguration {
pub min_total_token: u64,
pub min_total_token: usize,
pub should_speculate: bool,
pub max_cache_anchors: usize,
}
@@ -68,14 +68,14 @@ pub enum Model {
#[serde(rename = "custom")]
Custom {
name: String,
max_tokens: u64,
max_tokens: usize,
/// The name displayed in the UI, such as in the assistant panel model dropdown menu.
display_name: Option<String>,
/// Override this model with a different Anthropic model for tool calls.
tool_override: Option<String>,
/// Indicates whether this custom model supports caching.
cache_configuration: Option<AnthropicModelCacheConfiguration>,
max_output_tokens: Option<u64>,
max_output_tokens: Option<u32>,
default_temperature: Option<f32>,
#[serde(default)]
extra_beta_headers: Vec<String>,
@@ -211,7 +211,7 @@ impl Model {
}
}
pub fn max_token_count(&self) -> u64 {
pub fn max_token_count(&self) -> usize {
match self {
Self::ClaudeOpus4
| Self::ClaudeOpus4Thinking
@@ -228,7 +228,7 @@ impl Model {
}
}
pub fn max_output_tokens(&self) -> u64 {
pub fn max_output_tokens(&self) -> u32 {
match self {
Self::ClaudeOpus4
| Self::ClaudeOpus4Thinking
@@ -693,7 +693,7 @@ pub enum StringOrContents {
#[derive(Debug, Serialize, Deserialize)]
pub struct Request {
pub model: String,
pub max_tokens: u64,
pub max_tokens: u32,
pub messages: Vec<Message>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<Tool>,
@@ -730,13 +730,13 @@ pub struct Metadata {
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Usage {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input_tokens: Option<u64>,
pub input_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_tokens: Option<u64>,
pub output_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache_creation_input_tokens: Option<u64>,
pub cache_creation_input_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache_read_input_tokens: Option<u64>,
pub cache_read_input_tokens: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -846,7 +846,7 @@ impl ApiError {
matches!(self.error_type.as_str(), "rate_limit_error")
}
pub fn match_window_exceeded(&self) -> Option<u64> {
pub fn match_window_exceeded(&self) -> Option<usize> {
let Some(ApiErrorCode::InvalidRequestError) = self.code() else {
return None;
};
@@ -855,12 +855,12 @@ impl ApiError {
}
}
pub fn parse_prompt_too_long(message: &str) -> Option<u64> {
pub fn parse_prompt_too_long(message: &str) -> Option<usize> {
message
.strip_prefix("prompt is too long: ")?
.split_once(" tokens")?
.0
.parse()
.parse::<usize>()
.ok()
}

View File

@@ -13,9 +13,11 @@ use gpui::{AsyncApp, BackgroundExecutor, Task};
#[cfg(unix)]
use smol::fs;
#[cfg(unix)]
use smol::net::unix::UnixListener;
use smol::{fs::unix::PermissionsExt as _, net::unix::UnixListener};
#[cfg(unix)]
use util::{ResultExt as _, fs::make_file_executable, get_shell_safe_zed_path};
use util::ResultExt as _;
#[cfg(unix)]
use util::get_shell_safe_zed_path;
#[derive(PartialEq, Eq)]
pub enum AskPassResult {
@@ -120,7 +122,7 @@ impl AskPassSession {
shebang = "#!/bin/sh",
);
fs::write(&askpass_script_path, askpass_script).await?;
make_file_executable(&askpass_script_path).await?;
fs::set_permissions(&askpass_script_path, std::fs::Permissions::from_mode(0o755)).await?;
Ok(Self {
script_path: askpass_script_path,

View File

@@ -678,7 +678,7 @@ pub struct AssistantContext {
summary_task: Task<Option<()>>,
completion_count: usize,
pending_completions: Vec<PendingCompletion>,
token_count: Option<u64>,
token_count: Option<usize>,
pending_token_count: Task<Option<()>>,
pending_save: Task<Result<()>>,
pending_cache_warming_task: Task<Option<()>>,
@@ -1250,7 +1250,7 @@ impl AssistantContext {
}
}
pub fn token_count(&self) -> Option<u64> {
pub fn token_count(&self) -> Option<usize> {
self.token_count
}
@@ -2110,7 +2110,6 @@ impl AssistantContext {
);
}
}
LanguageModelCompletionEvent::RedactedThinking { .. } => {},
LanguageModelCompletionEvent::Text(mut chunk) => {
if let Some(start) = thought_process_stack.pop() {
let end = buffer.anchor_before(message_old_end_offset);

View File

@@ -3121,12 +3121,12 @@ fn invoked_slash_command_fold_placeholder(
enum TokenState {
NoTokensLeft {
max_token_count: u64,
token_count: u64,
max_token_count: usize,
token_count: usize,
},
HasMoreTokens {
max_token_count: u64,
token_count: u64,
max_token_count: usize,
token_count: usize,
over_warn_threshold: bool,
},
}
@@ -3139,7 +3139,9 @@ fn token_state(context: &Entity<AssistantContext>, cx: &App) -> Option<TokenStat
.model;
let token_count = context.read(cx).token_count()?;
let max_token_count = model.max_token_count();
let token_state = if max_token_count.saturating_sub(token_count) == 0 {
let remaining_tokens = max_token_count as isize - token_count as isize;
let token_state = if remaining_tokens <= 0 {
TokenState::NoTokensLeft {
max_token_count,
token_count,
@@ -3180,7 +3182,7 @@ fn size_for_image(data: &RenderImage, max_size: Size<Pixels>) -> Size<Pixels> {
}
}
pub fn humanize_token_count(count: u64) -> String {
pub fn humanize_token_count(count: usize) -> String {
match count {
0..=999 => count.to_string(),
1000..=9999 => {

View File

@@ -745,7 +745,6 @@ impl ContextStore {
&candidates,
&query,
false,
true,
100,
&Default::default(),
executor,

View File

@@ -310,7 +310,6 @@ impl ModelMatcher {
&self.candidates,
&query,
false,
true,
100,
&Default::default(),
self.bg_executor.clone(),
@@ -665,7 +664,7 @@ mod tests {
format!("{}/{}", self.provider_id.0, self.name.0)
}
fn max_token_count(&self) -> u64 {
fn max_token_count(&self) -> usize {
1000
}
@@ -673,7 +672,7 @@ mod tests {
&self,
_: LanguageModelRequest,
_: &App,
) -> BoxFuture<'static, http_client::Result<u64>> {
) -> BoxFuture<'static, http_client::Result<usize>> {
unimplemented!()
}

View File

@@ -62,7 +62,6 @@ impl SlashCommandCompletionProvider {
&candidates,
&command_name,
true,
true,
usize::MAX,
&Default::default(),
cx.background_executor().clone(),

View File

@@ -147,7 +147,6 @@ impl SlashCommand for DiagnosticsSlashCommand {
&Options::match_candidates_for_args(),
&query,
false,
true,
10,
&cancellation_flag,
executor,

View File

@@ -261,7 +261,6 @@ fn tab_items_for_queries(
&match_candidates,
query,
true,
true,
usize::MAX,
&cancel,
background_executor.clone(),

View File

@@ -75,7 +75,7 @@ impl FromStr for EditFormat {
impl EditFormat {
/// Return an optimal edit format for the language model
pub fn from_model(model: Arc<dyn LanguageModel>) -> anyhow::Result<Self> {
if model.provider_id().0 == "google" || model.id().0.to_lowercase().contains("gemini") {
if model.provider_id().0 == "google" {
Ok(EditFormat::DiffFenced)
} else {
Ok(EditFormat::XmlTags)

View File

@@ -82,10 +82,7 @@ fn view_release_notes_locally(
.update_in(cx, |workspace, window, cx| {
let project = workspace.project().clone();
let buffer = project.update(cx, |project, cx| {
let buffer = project.create_local_buffer("", markdown, cx);
project
.mark_buffer_as_non_searchable(buffer.read(cx).remote_id(), cx);
buffer
project.create_local_buffer("", markdown, cx)
});
buffer.update(cx, |buffer, cx| {
buffer.edit([(0..0, body.release_notes)], None, cx)

View File

@@ -152,7 +152,7 @@ pub enum Thinking {
#[derive(Debug)]
pub struct Request {
pub model: String,
pub max_tokens: u64,
pub max_tokens: u32,
pub messages: Vec<BedrockMessage>,
pub tools: Option<BedrockToolConfig>,
pub thinking: Option<Thinking>,

View File

@@ -99,10 +99,10 @@ pub enum Model {
#[serde(rename = "custom")]
Custom {
name: String,
max_tokens: u64,
max_tokens: usize,
/// The name displayed in the UI, such as in the assistant panel model dropdown menu.
display_name: Option<String>,
max_output_tokens: Option<u64>,
max_output_tokens: Option<u32>,
default_temperature: Option<f32>,
},
}
@@ -309,7 +309,7 @@ impl Model {
}
}
pub fn max_token_count(&self) -> u64 {
pub fn max_token_count(&self) -> usize {
match self {
Self::Claude3_5SonnetV2
| Self::Claude3Opus
@@ -328,7 +328,7 @@ impl Model {
}
}
pub fn max_output_tokens(&self) -> u64 {
pub fn max_output_tokens(&self) -> u32 {
match self {
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3_5Haiku => 4_096,
Self::Claude3_7Sonnet

View File

@@ -1028,11 +1028,7 @@ impl BufferDiff {
let (base_text_changed, mut changed_range) =
match (state.base_text_exists, new_state.base_text_exists) {
(false, false) => (true, None),
(true, true)
if state.base_text.remote_id() == new_state.base_text.remote_id()
&& state.base_text.syntax_update_count()
== new_state.base_text.syntax_update_count() =>
{
(true, true) if state.base_text.remote_id() == new_state.base_text.remote_id() => {
(false, new_state.compare(&state, buffer))
}
_ => (true, Some(text::Anchor::MIN..text::Anchor::MAX)),

View File

@@ -13,7 +13,6 @@ pub enum CliRequest {
Open {
paths: Vec<String>,
urls: Vec<String>,
diff_paths: Vec<[String; 2]>,
wait: bool,
open_new_workspace: Option<bool>,
env: Option<HashMap<String, String>>,

View File

@@ -89,9 +89,6 @@ struct Args {
/// Will attempt to give the correct command to run
#[arg(long)]
system_specs: bool,
/// Pairs of file paths to diff. Can be specified multiple times.
#[arg(long, action = clap::ArgAction::Append, num_args = 2, value_names = ["OLD_PATH", "NEW_PATH"])]
diff: Vec<String>,
/// Uninstall Zed from user system
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
@@ -235,17 +232,9 @@ fn main() -> Result<()> {
let exit_status = Arc::new(Mutex::new(None));
let mut paths = vec![];
let mut urls = vec![];
let mut diff_paths = vec![];
let mut stdin_tmp_file: Option<fs::File> = None;
let mut anonymous_fd_tmp_files = vec![];
for path in args.diff.chunks(2) {
diff_paths.push([
parse_path_with_position(&path[0])?,
parse_path_with_position(&path[1])?,
]);
}
for path in args.paths_with_position.iter() {
if path.starts_with("zed://")
|| path.starts_with("http://")
@@ -284,7 +273,6 @@ fn main() -> Result<()> {
tx.send(CliRequest::Open {
paths,
urls,
diff_paths,
wait: args.wait,
open_new_workspace,
env,

View File

@@ -28,6 +28,9 @@ feature_flags.workspace = true
futures.workspace = true
gpui.workspace = true
gpui_tokio.workspace = true
# Don't update `hickory-resolver`, it has a bug that causes it to not resolve DNS queries correctly.
# See https://github.com/hickory-dns/hickory-dns/issues/3048
hickory-resolver = { version = "0.24", features = ["tokio-runtime"] }
http_client.workspace = true
http_client_tls.workspace = true
httparse = "1.10"

View File

@@ -3,20 +3,30 @@
mod http_proxy;
mod socks_proxy;
use std::sync::LazyLock;
use anyhow::{Context as _, Result};
use hickory_resolver::{
AsyncResolver, TokioAsyncResolver,
config::LookupIpStrategy,
name_server::{GenericConnector, TokioRuntimeProvider},
system_conf,
};
use http_client::Url;
use http_proxy::{HttpProxyType, connect_http_proxy_stream, parse_http_proxy};
use socks_proxy::{SocksVersion, connect_socks_proxy_stream, parse_socks_proxy};
use tokio_socks::{IntoTargetAddr, TargetAddr};
use util::ResultExt;
pub(crate) async fn connect_proxy_stream(
proxy: &Url,
rpc_host: (&str, u16),
) -> Result<Box<dyn AsyncReadWrite>> {
let Some(((proxy_domain, proxy_port), proxy_type)) = parse_proxy_type(proxy) else {
let Some(((proxy_domain, proxy_port), proxy_type)) = parse_proxy_type(proxy).await else {
// If parsing the proxy URL fails, we must avoid falling back to an insecure connection.
// SOCKS proxies are often used in contexts where security and privacy are critical,
// so any fallback could expose users to significant risks.
anyhow::bail!("Parsing proxy url failed");
anyhow::bail!("Parsing proxy url type failed");
};
// Connect to proxy and wrap protocol later
@@ -39,10 +49,8 @@ enum ProxyType<'t> {
HttpProxy(HttpProxyType<'t>),
}
fn parse_proxy_type(proxy: &Url) -> Option<((String, u16), ProxyType<'_>)> {
async fn parse_proxy_type(proxy: &Url) -> Option<((String, u16), ProxyType<'_>)> {
let scheme = proxy.scheme();
let host = proxy.host()?.to_string();
let port = proxy.port_or_known_default()?;
let proxy_type = match scheme {
scheme if scheme.starts_with("socks") => {
Some(ProxyType::SocksProxy(parse_socks_proxy(scheme, proxy)))
@@ -52,8 +60,38 @@ fn parse_proxy_type(proxy: &Url) -> Option<((String, u16), ProxyType<'_>)> {
}
_ => None,
}?;
let (ip, port) = {
let host = proxy.host()?.to_string();
let port = proxy.port_or_known_default()?;
resolve_proxy_url_if_needed((host, port)).await.log_err()?
};
Some(((host, port), proxy_type))
Some(((ip, port), proxy_type))
}
static SYSTEM_DNS_RESOLVER: LazyLock<AsyncResolver<GenericConnector<TokioRuntimeProvider>>> =
LazyLock::new(|| {
let (config, mut opts) = system_conf::read_system_conf().unwrap();
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
TokioAsyncResolver::tokio(config, opts)
});
async fn resolve_proxy_url_if_needed(proxy: (String, u16)) -> Result<(String, u16)> {
let proxy = proxy
.into_target_addr()
.context("Failed to parse proxy addr")?;
match proxy {
TargetAddr::Domain(domain, port) => {
let ip = SYSTEM_DNS_RESOLVER
.lookup_ip(domain.as_ref())
.await?
.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("No IP found for proxy domain {domain}"))?;
Ok((ip.to_string(), port))
}
TargetAddr::Ip(ip_addr) => Ok((ip_addr.ip().to_string(), ip_addr.port())),
}
}
pub(crate) trait AsyncReadWrite:

View File

@@ -1,5 +1,7 @@
//! socks proxy
use std::net::SocketAddr;
use anyhow::{Context as _, Result};
use http_client::Url;
use tokio::net::TcpStream;
@@ -8,6 +10,8 @@ use tokio_socks::{
tcp::{Socks4Stream, Socks5Stream},
};
use crate::proxy::SYSTEM_DNS_RESOLVER;
use super::AsyncReadWrite;
/// Identification to a Socks V4 Proxy
@@ -73,12 +77,14 @@ pub(super) async fn connect_socks_proxy_stream(
};
let rpc_host = match (rpc_host, local_dns) {
(TargetAddr::Domain(domain, port), true) => {
let ip_addr = tokio::net::lookup_host((domain.as_ref(), port))
let ip_addr = SYSTEM_DNS_RESOLVER
.lookup_ip(domain.as_ref())
.await
.with_context(|| format!("Failed to lookup domain {}", domain))?
.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("Failed to lookup domain {}", domain))?;
TargetAddr::Ip(ip_addr)
TargetAddr::Ip(SocketAddr::new(ip_addr, port))
}
(rpc_host, _) => rpc_host,
};

View File

@@ -374,28 +374,22 @@ impl Telemetry {
return None;
}
let mut project_types: HashSet<&str> = HashSet::new();
let mut project_types: HashSet<String> = HashSet::new();
for (path, _, _) in updated_entries_set.iter() {
let Some(file_name) = path.file_name().and_then(|f| f.to_str()) else {
continue;
};
let project_type = if file_name == "pnpm-lock.yaml" {
Some("pnpm")
if file_name == "pnpm-lock.yaml" {
project_types.insert("pnpm".to_string());
} else if file_name == "yarn.lock" {
Some("yarn")
project_types.insert("yarn".to_string());
} else if file_name == "package.json" {
Some("node")
project_types.insert("node".to_string());
} else if DOTNET_PROJECT_FILES_REGEX.is_match(file_name) {
Some("dotnet")
} else {
None
};
if let Some(project_type) = project_type {
project_types.insert(project_type);
};
project_types.insert("dotnet".to_string());
}
}
if !project_types.is_empty() {
@@ -404,9 +398,9 @@ impl Telemetry {
.insert(worktree_id);
}
let mut project_types: Vec<_> = project_types.into_iter().map(String::from).collect();
project_types.sort();
Some(project_types)
let mut project_names_vec: Vec<String> = project_types.into_iter().collect();
project_names_vec.sort();
Some(project_names_vec)
}
fn report_event(self: &Arc<Self>, event: Event) {

View File

@@ -465,7 +465,6 @@ CREATE TABLE extension_versions (
provides_slash_commands BOOLEAN NOT NULL DEFAULT FALSE,
provides_indexed_docs_providers BOOLEAN NOT NULL DEFAULT FALSE,
provides_snippets BOOLEAN NOT NULL DEFAULT FALSE,
provides_debug_adapters BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (extension_id, version)
);

View File

@@ -1,2 +0,0 @@
alter table extension_versions
add column provides_debug_adapters bool not null default false

View File

@@ -31,7 +31,7 @@ use crate::llm::{AGENT_EXTENDED_TRIAL_FEATURE_FLAG, DEFAULT_MAX_MONTHLY_SPEND};
use crate::rpc::{ResultExt as _, Server};
use crate::stripe_client::{
StripeCancellationDetailsReason, StripeClient, StripeCustomerId, StripeSubscription,
StripeSubscriptionId, UpdateCustomerParams,
StripeSubscriptionId,
};
use crate::{AppState, Error, Result};
use crate::{db::UserId, llm::db::LlmDatabase};
@@ -353,17 +353,7 @@ async fn create_billing_subscription(
}
let customer_id = if let Some(existing_customer) = &existing_billing_customer {
let customer_id = StripeCustomerId(existing_customer.stripe_customer_id.clone().into());
if let Some(email) = user.email_address.as_deref() {
stripe_billing
.client()
.update_customer(&customer_id, UpdateCustomerParams { email: Some(email) })
.await
// Update of email address is best-effort - continue checkout even if it fails
.context("error updating stripe customer email address")
.log_err();
}
customer_id
StripeCustomerId(existing_customer.stripe_customer_id.clone().into())
} else {
stripe_billing
.find_or_create_customer_by_email(user.email_address.as_deref())

View File

@@ -321,9 +321,6 @@ impl Database {
provides_snippets: ActiveValue::Set(
version.provides.contains(&ExtensionProvides::Snippets),
),
provides_debug_adapters: ActiveValue::Set(
version.provides.contains(&ExtensionProvides::DebugAdapters),
),
download_count: ActiveValue::NotSet,
}
}))
@@ -434,10 +431,6 @@ fn apply_provides_filter(
condition = condition.add(extension_version::Column::ProvidesSnippets.eq(true));
}
if provides_filter.contains(&ExtensionProvides::DebugAdapters) {
condition = condition.add(extension_version::Column::ProvidesDebugAdapters.eq(true));
}
condition
}

View File

@@ -27,7 +27,6 @@ pub struct Model {
pub provides_slash_commands: bool,
pub provides_indexed_docs_providers: bool,
pub provides_snippets: bool,
pub provides_debug_adapters: bool,
}
impl Model {
@@ -69,10 +68,6 @@ impl Model {
provides.insert(ExtensionProvides::Snippets);
}
if self.provides_debug_adapters {
provides.insert(ExtensionProvides::DebugAdapters);
}
provides
}
}

View File

@@ -323,7 +323,6 @@ impl Server {
.add_request_handler(forward_read_only_project_request::<proto::SynchronizeBuffers>)
.add_request_handler(forward_read_only_project_request::<proto::InlayHints>)
.add_request_handler(forward_read_only_project_request::<proto::ResolveInlayHint>)
.add_request_handler(forward_read_only_project_request::<proto::GetColorPresentation>)
.add_request_handler(forward_mutating_project_request::<proto::GetCodeLens>)
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)
.add_request_handler(forward_read_only_project_request::<proto::GitGetBranches>)

View File

@@ -50,10 +50,6 @@ impl StripeBilling {
}
}
pub fn client(&self) -> &Arc<dyn StripeClient> {
&self.client
}
pub async fn initialize(&self) -> Result<()> {
log::info!("StripeBilling: initializing");

View File

@@ -27,11 +27,6 @@ pub struct CreateCustomerParams<'a> {
pub email: Option<&'a str>,
}
#[derive(Debug)]
pub struct UpdateCustomerParams<'a> {
pub email: Option<&'a str>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display)]
pub struct StripeSubscriptionId(pub Arc<str>);
@@ -198,12 +193,6 @@ pub trait StripeClient: Send + Sync {
async fn create_customer(&self, params: CreateCustomerParams<'_>) -> Result<StripeCustomer>;
async fn update_customer(
&self,
customer_id: &StripeCustomerId,
params: UpdateCustomerParams<'_>,
) -> Result<StripeCustomer>;
async fn list_subscriptions_for_customer(
&self,
customer_id: &StripeCustomerId,

View File

@@ -14,7 +14,7 @@ use crate::stripe_client::{
StripeCreateCheckoutSessionSubscriptionData, StripeCreateMeterEventParams,
StripeCreateSubscriptionParams, StripeCustomer, StripeCustomerId, StripeMeter, StripeMeterId,
StripePrice, StripePriceId, StripeSubscription, StripeSubscriptionId, StripeSubscriptionItem,
StripeSubscriptionItemId, UpdateCustomerParams, UpdateSubscriptionParams,
StripeSubscriptionItemId, UpdateSubscriptionParams,
};
#[derive(Debug, Clone)]
@@ -95,22 +95,6 @@ impl StripeClient for FakeStripeClient {
Ok(customer)
}
async fn update_customer(
&self,
customer_id: &StripeCustomerId,
params: UpdateCustomerParams<'_>,
) -> Result<StripeCustomer> {
let mut customers = self.customers.lock();
if let Some(customer) = customers.get_mut(customer_id) {
if let Some(email) = params.email {
customer.email = Some(email.to_string());
}
Ok(customer.clone())
} else {
Err(anyhow!("no customer found for {customer_id:?}"))
}
}
async fn list_subscriptions_for_customer(
&self,
customer_id: &StripeCustomerId,

View File

@@ -11,7 +11,7 @@ use stripe::{
CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehavior,
CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehaviorMissingPaymentMethod,
CreateCustomer, Customer, CustomerId, ListCustomers, Price, PriceId, Recurring, Subscription,
SubscriptionId, SubscriptionItem, SubscriptionItemId, UpdateCustomer, UpdateSubscriptionItems,
SubscriptionId, SubscriptionItem, SubscriptionItemId, UpdateSubscriptionItems,
UpdateSubscriptionTrialSettings, UpdateSubscriptionTrialSettingsEndBehavior,
UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod,
};
@@ -25,8 +25,7 @@ use crate::stripe_client::{
StripePriceId, StripePriceRecurring, StripeSubscription, StripeSubscriptionId,
StripeSubscriptionItem, StripeSubscriptionItemId, StripeSubscriptionTrialSettings,
StripeSubscriptionTrialSettingsEndBehavior,
StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod, UpdateCustomerParams,
UpdateSubscriptionParams,
StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod, UpdateSubscriptionParams,
};
pub struct RealStripeClient {
@@ -79,24 +78,6 @@ impl StripeClient for RealStripeClient {
Ok(StripeCustomer::from(customer))
}
async fn update_customer(
&self,
customer_id: &StripeCustomerId,
params: UpdateCustomerParams<'_>,
) -> Result<StripeCustomer> {
let customer = Customer::update(
&self.client,
&customer_id.try_into()?,
UpdateCustomer {
email: params.email,
..Default::default()
},
)
.await?;
Ok(StripeCustomer::from(customer))
}
async fn list_subscriptions_for_customer(
&self,
customer_id: &StripeCustomerId,

View File

@@ -4,10 +4,10 @@ use crate::{
};
use call::ActiveCall;
use editor::{
DocumentColorsRenderMode, Editor, EditorSettings, RowInfo,
Editor, RowInfo,
actions::{
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
ExpandMacroRecursively, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
},
test::{
editor_test_context::{AssertionContextManager, EditorTestContext},
@@ -15,8 +15,8 @@ use editor::{
},
};
use fs::Fs;
use futures::{StreamExt, lock::Mutex};
use gpui::{App, Rgba, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
use futures::StreamExt;
use gpui::{TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
use indoc::indoc;
use language::{
FakeLspAdapter,
@@ -35,8 +35,7 @@ use rpc::RECEIVE_TIMEOUT;
use serde_json::json;
use settings::SettingsStore;
use std::{
collections::BTreeSet,
ops::{Deref as _, Range},
ops::Range,
path::{Path, PathBuf},
sync::{
Arc,
@@ -1952,876 +1951,6 @@ async fn test_inlay_hint_refresh_is_forwarded(
});
}
#[gpui::test(iterations = 10)]
async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
let expected_color = Rgba {
r: 0.33,
g: 0.33,
b: 0.33,
a: 0.33,
};
let mut server = TestServer::start(cx_a.executor()).await;
let executor = cx_a.executor();
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b = cx_b.read(ActiveCall::global);
cx_a.update(editor::init);
cx_b.update(editor::init);
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<EditorSettings>(cx, |settings| {
settings.lsp_document_colors = Some(DocumentColorsRenderMode::None);
});
});
});
cx_b.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<EditorSettings>(cx, |settings| {
settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
});
});
});
client_a.language_registry().add(rust_lang());
client_b.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
},
..FakeLspAdapter::default()
},
);
// Client A opens a project.
client_a
.fs()
.insert_tree(
path!("/a"),
json!({
"main.rs": "fn main() { a }",
}),
)
.await;
let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
active_call_a
.update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
.await
.unwrap();
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
// Client B joins the project
let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await
.unwrap();
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
// The host opens a rust file.
let _buffer_a = project_a
.update(cx_a, |project, cx| {
project.open_local_buffer(path!("/a/main.rs"), cx)
})
.await
.unwrap();
let editor_a = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let fake_language_server = fake_language_servers.next().await.unwrap();
let requests_made = Arc::new(AtomicUsize::new(0));
let closure_requests_made = Arc::clone(&requests_made);
let mut color_request_handle = fake_language_server
.set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
let requests_made = Arc::clone(&closure_requests_made);
async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
);
requests_made.fetch_add(1, atomic::Ordering::Release);
Ok(vec![lsp::ColorInformation {
range: lsp::Range {
start: lsp::Position {
line: 0,
character: 0,
},
end: lsp::Position {
line: 0,
character: 1,
},
},
color: lsp::Color {
red: 0.33,
green: 0.33,
blue: 0.33,
alpha: 0.33,
},
}])
}
});
executor.run_until_parked();
assert_eq!(
0,
requests_made.load(atomic::Ordering::Acquire),
"Host did not enable document colors, hence should query for none"
);
editor_a.update(cx_a, |editor, cx| {
assert_eq!(
Vec::<Rgba>::new(),
extract_color_inlays(editor, cx),
"No query colors should result in no hints"
);
});
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
color_request_handle.next().await.unwrap();
executor.run_until_parked();
assert_eq!(
1,
requests_made.load(atomic::Ordering::Acquire),
"The client opened the file and got its first colors back"
);
editor_b.update(cx_b, |editor, cx| {
assert_eq!(
vec![expected_color],
extract_color_inlays(editor, cx),
"With document colors as inlays, color inlays should be pushed"
);
});
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
editor.handle_input(":", window, cx);
});
color_request_handle.next().await.unwrap();
executor.run_until_parked();
assert_eq!(
2,
requests_made.load(atomic::Ordering::Acquire),
"After the host edits his file, the client should request the colors again"
);
editor_a.update(cx_a, |editor, cx| {
assert_eq!(
Vec::<Rgba>::new(),
extract_color_inlays(editor, cx),
"Host has no colors still"
);
});
editor_b.update(cx_b, |editor, cx| {
assert_eq!(vec![expected_color], extract_color_inlays(editor, cx),);
});
cx_b.update(|_, cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<EditorSettings>(cx, |settings| {
settings.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
});
});
});
executor.run_until_parked();
assert_eq!(
2,
requests_made.load(atomic::Ordering::Acquire),
"After the client have changed the colors settings, no extra queries should happen"
);
editor_a.update(cx_a, |editor, cx| {
assert_eq!(
Vec::<Rgba>::new(),
extract_color_inlays(editor, cx),
"Host is unaffected by the client's settings changes"
);
});
editor_b.update(cx_b, |editor, cx| {
assert_eq!(
Vec::<Rgba>::new(),
extract_color_inlays(editor, cx),
"Client should have no colors hints, as in the settings"
);
});
cx_b.update(|_, cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<EditorSettings>(cx, |settings| {
settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
});
});
});
executor.run_until_parked();
assert_eq!(
2,
requests_made.load(atomic::Ordering::Acquire),
"After falling back to colors as inlays, no extra LSP queries are made"
);
editor_a.update(cx_a, |editor, cx| {
assert_eq!(
Vec::<Rgba>::new(),
extract_color_inlays(editor, cx),
"Host is unaffected by the client's settings changes, again"
);
});
editor_b.update(cx_b, |editor, cx| {
assert_eq!(
vec![expected_color],
extract_color_inlays(editor, cx),
"Client should have its color hints back"
);
});
cx_a.update(|_, cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<EditorSettings>(cx, |settings| {
settings.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
});
});
});
color_request_handle.next().await.unwrap();
executor.run_until_parked();
assert_eq!(
3,
requests_made.load(atomic::Ordering::Acquire),
"After the host enables document colors, another LSP query should be made"
);
editor_a.update(cx_a, |editor, cx| {
assert_eq!(
Vec::<Rgba>::new(),
extract_color_inlays(editor, cx),
"Host did not configure document colors as hints hence gets nothing"
);
});
editor_b.update(cx_b, |editor, cx| {
assert_eq!(
vec![expected_color],
extract_color_inlays(editor, cx),
"Client should be unaffected by the host's settings changes"
);
});
}
#[gpui::test(iterations = 10)]
async fn test_lsp_pull_diagnostics(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
let mut server = TestServer::start(cx_a.executor()).await;
let executor = cx_a.executor();
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b = cx_b.read(ActiveCall::global);
cx_a.update(editor::init);
cx_b.update(editor::init);
client_a.language_registry().add(rust_lang());
client_b.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
lsp::DiagnosticOptions {
identifier: Some("test-pulls".to_string()),
inter_file_dependencies: true,
workspace_diagnostics: true,
work_done_progress_options: lsp::WorkDoneProgressOptions {
work_done_progress: None,
},
},
)),
..lsp::ServerCapabilities::default()
},
..FakeLspAdapter::default()
},
);
// Client A opens a project.
client_a
.fs()
.insert_tree(
path!("/a"),
json!({
"main.rs": "fn main() { a }",
"lib.rs": "fn other() {}",
}),
)
.await;
let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
active_call_a
.update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
.await
.unwrap();
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
// Client B joins the project
let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await
.unwrap();
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
executor.start_waiting();
// The host opens a rust file.
let _buffer_a = project_a
.update(cx_a, |project, cx| {
project.open_local_buffer(path!("/a/main.rs"), cx)
})
.await
.unwrap();
let editor_a_main = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let fake_language_server = fake_language_servers.next().await.unwrap();
let expected_push_diagnostic_main_message = "pushed main diagnostic";
let expected_push_diagnostic_lib_message = "pushed lib diagnostic";
let expected_pull_diagnostic_main_message = "pulled main diagnostic";
let expected_pull_diagnostic_lib_message = "pulled lib diagnostic";
let expected_workspace_pull_diagnostics_main_message = "pulled workspace main diagnostic";
let expected_workspace_pull_diagnostics_lib_message = "pulled workspace lib diagnostic";
let diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<Option<String>>::new()));
let workspace_diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<String>::new()));
let diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
let closure_diagnostics_pulls_made = diagnostics_pulls_made.clone();
let closure_diagnostics_pulls_result_ids = diagnostics_pulls_result_ids.clone();
let mut pull_diagnostics_handle = fake_language_server
.set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
let requests_made = closure_diagnostics_pulls_made.clone();
let diagnostics_pulls_result_ids = closure_diagnostics_pulls_result_ids.clone();
async move {
let message = if lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
== params.text_document.uri
{
expected_pull_diagnostic_main_message.to_string()
} else if lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap()
== params.text_document.uri
{
expected_pull_diagnostic_lib_message.to_string()
} else {
panic!("Unexpected document: {}", params.text_document.uri)
};
{
diagnostics_pulls_result_ids
.lock()
.await
.insert(params.previous_result_id);
}
let new_requests_count = requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
Ok(lsp::DocumentDiagnosticReportResult::Report(
lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
related_documents: None,
full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
result_id: Some(format!("pull-{new_requests_count}")),
items: vec![lsp::Diagnostic {
range: lsp::Range {
start: lsp::Position {
line: 0,
character: 0,
},
end: lsp::Position {
line: 0,
character: 2,
},
},
severity: Some(lsp::DiagnosticSeverity::ERROR),
message,
..lsp::Diagnostic::default()
}],
},
}),
))
}
});
let workspace_diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
let closure_workspace_diagnostics_pulls_made = workspace_diagnostics_pulls_made.clone();
let closure_workspace_diagnostics_pulls_result_ids =
workspace_diagnostics_pulls_result_ids.clone();
let mut workspace_diagnostics_pulls_handle = fake_language_server
.set_request_handler::<lsp::request::WorkspaceDiagnosticRequest, _, _>(
move |params, _| {
let workspace_requests_made = closure_workspace_diagnostics_pulls_made.clone();
let workspace_diagnostics_pulls_result_ids =
closure_workspace_diagnostics_pulls_result_ids.clone();
async move {
let workspace_request_count =
workspace_requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
{
workspace_diagnostics_pulls_result_ids
.lock()
.await
.extend(params.previous_result_ids.into_iter().map(|id| id.value));
}
Ok(lsp::WorkspaceDiagnosticReportResult::Report(
lsp::WorkspaceDiagnosticReport {
items: vec![
lsp::WorkspaceDocumentDiagnosticReport::Full(
lsp::WorkspaceFullDocumentDiagnosticReport {
uri: lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
version: None,
full_document_diagnostic_report:
lsp::FullDocumentDiagnosticReport {
result_id: Some(format!(
"workspace_{workspace_request_count}"
)),
items: vec![lsp::Diagnostic {
range: lsp::Range {
start: lsp::Position {
line: 0,
character: 1,
},
end: lsp::Position {
line: 0,
character: 3,
},
},
severity: Some(lsp::DiagnosticSeverity::WARNING),
message:
expected_workspace_pull_diagnostics_main_message
.to_string(),
..lsp::Diagnostic::default()
}],
},
},
),
lsp::WorkspaceDocumentDiagnosticReport::Full(
lsp::WorkspaceFullDocumentDiagnosticReport {
uri: lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap(),
version: None,
full_document_diagnostic_report:
lsp::FullDocumentDiagnosticReport {
result_id: Some(format!(
"workspace_{workspace_request_count}"
)),
items: vec![lsp::Diagnostic {
range: lsp::Range {
start: lsp::Position {
line: 0,
character: 1,
},
end: lsp::Position {
line: 0,
character: 3,
},
},
severity: Some(lsp::DiagnosticSeverity::WARNING),
message:
expected_workspace_pull_diagnostics_lib_message
.to_string(),
..lsp::Diagnostic::default()
}],
},
},
),
],
},
))
}
},
);
workspace_diagnostics_pulls_handle.next().await.unwrap();
assert_eq!(
1,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"Workspace diagnostics should be pulled initially on a server startup"
);
pull_diagnostics_handle.next().await.unwrap();
assert_eq!(
1,
diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"Host should query pull diagnostics when the editor is opened"
);
executor.run_until_parked();
editor_a_main.update(cx_a, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let all_diagnostics = snapshot
.diagnostics_in_range(0..snapshot.len())
.collect::<Vec<_>>();
assert_eq!(
all_diagnostics.len(),
1,
"Expected single diagnostic, but got: {all_diagnostics:?}"
);
let diagnostic = &all_diagnostics[0];
let expected_messages = [
expected_workspace_pull_diagnostics_main_message,
expected_pull_diagnostic_main_message,
];
assert!(
expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
"Expected {expected_messages:?} on the host, but got: {}",
diagnostic.diagnostic.message
);
});
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
&lsp::PublishDiagnosticsParams {
uri: lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
diagnostics: vec![lsp::Diagnostic {
range: lsp::Range {
start: lsp::Position {
line: 0,
character: 3,
},
end: lsp::Position {
line: 0,
character: 4,
},
},
severity: Some(lsp::DiagnosticSeverity::INFORMATION),
message: expected_push_diagnostic_main_message.to_string(),
..lsp::Diagnostic::default()
}],
version: None,
},
);
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
&lsp::PublishDiagnosticsParams {
uri: lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap(),
diagnostics: vec![lsp::Diagnostic {
range: lsp::Range {
start: lsp::Position {
line: 0,
character: 3,
},
end: lsp::Position {
line: 0,
character: 4,
},
},
severity: Some(lsp::DiagnosticSeverity::INFORMATION),
message: expected_push_diagnostic_lib_message.to_string(),
..lsp::Diagnostic::default()
}],
version: None,
},
);
executor.run_until_parked();
editor_a_main.update(cx_a, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let all_diagnostics = snapshot
.diagnostics_in_range(0..snapshot.len())
.collect::<Vec<_>>();
assert_eq!(
all_diagnostics.len(),
2,
"Expected pull and push diagnostics, but got: {all_diagnostics:?}"
);
let expected_messages = [
expected_workspace_pull_diagnostics_main_message,
expected_pull_diagnostic_main_message,
expected_push_diagnostic_main_message,
];
for diagnostic in all_diagnostics {
assert!(
expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
"Expected push and pull messages on the host: {expected_messages:?}, but got: {}",
diagnostic.diagnostic.message
);
}
});
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b_main = workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
pull_diagnostics_handle.next().await.unwrap();
assert_eq!(
2,
diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"Client should query pull diagnostics when its editor is opened"
);
executor.run_until_parked();
assert_eq!(
1,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"Workspace diagnostics should not be changed as the remote client does not initialize the workspace diagnostics pull"
);
editor_b_main.update(cx_b, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let all_diagnostics = snapshot
.diagnostics_in_range(0..snapshot.len())
.collect::<Vec<_>>();
assert_eq!(
all_diagnostics.len(),
2,
"Expected pull and push diagnostics, but got: {all_diagnostics:?}"
);
// Despite the workspace diagnostics not re-initialized for the remote client, we can still expect its message synced from the host.
let expected_messages = [
expected_workspace_pull_diagnostics_main_message,
expected_pull_diagnostic_main_message,
expected_push_diagnostic_main_message,
];
for diagnostic in all_diagnostics {
assert!(
expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
"The client should get both push and pull messages: {expected_messages:?}, but got: {}",
diagnostic.diagnostic.message
);
}
});
let editor_b_lib = workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "lib.rs"), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
pull_diagnostics_handle.next().await.unwrap();
assert_eq!(
3,
diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"Client should query pull diagnostics when its another editor is opened"
);
executor.run_until_parked();
assert_eq!(
1,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"The remote client still did not anything to trigger the workspace diagnostics pull"
);
editor_b_lib.update(cx_b, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let all_diagnostics = snapshot
.diagnostics_in_range(0..snapshot.len())
.collect::<Vec<_>>();
let expected_messages = [
expected_pull_diagnostic_lib_message,
// TODO bug: the pushed diagnostics are not being sent to the client when they open the corresponding buffer.
// expected_push_diagnostic_lib_message,
];
assert_eq!(
all_diagnostics.len(),
1,
"Expected pull diagnostics, but got: {all_diagnostics:?}"
);
for diagnostic in all_diagnostics {
assert!(
expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
"The client should get both push and pull messages: {expected_messages:?}, but got: {}",
diagnostic.diagnostic.message
);
}
});
{
assert!(
diagnostics_pulls_result_ids.lock().await.len() > 0,
"Initial diagnostics pulls should report None at least"
);
assert_eq!(
0,
workspace_diagnostics_pulls_result_ids
.lock()
.await
.deref()
.len(),
"After the initial workspace request, opening files should not reuse any result ids"
);
}
editor_b_lib.update_in(cx_b, |editor, window, cx| {
editor.move_to_end(&MoveToEnd, window, cx);
editor.handle_input(":", window, cx);
});
pull_diagnostics_handle.next().await.unwrap();
assert_eq!(
4,
diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"Client lib.rs edits should trigger another diagnostics pull for a buffer"
);
workspace_diagnostics_pulls_handle.next().await.unwrap();
assert_eq!(
2,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"After client lib.rs edits, the workspace diagnostics request should follow"
);
executor.run_until_parked();
editor_b_main.update_in(cx_b, |editor, window, cx| {
editor.move_to_end(&MoveToEnd, window, cx);
editor.handle_input(":", window, cx);
});
pull_diagnostics_handle.next().await.unwrap();
pull_diagnostics_handle.next().await.unwrap();
assert_eq!(
6,
diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"Client main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
);
workspace_diagnostics_pulls_handle.next().await.unwrap();
assert_eq!(
3,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"After client main.rs edits, the workspace diagnostics pull should follow"
);
executor.run_until_parked();
editor_a_main.update_in(cx_a, |editor, window, cx| {
editor.move_to_end(&MoveToEnd, window, cx);
editor.handle_input(":", window, cx);
});
pull_diagnostics_handle.next().await.unwrap();
pull_diagnostics_handle.next().await.unwrap();
assert_eq!(
8,
diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"Host main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
);
workspace_diagnostics_pulls_handle.next().await.unwrap();
assert_eq!(
4,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"After host main.rs edits, the workspace diagnostics pull should follow"
);
executor.run_until_parked();
let diagnostic_pulls_result_ids = diagnostics_pulls_result_ids.lock().await.len();
let workspace_pulls_result_ids = workspace_diagnostics_pulls_result_ids.lock().await.len();
{
assert!(
diagnostic_pulls_result_ids > 1,
"Should have sent result ids when pulling diagnostics"
);
assert!(
workspace_pulls_result_ids > 1,
"Should have sent result ids when pulling workspace diagnostics"
);
}
fake_language_server
.request::<lsp::request::WorkspaceDiagnosticRefresh>(())
.await
.into_response()
.expect("workspace diagnostics refresh request failed");
assert_eq!(
8,
diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"No single file pulls should happen after the diagnostics refresh server request"
);
workspace_diagnostics_pulls_handle.next().await.unwrap();
assert_eq!(
5,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"Another workspace diagnostics pull should happen after the diagnostics refresh server request"
);
{
assert!(
diagnostics_pulls_result_ids.lock().await.len() == diagnostic_pulls_result_ids,
"Pulls should not happen hence no extra ids should appear"
);
assert!(
workspace_diagnostics_pulls_result_ids.lock().await.len() > workspace_pulls_result_ids,
"More workspace diagnostics should be pulled"
);
}
editor_b_lib.update(cx_b, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let all_diagnostics = snapshot
.diagnostics_in_range(0..snapshot.len())
.collect::<Vec<_>>();
let expected_messages = [
expected_workspace_pull_diagnostics_lib_message,
expected_pull_diagnostic_lib_message,
expected_push_diagnostic_lib_message,
];
assert_eq!(all_diagnostics.len(), 1);
for diagnostic in &all_diagnostics {
assert!(
expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
"Unexpected diagnostics: {all_diagnostics:?}"
);
}
});
editor_b_main.update(cx_b, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let all_diagnostics = snapshot
.diagnostics_in_range(0..snapshot.len())
.collect::<Vec<_>>();
assert_eq!(all_diagnostics.len(), 2);
let expected_messages = [
expected_workspace_pull_diagnostics_main_message,
expected_pull_diagnostic_main_message,
expected_push_diagnostic_main_message,
];
for diagnostic in &all_diagnostics {
assert!(
expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
"Unexpected diagnostics: {all_diagnostics:?}"
);
}
});
editor_a_main.update(cx_a, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let all_diagnostics = snapshot
.diagnostics_in_range(0..snapshot.len())
.collect::<Vec<_>>();
assert_eq!(all_diagnostics.len(), 2);
let expected_messages = [
expected_workspace_pull_diagnostics_main_message,
expected_pull_diagnostic_main_message,
expected_push_diagnostic_main_message,
];
for diagnostic in &all_diagnostics {
assert!(
expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
"Unexpected diagnostics: {all_diagnostics:?}"
);
}
});
}
#[gpui::test(iterations = 10)]
async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
let mut server = TestServer::start(cx_a.executor()).await;
@@ -3705,16 +2834,6 @@ fn extract_hint_labels(editor: &Editor) -> Vec<String> {
labels
}
#[track_caller]
fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
editor
.all_inlays(cx)
.into_iter()
.filter_map(|inlay| inlay.get_color())
.map(Rgba::from)
.collect()
}
fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
git::blame::BlameEntry {
sha: sha.parse().unwrap(),

View File

@@ -293,7 +293,6 @@ impl MessageEditor {
candidates,
query,
true,
true,
LIMIT,
&Default::default(),
cx.background_executor().clone(),

View File

@@ -499,7 +499,6 @@ impl CollabPanel {
&self.match_candidates,
&query,
true,
true,
usize::MAX,
&Default::default(),
executor.clone(),
@@ -543,7 +542,6 @@ impl CollabPanel {
&self.match_candidates,
&query,
true,
true,
usize::MAX,
&Default::default(),
executor.clone(),
@@ -595,7 +593,6 @@ impl CollabPanel {
&self.match_candidates,
&query,
true,
true,
usize::MAX,
&Default::default(),
executor.clone(),
@@ -626,7 +623,6 @@ impl CollabPanel {
&self.match_candidates,
&query,
true,
true,
usize::MAX,
&Default::default(),
executor.clone(),
@@ -703,7 +699,6 @@ impl CollabPanel {
&self.match_candidates,
&query,
true,
true,
usize::MAX,
&Default::default(),
executor.clone(),
@@ -739,7 +734,6 @@ impl CollabPanel {
&self.match_candidates,
&query,
true,
true,
usize::MAX,
&Default::default(),
executor.clone(),
@@ -764,7 +758,6 @@ impl CollabPanel {
&self.match_candidates,
&query,
true,
true,
usize::MAX,
&Default::default(),
executor.clone(),
@@ -798,7 +791,6 @@ impl CollabPanel {
&self.match_candidates,
&query,
true,
true,
usize::MAX,
&Default::default(),
executor.clone(),
@@ -1645,10 +1637,6 @@ impl CollabPanel {
self.channel_name_editor.update(cx, |editor, cx| {
editor.insert(" ", window, cx);
});
} else if self.filter_editor.focus_handle(cx).is_focused(window) {
self.filter_editor.update(cx, |editor, cx| {
editor.insert(" ", window, cx);
});
}
}
@@ -2049,9 +2037,7 @@ impl CollabPanel {
dispatch_context.add("CollabPanel");
dispatch_context.add("menu");
let identifier = if self.channel_name_editor.focus_handle(cx).is_focused(window)
|| self.filter_editor.focus_handle(cx).is_focused(window)
{
let identifier = if self.channel_name_editor.focus_handle(cx).is_focused(window) {
"editing"
} else {
"not_editing"
@@ -3037,7 +3023,7 @@ impl Render for CollabPanel {
.on_action(cx.listener(CollabPanel::start_move_selected_channel))
.on_action(cx.listener(CollabPanel::move_channel_up))
.on_action(cx.listener(CollabPanel::move_channel_down))
.track_focus(&self.focus_handle)
.track_focus(&self.focus_handle(cx))
.size_full()
.child(if self.user_store.read(cx).current_user().is_none() {
self.render_signed_out(cx)

View File

@@ -295,7 +295,6 @@ impl PickerDelegate for ChannelModalDelegate {
&self.match_candidates,
&query,
true,
true,
usize::MAX,
&Default::default(),
cx.background_executor().clone(),

View File

@@ -327,7 +327,6 @@ impl PickerDelegate for CommandPaletteDelegate {
&candidates,
&query,
true,
true,
10000,
&Default::default(),
executor,

View File

@@ -29,7 +29,6 @@ chrono.workspace = true
client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
dirs.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true

View File

@@ -24,7 +24,6 @@ use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServer
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use request::StatusNotification;
use serde_json::json;
use settings::SettingsStore;
use sign_in::{reinstall_and_sign_in_within_workspace, sign_out_within_workspace};
use std::collections::hash_map::Entry;
@@ -62,15 +61,7 @@ pub fn init(
node_runtime: NodeRuntime,
cx: &mut App,
) {
let language_settings = all_language_settings(None, cx);
let configuration = copilot_chat::CopilotChatConfiguration {
enterprise_uri: language_settings
.edit_predictions
.copilot
.enterprise_uri
.clone(),
};
copilot_chat::init(fs.clone(), http.clone(), configuration, cx);
copilot_chat::init(fs.clone(), http.clone(), cx);
let copilot = cx.new({
let node_runtime = node_runtime.clone();
@@ -356,11 +347,8 @@ impl Copilot {
_subscription: cx.on_app_quit(Self::shutdown_language_server),
};
this.start_copilot(true, false, cx);
cx.observe_global::<SettingsStore>(move |this, cx| {
this.start_copilot(true, false, cx);
this.send_configuration_update(cx);
})
.detach();
cx.observe_global::<SettingsStore>(move |this, cx| this.start_copilot(true, false, cx))
.detach();
this
}
@@ -447,43 +435,6 @@ impl Copilot {
if env.is_empty() { None } else { Some(env) }
}
fn send_configuration_update(&mut self, cx: &mut Context<Self>) {
let copilot_settings = all_language_settings(None, cx)
.edit_predictions
.copilot
.clone();
let settings = json!({
"http": {
"proxy": copilot_settings.proxy,
"proxyStrictSSL": !copilot_settings.proxy_no_verify.unwrap_or(false)
},
"github-enterprise": {
"uri": copilot_settings.enterprise_uri
}
});
if let Some(copilot_chat) = copilot_chat::CopilotChat::global(cx) {
copilot_chat.update(cx, |chat, cx| {
chat.set_configuration(
copilot_chat::CopilotChatConfiguration {
enterprise_uri: copilot_settings.enterprise_uri.clone(),
},
cx,
);
});
}
if let Ok(server) = self.server.as_running() {
server
.lsp
.notify::<lsp::notification::DidChangeConfiguration>(
&lsp::DidChangeConfigurationParams { settings },
)
.log_err();
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn fake(cx: &mut gpui::TestAppContext) -> (Entity<Self>, lsp::FakeLanguageServer) {
use fs::FakeFs;
@@ -590,6 +541,12 @@ impl Copilot {
.into_response()
.context("copilot: check status")?;
server
.request::<request::SetEditorInfo>(editor_info)
.await
.into_response()
.context("copilot: set editor info")?;
anyhow::Ok((server, status))
};
@@ -607,8 +564,6 @@ impl Copilot {
});
cx.emit(Event::CopilotLanguageServerStarted);
this.update_sign_in_status(status, cx);
// Send configuration now that the LSP is fully started
this.send_configuration_update(cx);
}
Err(error) => {
this.server = CopilotServer::Error(error.to_string().into());

View File

@@ -19,47 +19,10 @@ use settings::watch_config_dir;
pub const COPILOT_OAUTH_ENV_VAR: &str = "GH_COPILOT_TOKEN";
#[derive(Default, Clone, Debug, PartialEq)]
pub struct CopilotChatConfiguration {
pub enterprise_uri: Option<String>,
}
impl CopilotChatConfiguration {
pub fn token_url(&self) -> String {
if let Some(enterprise_uri) = &self.enterprise_uri {
let domain = Self::parse_domain(enterprise_uri);
format!("https://api.{}/copilot_internal/v2/token", domain)
} else {
"https://api.github.com/copilot_internal/v2/token".to_string()
}
}
pub fn oauth_domain(&self) -> String {
if let Some(enterprise_uri) = &self.enterprise_uri {
Self::parse_domain(enterprise_uri)
} else {
"github.com".to_string()
}
}
pub fn api_url_from_endpoint(&self, endpoint: &str) -> String {
format!("{}/chat/completions", endpoint)
}
pub fn models_url_from_endpoint(&self, endpoint: &str) -> String {
format!("{}/models", endpoint)
}
fn parse_domain(enterprise_uri: &str) -> String {
let uri = enterprise_uri.trim_end_matches('/');
if let Some(domain) = uri.strip_prefix("https://") {
domain.split('/').next().unwrap_or(domain).to_string()
} else if let Some(domain) = uri.strip_prefix("http://") {
domain.split('/').next().unwrap_or(domain).to_string()
} else {
uri.split('/').next().unwrap_or(uri).to_string()
}
}
pub struct CopilotChatSettings {
pub api_url: Arc<str>,
pub auth_url: Arc<str>,
pub models_url: Arc<str>,
}
// Copilot's base model; defined by Microsoft in premium requests table
@@ -126,7 +89,7 @@ struct ModelLimits {
#[serde(default)]
max_output_tokens: usize,
#[serde(default)]
max_prompt_tokens: u64,
max_prompt_tokens: usize,
}
#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
@@ -182,7 +145,7 @@ impl Model {
self.name.as_str()
}
pub fn max_token_count(&self) -> u64 {
pub fn max_token_count(&self) -> usize {
self.capabilities.limits.max_prompt_tokens
}
@@ -311,14 +274,6 @@ pub struct FunctionContent {
pub struct ResponseEvent {
pub choices: Vec<ResponseChoice>,
pub id: String,
pub usage: Option<Usage>,
}
#[derive(Deserialize, Debug)]
pub struct Usage {
pub completion_tokens: u64,
pub prompt_tokens: u64,
pub total_tokens: u64,
}
#[derive(Debug, Deserialize)]
@@ -354,19 +309,12 @@ pub struct FunctionChunk {
struct ApiTokenResponse {
token: String,
expires_at: i64,
endpoints: ApiTokenResponseEndpoints,
}
#[derive(Deserialize)]
struct ApiTokenResponseEndpoints {
api: String,
}
#[derive(Clone)]
struct ApiToken {
api_key: String,
expires_at: DateTime<chrono::Utc>,
api_endpoint: String,
}
impl ApiToken {
@@ -387,7 +335,6 @@ impl TryFrom<ApiTokenResponse> for ApiToken {
Ok(Self {
api_key: response.token,
expires_at,
api_endpoint: response.endpoints.api,
})
}
}
@@ -399,18 +346,13 @@ impl Global for GlobalCopilotChat {}
pub struct CopilotChat {
oauth_token: Option<String>,
api_token: Option<ApiToken>,
configuration: CopilotChatConfiguration,
settings: CopilotChatSettings,
models: Option<Vec<Model>>,
client: Arc<dyn HttpClient>,
}
pub fn init(
fs: Arc<dyn Fs>,
client: Arc<dyn HttpClient>,
configuration: CopilotChatConfiguration,
cx: &mut App,
) {
let copilot_chat = cx.new(|cx| CopilotChat::new(fs, client, configuration, cx));
pub fn init(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &mut App) {
let copilot_chat = cx.new(|cx| CopilotChat::new(fs, client, cx));
cx.set_global(GlobalCopilotChat(copilot_chat));
}
@@ -418,15 +360,12 @@ pub fn copilot_chat_config_dir() -> &'static PathBuf {
static COPILOT_CHAT_CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
COPILOT_CHAT_CONFIG_DIR.get_or_init(|| {
let config_dir = if cfg!(target_os = "windows") {
dirs::data_local_dir().expect("failed to determine LocalAppData directory")
if cfg!(target_os = "windows") {
home_dir().join("AppData").join("Local")
} else {
std::env::var("XDG_CONFIG_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| home_dir().join(".config"))
};
config_dir.join("github-copilot")
home_dir().join(".config")
}
.join("github-copilot")
})
}
@@ -441,15 +380,10 @@ impl CopilotChat {
.map(|model| model.0.clone())
}
fn new(
fs: Arc<dyn Fs>,
client: Arc<dyn HttpClient>,
configuration: CopilotChatConfiguration,
cx: &mut Context<Self>,
) -> Self {
fn new(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &mut Context<Self>) -> Self {
let config_paths: HashSet<PathBuf> = copilot_chat_config_paths().into_iter().collect();
let dir_path = copilot_chat_config_dir();
let settings = CopilotChatSettings::default();
cx.spawn(async move |this, cx| {
let mut parent_watch_rx = watch_config_dir(
cx.background_executor(),
@@ -458,9 +392,7 @@ impl CopilotChat {
config_paths,
);
while let Some(contents) = parent_watch_rx.next().await {
let oauth_domain =
this.read_with(cx, |this, _| this.configuration.oauth_domain())?;
let oauth_token = extract_oauth_token(contents, &oauth_domain);
let oauth_token = extract_oauth_token(contents);
this.update(cx, |this, cx| {
this.oauth_token = oauth_token.clone();
@@ -479,10 +411,9 @@ impl CopilotChat {
oauth_token: std::env::var(COPILOT_OAUTH_ENV_VAR).ok(),
api_token: None,
models: None,
configuration,
settings,
client,
};
if this.oauth_token.is_some() {
cx.spawn(async move |this, mut cx| Self::update_models(&this, &mut cx).await)
.detach_and_log_err(cx);
@@ -492,26 +423,30 @@ impl CopilotChat {
}
async fn update_models(this: &WeakEntity<Self>, cx: &mut AsyncApp) -> Result<()> {
let (oauth_token, client, configuration) = this.read_with(cx, |this, _| {
let (oauth_token, client, auth_url) = this.read_with(cx, |this, _| {
(
this.oauth_token.clone(),
this.client.clone(),
this.configuration.clone(),
this.settings.auth_url.clone(),
)
})?;
let api_token = request_api_token(
&oauth_token.ok_or_else(|| {
anyhow!("OAuth token is missing while updating Copilot Chat models")
})?,
auth_url,
client.clone(),
)
.await?;
let oauth_token = oauth_token
.ok_or_else(|| anyhow!("OAuth token is missing while updating Copilot Chat models"))?;
let token_url = configuration.token_url();
let api_token = request_api_token(&oauth_token, token_url.into(), client.clone()).await?;
let models_url = configuration.models_url_from_endpoint(&api_token.api_endpoint);
let models =
get_models(models_url.into(), api_token.api_key.clone(), client.clone()).await?;
let models_url = this.update(cx, |this, cx| {
this.api_token = Some(api_token.clone());
cx.notify();
this.settings.models_url.clone()
})?;
let models = get_models(models_url, api_token.api_key, client.clone()).await?;
this.update(cx, |this, cx| {
this.api_token = Some(api_token);
this.models = Some(models);
cx.notify();
})?;
@@ -536,23 +471,23 @@ impl CopilotChat {
.flatten()
.context("Copilot chat is not enabled")?;
let (oauth_token, api_token, client, configuration) = this.read_with(&cx, |this, _| {
(
this.oauth_token.clone(),
this.api_token.clone(),
this.client.clone(),
this.configuration.clone(),
)
})?;
let (oauth_token, api_token, client, api_url, auth_url) =
this.read_with(&cx, |this, _| {
(
this.oauth_token.clone(),
this.api_token.clone(),
this.client.clone(),
this.settings.api_url.clone(),
this.settings.auth_url.clone(),
)
})?;
let oauth_token = oauth_token.context("No OAuth token available")?;
let token = match api_token {
Some(api_token) if api_token.remaining_seconds() > 5 * 60 => api_token.clone(),
_ => {
let token_url = configuration.token_url();
let token =
request_api_token(&oauth_token, token_url.into(), client.clone()).await?;
let token = request_api_token(&oauth_token, auth_url, client.clone()).await?;
this.update(&mut cx, |this, cx| {
this.api_token = Some(token.clone());
cx.notify();
@@ -561,19 +496,13 @@ impl CopilotChat {
}
};
let api_url = configuration.api_url_from_endpoint(&token.api_endpoint);
stream_completion(client.clone(), token.api_key, api_url.into(), request).await
stream_completion(client.clone(), token.api_key, api_url, request).await
}
pub fn set_configuration(
&mut self,
configuration: CopilotChatConfiguration,
cx: &mut Context<Self>,
) {
let same_configuration = self.configuration == configuration;
self.configuration = configuration;
if !same_configuration {
self.api_token = None;
pub fn set_settings(&mut self, settings: CopilotChatSettings, cx: &mut Context<Self>) {
let same_settings = self.settings == settings;
self.settings = settings;
if !same_settings {
cx.spawn(async move |this, cx| {
Self::update_models(&this, cx).await?;
Ok::<_, anyhow::Error>(())
@@ -593,12 +522,16 @@ async fn get_models(
let mut models: Vec<Model> = all_models
.into_iter()
.filter(|model| {
// Ensure user has access to the model; Policy is present only for models that must be
// enabled in the GitHub dashboard
model.model_picker_enabled
&& model
.policy
.as_ref()
.is_none_or(|policy| policy.state == "enabled")
})
// The first model from the API response, in any given family, appear to be the non-tagged
// models, which are likely the best choice (e.g. gpt-4o rather than gpt-4o-2024-11-20)
.dedup_by(|a, b| a.capabilities.family == b.capabilities.family)
.collect();
@@ -675,12 +608,12 @@ async fn request_api_token(
}
}
fn extract_oauth_token(contents: String, domain: &str) -> Option<String> {
fn extract_oauth_token(contents: String) -> Option<String> {
serde_json::from_str::<serde_json::Value>(&contents)
.map(|v| {
v.as_object().and_then(|obj| {
obj.iter().find_map(|(key, value)| {
if key.starts_with(domain) {
if key.starts_with("github.com") {
value["oauth_token"].as_str().map(|v| v.to_string())
} else {
None

View File

@@ -1,10 +1,10 @@
use ::fs::Fs;
use anyhow::{Context as _, Result, anyhow};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use collections::HashMap;
pub use dap_types::{StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest};
use fs::Fs;
use futures::io::BufReader;
use gpui::{AsyncApp, SharedString};
pub use http_client::{HttpClient, github::latest_github_release};
@@ -337,14 +337,13 @@ pub async fn download_adapter_from_github(
pub trait DebugAdapter: 'static + Send + Sync {
fn name(&self) -> DebugAdapterName;
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario>;
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario>;
async fn get_binary(
&self,
delegate: &Arc<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary>;
@@ -356,7 +355,7 @@ pub trait DebugAdapter: 'static + Send + Sync {
/// Extracts the kind (attach/launch) of debug configuration from the given JSON config.
/// This method should only return error when the kind cannot be determined for a given configuration;
/// in particular, it *should not* validate whether the request as a whole is valid, because that's best left to the debug adapter itself to decide.
async fn request_kind(
fn request_kind(
&self,
config: &serde_json::Value,
) -> Result<StartDebuggingRequestArgumentsRequest> {
@@ -399,7 +398,7 @@ impl DebugAdapter for FakeAdapter {
serde_json::Value::Null
}
async fn request_kind(
fn request_kind(
&self,
config: &serde_json::Value,
) -> Result<StartDebuggingRequestArgumentsRequest> {
@@ -418,7 +417,7 @@ impl DebugAdapter for FakeAdapter {
None
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
let config = serde_json::to_value(zed_scenario.request).unwrap();
Ok(DebugScenario {
@@ -435,7 +434,6 @@ impl DebugAdapter for FakeAdapter {
_: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
_: Option<PathBuf>,
_: Option<Vec<String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
Ok(DebugAdapterBinary {
@@ -445,7 +443,7 @@ impl DebugAdapter for FakeAdapter {
envs: HashMap::default(),
cwd: None,
request_args: StartDebuggingRequestArguments {
request: self.request_kind(&task_definition.config).await?,
request: self.request_kind(&task_definition.config)?,
configuration: task_definition.config.clone(),
},
})

View File

@@ -8,7 +8,8 @@ use dap_types::{
requests::Request,
};
use futures::channel::oneshot;
use gpui::AsyncApp;
use gpui::{AppContext, AsyncApp};
use smol::channel::{Receiver, Sender};
use std::{
hash::Hash,
sync::atomic::{AtomicU64, Ordering},
@@ -43,56 +44,99 @@ impl DebugAdapterClient {
id: SessionId,
binary: DebugAdapterBinary,
message_handler: DapMessageHandler,
cx: &mut AsyncApp,
cx: AsyncApp,
) -> Result<Self> {
let transport_delegate = TransportDelegate::start(&binary, cx).await?;
let ((server_rx, server_tx), transport_delegate) =
TransportDelegate::start(&binary, cx.clone()).await?;
let this = Self {
id,
binary,
transport_delegate,
sequence_count: AtomicU64::new(1),
};
this.connect(message_handler, cx).await?;
log::info!("Successfully connected to debug adapter");
let client_id = this.id;
// start handling events/reverse requests
cx.background_spawn(Self::handle_receive_messages(
client_id,
server_rx,
server_tx.clone(),
message_handler,
))
.detach();
Ok(this)
}
pub fn should_reconnect_for_ssh(&self) -> bool {
self.transport_delegate.tcp_arguments().is_some()
&& self.binary.command.as_deref() == Some("ssh")
}
pub async fn connect(
&self,
message_handler: DapMessageHandler,
cx: &mut AsyncApp,
) -> Result<()> {
self.transport_delegate.connect(message_handler, cx).await
}
pub async fn create_child_connection(
pub async fn reconnect(
&self,
session_id: SessionId,
binary: DebugAdapterBinary,
message_handler: DapMessageHandler,
cx: &mut AsyncApp,
cx: AsyncApp,
) -> Result<Self> {
let binary = if let Some(connection) = self.transport_delegate.tcp_arguments() {
DebugAdapterBinary {
command: None,
arguments: Default::default(),
envs: Default::default(),
cwd: Default::default(),
connection: Some(connection),
let binary = match self.transport_delegate.transport() {
crate::transport::Transport::Tcp(tcp_transport) => DebugAdapterBinary {
command: binary.command,
arguments: binary.arguments,
envs: binary.envs,
cwd: binary.cwd,
connection: Some(crate::adapters::TcpArguments {
host: tcp_transport.host,
port: tcp_transport.port,
timeout: Some(tcp_transport.timeout),
}),
request_args: binary.request_args,
}
} else {
self.binary.clone()
},
_ => self.binary.clone(),
};
Self::start(session_id, binary, message_handler, cx).await
}
async fn handle_receive_messages(
client_id: SessionId,
server_rx: Receiver<Message>,
client_tx: Sender<Message>,
mut message_handler: DapMessageHandler,
) -> Result<()> {
let result = loop {
let message = match server_rx.recv().await {
Ok(message) => message,
Err(e) => break Err(e.into()),
};
match message {
Message::Event(ev) => {
log::debug!("Client {} received event `{}`", client_id.0, &ev);
message_handler(Message::Event(ev))
}
Message::Request(req) => {
log::debug!(
"Client {} received reverse request `{}`",
client_id.0,
&req.command
);
message_handler(Message::Request(req))
}
Message::Response(response) => {
log::debug!("Received response after request timeout: {:#?}", response);
}
}
smol::future::yield_now().await;
};
drop(client_tx);
log::debug!("Handle receive messages dropped");
result
}
/// Send a request to an adapter and get a response back
/// Note: This function will block until a response is sent back from the adapter
pub async fn request<R: Request>(&self, arguments: R::Arguments) -> Result<R::Response> {
@@ -185,11 +229,8 @@ impl DebugAdapterClient {
+ Send
+ FnMut(u64, R::Arguments) -> Result<R::Response, dap_types::ErrorResponse>,
{
self.transport_delegate
.transport
.lock()
.as_fake()
.on_request::<R, F>(handler);
let transport = self.transport_delegate.transport().as_fake();
transport.on_request::<R, F>(handler);
}
#[cfg(any(test, feature = "test-support"))]
@@ -208,11 +249,8 @@ impl DebugAdapterClient {
where
F: 'static + Send + Fn(Response),
{
self.transport_delegate
.transport
.lock()
.as_fake()
.on_response::<R, F>(handler);
let transport = self.transport_delegate.transport().as_fake();
transport.on_response::<R, F>(handler).await;
}
#[cfg(any(test, feature = "test-support"))]
@@ -269,7 +307,7 @@ mod tests {
},
},
Box::new(|_| panic!("Did not expect to hit this code path")),
&mut cx.to_async(),
cx.to_async(),
)
.await
.unwrap();
@@ -351,7 +389,7 @@ mod tests {
);
}
}),
&mut cx.to_async(),
cx.to_async(),
)
.await
.unwrap();
@@ -409,7 +447,7 @@ mod tests {
);
}
}),
&mut cx.to_async(),
cx.to_async(),
)
.await
.unwrap();

View File

@@ -51,26 +51,18 @@ pub fn send_telemetry(scenario: &DebugScenario, location: TelemetrySpawnLocation
let Some(adapter) = cx.global::<DapRegistry>().adapter(&scenario.adapter) else {
return;
};
let kind = adapter
.request_kind(&scenario.config)
.ok()
.map(serde_json::to_value)
.and_then(Result::ok);
let dock = DebuggerSettings::get_global(cx).dock;
let config = scenario.config.clone();
let with_build_task = scenario.build.is_some();
let adapter_name = scenario.adapter.clone();
cx.spawn(async move |_| {
let kind = adapter
.request_kind(&config)
.await
.ok()
.map(serde_json::to_value)
.and_then(Result::ok);
telemetry::event!(
"Debugger Session Started",
spawn_location = location,
with_build_task = with_build_task,
kind = kind,
adapter = adapter_name,
dock_position = dock,
);
})
.detach();
telemetry::event!(
"Debugger Session Started",
spawn_location = location,
with_build_task = scenario.build.is_some(),
kind = kind,
adapter = scenario.adapter.as_ref(),
dock_position = dock,
);
}

View File

@@ -50,23 +50,20 @@ impl DapRegistry {
let name = adapter.name();
let _previous_value = self.0.write().adapters.insert(name, adapter);
}
pub fn add_locator(&self, locator: Arc<dyn DapLocator>) {
self.0.write().locators.insert(locator.name(), locator);
}
pub fn remove_adapter(&self, name: &str) {
self.0.write().adapters.remove(name);
}
pub fn remove_locator(&self, locator: &str) {
self.0.write().locators.remove(locator);
}
pub fn adapter_language(&self, adapter_name: &str) -> Option<LanguageName> {
self.adapter(adapter_name)
.and_then(|adapter| adapter.adapter_language_name())
}
pub fn add_locator(&self, locator: Arc<dyn DapLocator>) {
let _previous_value = self.0.write().locators.insert(locator.name(), locator);
debug_assert!(
_previous_value.is_none(),
"Attempted to insert a new debug locator when one is already registered"
);
}
pub async fn adapters_schema(&self) -> task::AdapterSchemas {
let mut schemas = AdapterSchemas(vec![]);

View File

@@ -1,19 +1,16 @@
use anyhow::{Context as _, Result, anyhow, bail};
#[cfg(any(test, feature = "test-support"))]
use async_pipe::{PipeReader, PipeWriter};
use dap_types::{
ErrorResponse,
messages::{Message, Response},
};
use futures::{AsyncRead, AsyncReadExt as _, AsyncWrite, FutureExt as _, channel::oneshot, select};
use gpui::{AppContext as _, AsyncApp, BackgroundExecutor, Task};
use parking_lot::Mutex;
use proto::ErrorExt;
use gpui::{AppContext as _, AsyncApp, Task};
use settings::Settings as _;
use smallvec::SmallVec;
use smol::{
channel::{Receiver, Sender, unbounded},
io::{AsyncBufReadExt as _, AsyncWriteExt, BufReader},
lock::Mutex,
net::{TcpListener, TcpStream},
};
use std::{
@@ -26,11 +23,7 @@ use std::{
use task::TcpArgumentsTemplate;
use util::ConnectionResult;
use crate::{
adapters::{DebugAdapterBinary, TcpArguments},
client::DapMessageHandler,
debugger_settings::DebuggerSettings,
};
use crate::{adapters::DebugAdapterBinary, debugger_settings::DebuggerSettings};
pub(crate) type IoMessage = str;
pub(crate) type Command = str;
@@ -42,144 +35,212 @@ pub enum LogKind {
Rpc,
}
#[derive(Clone, Copy)]
pub enum IoKind {
StdIn,
StdOut,
StdErr,
}
type Requests = Arc<Mutex<HashMap<u64, oneshot::Sender<Result<Response>>>>>;
type LogHandlers = Arc<Mutex<SmallVec<[(LogKind, IoHandler); 2]>>>;
pub struct TransportPipe {
input: Box<dyn AsyncWrite + Unpin + Send + 'static>,
output: Box<dyn AsyncRead + Unpin + Send + 'static>,
stdout: Option<Box<dyn AsyncRead + Unpin + Send + 'static>>,
stderr: Option<Box<dyn AsyncRead + Unpin + Send + 'static>>,
}
pub trait Transport: Send + Sync {
fn has_adapter_logs(&self) -> bool;
fn tcp_arguments(&self) -> Option<TcpArguments>;
fn connect(
&mut self,
) -> Task<
Result<(
Box<dyn AsyncWrite + Unpin + Send + 'static>,
Box<dyn AsyncRead + Unpin + Send + 'static>,
)>,
>;
fn kill(&self);
#[cfg(any(test, feature = "test-support"))]
fn as_fake(&self) -> &FakeTransport {
unreachable!()
impl TransportPipe {
pub fn new(
input: Box<dyn AsyncWrite + Unpin + Send + 'static>,
output: Box<dyn AsyncRead + Unpin + Send + 'static>,
stdout: Option<Box<dyn AsyncRead + Unpin + Send + 'static>>,
stderr: Option<Box<dyn AsyncRead + Unpin + Send + 'static>>,
) -> Self {
TransportPipe {
input,
output,
stdout,
stderr,
}
}
}
async fn start(
binary: &DebugAdapterBinary,
log_handlers: LogHandlers,
cx: &mut AsyncApp,
) -> Result<Box<dyn Transport>> {
type Requests = Arc<parking_lot::Mutex<HashMap<u64, oneshot::Sender<Result<Response>>>>>;
type LogHandlers = Arc<parking_lot::Mutex<SmallVec<[(LogKind, IoHandler); 2]>>>;
pub enum Transport {
Stdio(StdioTransport),
Tcp(TcpTransport),
#[cfg(any(test, feature = "test-support"))]
if cfg!(any(test, feature = "test-support")) {
return Ok(Box::new(FakeTransport::start(cx).await?));
Fake(FakeTransport),
}
impl Transport {
async fn start(binary: &DebugAdapterBinary, cx: AsyncApp) -> Result<(TransportPipe, Self)> {
#[cfg(any(test, feature = "test-support"))]
if cfg!(any(test, feature = "test-support")) {
return FakeTransport::start(cx)
.await
.map(|(transports, fake)| (transports, Self::Fake(fake)));
}
if binary.connection.is_some() {
TcpTransport::start(binary, cx)
.await
.map(|(transports, tcp)| (transports, Self::Tcp(tcp)))
.context("Tried to connect to a debug adapter via TCP transport layer")
} else {
StdioTransport::start(binary, cx)
.await
.map(|(transports, stdio)| (transports, Self::Stdio(stdio)))
.context("Tried to connect to a debug adapter via stdin/stdout transport layer")
}
}
if binary.connection.is_some() {
Ok(Box::new(
TcpTransport::start(binary, log_handlers, cx).await?,
))
} else {
Ok(Box::new(
StdioTransport::start(binary, log_handlers, cx).await?,
))
fn has_adapter_logs(&self) -> bool {
match self {
Transport::Stdio(stdio_transport) => stdio_transport.has_adapter_logs(),
Transport::Tcp(tcp_transport) => tcp_transport.has_adapter_logs(),
#[cfg(any(test, feature = "test-support"))]
Transport::Fake(fake_transport) => fake_transport.has_adapter_logs(),
}
}
async fn kill(&self) {
match self {
Transport::Stdio(stdio_transport) => stdio_transport.kill().await,
Transport::Tcp(tcp_transport) => tcp_transport.kill().await,
#[cfg(any(test, feature = "test-support"))]
Transport::Fake(fake_transport) => fake_transport.kill().await,
}
}
#[cfg(any(test, feature = "test-support"))]
pub(crate) fn as_fake(&self) -> &FakeTransport {
match self {
Transport::Fake(fake_transport) => fake_transport,
_ => panic!("Not a fake transport layer"),
}
}
}
pub(crate) struct TransportDelegate {
log_handlers: LogHandlers,
pending_requests: Requests,
pub(crate) transport: Mutex<Box<dyn Transport>>,
server_tx: smol::lock::Mutex<Option<Sender<Message>>>,
tasks: Mutex<Vec<Task<()>>>,
transport: Transport,
server_tx: Arc<Mutex<Option<Sender<Message>>>>,
_tasks: Vec<Task<()>>,
}
impl TransportDelegate {
pub(crate) async fn start(binary: &DebugAdapterBinary, cx: &mut AsyncApp) -> Result<Self> {
let log_handlers: LogHandlers = Default::default();
let transport = start(binary, log_handlers.clone(), cx).await?;
Ok(Self {
transport: Mutex::new(transport),
log_handlers,
pub(crate) async fn start(
binary: &DebugAdapterBinary,
cx: AsyncApp,
) -> Result<((Receiver<Message>, Sender<Message>), Self)> {
let (transport_pipes, transport) = Transport::start(binary, cx.clone()).await?;
let mut this = Self {
transport,
server_tx: Default::default(),
log_handlers: Default::default(),
pending_requests: Default::default(),
tasks: Default::default(),
})
_tasks: Vec::new(),
};
let messages = this.start_handlers(transport_pipes, cx).await?;
Ok((messages, this))
}
pub async fn connect(
&self,
message_handler: DapMessageHandler,
cx: &mut AsyncApp,
) -> Result<()> {
async fn start_handlers(
&mut self,
mut params: TransportPipe,
cx: AsyncApp,
) -> Result<(Receiver<Message>, Sender<Message>)> {
let (client_tx, server_rx) = unbounded::<Message>();
let (server_tx, client_rx) = unbounded::<Message>();
self.tasks.lock().clear();
let log_dap_communications =
cx.update(|cx| DebuggerSettings::get_global(cx).log_dap_communications)
.with_context(|| "Failed to get Debugger Setting log dap communications error in transport::start_handlers. Defaulting to false")
.unwrap_or(false);
let connect = self.transport.lock().connect();
let (input, output) = connect.await?;
let log_handler = if log_dap_communications {
Some(self.log_handlers.clone())
} else {
None
};
let pending_requests = self.pending_requests.clone();
let output_log_handler = log_handler.clone();
{
let mut tasks = self.tasks.lock();
tasks.push(cx.background_spawn(async move {
match Self::recv_from_server(
output,
message_handler,
let adapter_log_handler = log_handler.clone();
cx.update(|cx| {
if let Some(stdout) = params.stdout.take() {
self._tasks.push(cx.background_spawn(async move {
match Self::handle_adapter_log(stdout, adapter_log_handler).await {
ConnectionResult::Timeout => {
log::error!("Timed out when handling debugger log");
}
ConnectionResult::ConnectionReset => {
log::info!("Debugger logs connection closed");
}
ConnectionResult::Result(Ok(())) => {}
ConnectionResult::Result(Err(e)) => {
log::error!("Error handling debugger log: {e}");
}
}
}));
}
let pending_requests = self.pending_requests.clone();
let output_log_handler = log_handler.clone();
self._tasks.push(cx.background_spawn(async move {
match Self::handle_output(
params.output,
client_tx,
pending_requests.clone(),
output_log_handler,
)
.await
{
Ok(()) => {
pending_requests.lock().drain().for_each(|(_, request)| {
request
.send(Err(anyhow!("debugger shutdown unexpectedly")))
.ok();
});
}
Err(e) => {
pending_requests.lock().drain().for_each(|(_, request)| {
request.send(Err(e.cloned())).ok();
});
}
Ok(()) => {}
Err(e) => log::error!("Error handling debugger output: {e}"),
}
let mut pending_requests = pending_requests.lock();
pending_requests.drain().for_each(|(_, request)| {
request
.send(Err(anyhow!("debugger shutdown unexpectedly")))
.ok();
});
}));
tasks.push(cx.background_spawn(async move {
match Self::send_to_server(input, client_rx, log_handler).await {
if let Some(stderr) = params.stderr.take() {
let log_handlers = self.log_handlers.clone();
self._tasks.push(cx.background_spawn(async move {
match Self::handle_error(stderr, log_handlers).await {
ConnectionResult::Timeout => {
log::error!("Timed out reading debugger error stream")
}
ConnectionResult::ConnectionReset => {
log::info!("Debugger closed its error stream")
}
ConnectionResult::Result(Ok(())) => {}
ConnectionResult::Result(Err(e)) => {
log::error!("Error handling debugger error: {e}")
}
}
}));
}
let log_handler = log_handler.clone();
self._tasks.push(cx.background_spawn(async move {
match Self::handle_input(params.input, client_rx, log_handler).await {
Ok(()) => {}
Err(e) => log::error!("Error handling debugger input: {e}"),
}
}));
}
})?;
{
let mut lock = self.server_tx.lock().await;
*lock = Some(server_tx.clone());
}
Ok(())
}
pub(crate) fn tcp_arguments(&self) -> Option<TcpArguments> {
self.transport.lock().tcp_arguments()
Ok((server_rx, server_tx))
}
pub(crate) fn add_pending_request(
@@ -199,39 +260,48 @@ impl TransportDelegate {
}
}
async fn handle_adapter_log(
stdout: impl AsyncRead + Unpin + Send + 'static,
iokind: IoKind,
log_handlers: LogHandlers,
) {
async fn handle_adapter_log<Stdout>(
stdout: Stdout,
log_handlers: Option<LogHandlers>,
) -> ConnectionResult<()>
where
Stdout: AsyncRead + Unpin + Send + 'static,
{
let mut reader = BufReader::new(stdout);
let mut line = String::new();
loop {
let result = loop {
line.truncate(0);
match reader.read_line(&mut line).await {
Ok(0) => break,
match reader
.read_line(&mut line)
.await
.context("reading adapter log line")
{
Ok(0) => break ConnectionResult::ConnectionReset,
Ok(_) => {}
Err(e) => {
log::debug!("handle_adapter_log: {}", e);
break;
}
Err(e) => break ConnectionResult::Result(Err(e)),
}
for (kind, handler) in log_handlers.lock().iter_mut() {
if matches!(kind, LogKind::Adapter) {
handler(iokind, None, line.as_str());
if let Some(log_handlers) = log_handlers.as_ref() {
for (kind, handler) in log_handlers.lock().iter_mut() {
if matches!(kind, LogKind::Adapter) {
handler(IoKind::StdOut, None, line.as_str());
}
}
}
}
};
log::debug!("Handle adapter log dropped");
result
}
fn build_rpc_message(message: String) -> String {
format!("Content-Length: {}\r\n\r\n{}", message.len(), message)
}
async fn send_to_server<Stdin>(
async fn handle_input<Stdin>(
mut server_stdin: Stdin,
client_rx: Receiver<Message>,
log_handlers: Option<LogHandlers>,
@@ -281,9 +351,9 @@ impl TransportDelegate {
result
}
async fn recv_from_server<Stdout>(
async fn handle_output<Stdout>(
server_stdout: Stdout,
mut message_handler: DapMessageHandler,
client_tx: Sender<Message>,
pending_requests: Requests,
log_handlers: Option<LogHandlers>,
) -> Result<()>
@@ -309,19 +379,54 @@ impl TransportDelegate {
log::trace!("Did not send response `{:?}` for a cancelled", e);
}
} else {
message_handler(Message::Response(res))
client_tx.send(Message::Response(res)).await?;
}
}
ConnectionResult::Result(Ok(message)) => message_handler(message),
ConnectionResult::Result(Ok(message)) => client_tx.send(message).await?,
ConnectionResult::Result(Err(e)) => break Err(e),
}
};
drop(client_tx);
log::debug!("Handle adapter output dropped");
result
}
async fn handle_error<Stderr>(stderr: Stderr, log_handlers: LogHandlers) -> ConnectionResult<()>
where
Stderr: AsyncRead + Unpin + Send + 'static,
{
log::debug!("Handle error started");
let mut buffer = String::new();
let mut reader = BufReader::new(stderr);
let result = loop {
match reader
.read_line(&mut buffer)
.await
.context("reading error log line")
{
Ok(0) => break ConnectionResult::ConnectionReset,
Ok(_) => {
for (kind, log_handler) in log_handlers.lock().iter_mut() {
if matches!(kind, LogKind::Adapter) {
log_handler(IoKind::StdErr, None, buffer.as_str());
}
}
buffer.truncate(0);
}
Err(error) => break ConnectionResult::Result(Err(error)),
}
};
log::debug!("Handle adapter error dropped");
result
}
fn process_response(response: Response) -> Result<Response> {
if response.success {
Ok(response)
@@ -355,10 +460,14 @@ impl TransportDelegate {
loop {
buffer.truncate(0);
match reader.read_line(buffer).await {
match reader
.read_line(buffer)
.await
.with_context(|| "reading a message from server")
{
Ok(0) => return ConnectionResult::ConnectionReset,
Ok(_) => {}
Err(e) => return ConnectionResult::Result(Err(e.into())),
Err(e) => return ConnectionResult::Result(Err(e)),
};
if buffer == "\r\n" {
@@ -420,7 +529,7 @@ impl TransportDelegate {
}
self.pending_requests.lock().clear();
self.transport.lock().kill();
self.transport.kill().await;
log::debug!("Shutdown client completed");
@@ -428,7 +537,11 @@ impl TransportDelegate {
}
pub fn has_adapter_logs(&self) -> bool {
self.transport.lock().has_adapter_logs()
self.transport.has_adapter_logs()
}
pub fn transport(&self) -> &Transport {
&self.transport
}
pub fn add_log_handler<F>(&self, f: F, kind: LogKind)
@@ -441,13 +554,10 @@ impl TransportDelegate {
}
pub struct TcpTransport {
executor: BackgroundExecutor,
pub port: u16,
pub host: Ipv4Addr,
pub timeout: u64,
process: Arc<Mutex<Option<Child>>>,
_stderr_task: Option<Task<()>>,
_stdout_task: Option<Task<()>>,
process: Option<Mutex<Child>>,
}
impl TcpTransport {
@@ -467,11 +577,7 @@ impl TcpTransport {
.port())
}
async fn start(
binary: &DebugAdapterBinary,
log_handlers: LogHandlers,
cx: &mut AsyncApp,
) -> Result<Self> {
async fn start(binary: &DebugAdapterBinary, cx: AsyncApp) -> Result<(TransportPipe, Self)> {
let connection_args = binary
.connection
.as_ref()
@@ -480,11 +586,7 @@ impl TcpTransport {
let host = connection_args.host;
let port = connection_args.port;
let mut process = None;
let mut stdout_task = None;
let mut stderr_task = None;
if let Some(command) = &binary.command {
let mut process = if let Some(command) = &binary.command {
let mut command = util::command::new_std_command(&command);
if let Some(cwd) = &binary.cwd {
@@ -494,142 +596,101 @@ impl TcpTransport {
command.args(&binary.arguments);
command.envs(&binary.envs);
let mut p = Child::spawn(command, Stdio::null())
.with_context(|| "failed to start debug adapter.")?;
stdout_task = p.stdout.take().map(|stdout| {
cx.background_executor()
.spawn(TransportDelegate::handle_adapter_log(
stdout,
IoKind::StdOut,
log_handlers.clone(),
))
});
stderr_task = p.stderr.take().map(|stderr| {
cx.background_executor()
.spawn(TransportDelegate::handle_adapter_log(
stderr,
IoKind::StdErr,
log_handlers,
))
});
process = Some(p);
Some(
Child::spawn(command, Stdio::null())
.with_context(|| "failed to start debug adapter.")?,
)
} else {
None
};
let address = SocketAddrV4::new(host, port);
let timeout = connection_args.timeout.unwrap_or_else(|| {
cx.update(|cx| DebuggerSettings::get_global(cx).timeout)
.unwrap_or(20000u64)
.unwrap_or(2000u64)
});
let (mut process, (rx, tx)) = select! {
_ = cx.background_executor().timer(Duration::from_millis(timeout)).fuse() => {
anyhow::bail!("Connection to TCP DAP timeout {host}:{port}");
},
result = cx.spawn(async move |cx| {
loop {
match TcpStream::connect(address).await {
Ok(stream) => return Ok((process, stream.split())),
Err(_) => {
if let Some(p) = &mut process {
if let Ok(Some(_)) = p.try_status() {
let output = process.take().unwrap().into_inner().output().await?;
let output = if output.stderr.is_empty() {
String::from_utf8_lossy(&output.stdout).to_string()
} else {
String::from_utf8_lossy(&output.stderr).to_string()
};
anyhow::bail!("{output}\nerror: process exited before debugger attached.");
}
}
cx.background_executor().timer(Duration::from_millis(100)).await;
}
}
}
}).fuse() => result?
};
log::info!(
"Debug adapter has connected to TCP server {}:{}",
host,
port
);
let stdout = process.as_mut().and_then(|p| p.stdout.take());
let stderr = process.as_mut().and_then(|p| p.stderr.take());
let this = Self {
executor: cx.background_executor().clone(),
port,
host,
process: Arc::new(Mutex::new(process)),
process: process.map(Mutex::new),
timeout,
_stdout_task: stdout_task,
_stderr_task: stderr_task,
};
Ok(this)
}
}
let pipe = TransportPipe::new(
Box::new(tx),
Box::new(BufReader::new(rx)),
stdout.map(|s| Box::new(s) as Box<dyn AsyncRead + Unpin + Send>),
stderr.map(|s| Box::new(s) as Box<dyn AsyncRead + Unpin + Send>),
);
Ok((pipe, this))
}
impl Transport for TcpTransport {
fn has_adapter_logs(&self) -> bool {
true
}
fn kill(&self) {
if let Some(process) = &mut *self.process.lock() {
process.kill();
async fn kill(&self) {
if let Some(process) = &self.process {
let mut process = process.lock().await;
Child::kill(&mut process);
}
}
fn tcp_arguments(&self) -> Option<TcpArguments> {
Some(TcpArguments {
host: self.host,
port: self.port,
timeout: Some(self.timeout),
})
}
fn connect(
&mut self,
) -> Task<
Result<(
Box<dyn AsyncWrite + Unpin + Send + 'static>,
Box<dyn AsyncRead + Unpin + Send + 'static>,
)>,
> {
let executor = self.executor.clone();
let timeout = self.timeout;
let address = SocketAddrV4::new(self.host, self.port);
let process = self.process.clone();
executor.clone().spawn(async move {
select! {
_ = executor.timer(Duration::from_millis(timeout)).fuse() => {
anyhow::bail!("Connection to TCP DAP timeout {address}");
},
result = executor.clone().spawn(async move {
loop {
match TcpStream::connect(address).await {
Ok(stream) => {
let (read, write) = stream.split();
return Ok((Box::new(write) as _, Box::new(read) as _))
},
Err(_) => {
let has_process = process.lock().is_some();
if has_process {
let status = process.lock().as_mut().unwrap().try_status();
if let Ok(Some(_)) = status {
let process = process.lock().take().unwrap().into_inner();
let output = process.output().await?;
let output = if output.stderr.is_empty() {
String::from_utf8_lossy(&output.stdout).to_string()
} else {
String::from_utf8_lossy(&output.stderr).to_string()
};
anyhow::bail!("{output}\nerror: process exited before debugger attached.");
}
}
executor.timer(Duration::from_millis(100)).await;
}
}
}
}).fuse() => result
}
})
}
}
impl Drop for TcpTransport {
fn drop(&mut self) {
if let Some(mut p) = self.process.lock().take() {
p.kill();
if let Some(mut p) = self.process.take() {
p.get_mut().kill();
}
}
}
pub struct StdioTransport {
process: Mutex<Child>,
_stderr_task: Option<Task<()>>,
}
impl StdioTransport {
// #[allow(dead_code, reason = "This is used in non test builds of Zed")]
async fn start(
binary: &DebugAdapterBinary,
log_handlers: LogHandlers,
cx: &mut AsyncApp,
) -> Result<Self> {
#[allow(dead_code, reason = "This is used in non test builds of Zed")]
async fn start(binary: &DebugAdapterBinary, _: AsyncApp) -> Result<(TransportPipe, Self)> {
let Some(binary_command) = &binary.command else {
bail!(
"When using the `stdio` transport, the path to a debug adapter binary must be set by Zed."
@@ -652,52 +713,42 @@ impl StdioTransport {
)
})?;
let err_task = process.stderr.take().map(|stderr| {
cx.background_spawn(TransportDelegate::handle_adapter_log(
stderr,
IoKind::StdErr,
log_handlers,
))
});
let stdin = process.stdin.take().context("Failed to open stdin")?;
let stdout = process.stdout.take().context("Failed to open stdout")?;
let stderr = process
.stderr
.take()
.map(|io_err| Box::new(io_err) as Box<dyn AsyncRead + Unpin + Send>);
if stderr.is_none() {
bail!(
"Failed to connect to stderr for debug adapter command {}",
&binary_command
);
}
log::info!("Debug adapter has connected to stdio adapter");
let process = Mutex::new(process);
Ok(Self {
process,
_stderr_task: err_task,
})
Ok((
TransportPipe::new(
Box::new(stdin),
Box::new(BufReader::new(stdout)),
None,
stderr,
),
Self { process },
))
}
}
impl Transport for StdioTransport {
fn has_adapter_logs(&self) -> bool {
false
}
fn kill(&self) {
self.process.lock().kill()
}
fn connect(
&mut self,
) -> Task<
Result<(
Box<dyn AsyncWrite + Unpin + Send + 'static>,
Box<dyn AsyncRead + Unpin + Send + 'static>,
)>,
> {
let mut process = self.process.lock();
let result = util::maybe!({
Ok((
Box::new(process.stdin.take().context("Cannot reconnect")?) as _,
Box::new(process.stdout.take().context("Cannot reconnect")?) as _,
))
});
Task::ready(result)
}
fn tcp_arguments(&self) -> Option<TcpArguments> {
None
async fn kill(&self) {
let mut process = self.process.lock().await;
Child::kill(&mut process);
}
}
@@ -717,12 +768,9 @@ type ResponseHandler = Box<dyn Send + Fn(Response)>;
#[cfg(any(test, feature = "test-support"))]
pub struct FakeTransport {
// for sending fake response back from adapter side
request_handlers: Arc<Mutex<HashMap<&'static str, RequestHandler>>>,
request_handlers: Arc<parking_lot::Mutex<HashMap<&'static str, RequestHandler>>>,
// for reverse request responses
response_handlers: Arc<Mutex<HashMap<&'static str, ResponseHandler>>>,
stdin_writer: Option<PipeWriter>,
stdout_reader: Option<PipeReader>,
response_handlers: Arc<parking_lot::Mutex<HashMap<&'static str, ResponseHandler>>>,
}
#[cfg(any(test, feature = "test-support"))]
@@ -758,7 +806,7 @@ impl FakeTransport {
);
}
pub fn on_response<R: dap_types::requests::Request, F>(&self, handler: F)
pub async fn on_response<R: dap_types::requests::Request, F>(&self, handler: F)
where
F: 'static + Send + Fn(Response),
{
@@ -767,23 +815,20 @@ impl FakeTransport {
.insert(R::COMMAND, Box::new(handler));
}
async fn start(cx: &mut AsyncApp) -> Result<Self> {
async fn start(cx: AsyncApp) -> Result<(TransportPipe, Self)> {
let this = Self {
request_handlers: Arc::new(parking_lot::Mutex::new(HashMap::default())),
response_handlers: Arc::new(parking_lot::Mutex::new(HashMap::default())),
};
use dap_types::requests::{Request, RunInTerminal, StartDebugging};
use serde_json::json;
let (stdin_writer, stdin_reader) = async_pipe::pipe();
let (stdout_writer, stdout_reader) = async_pipe::pipe();
let this = Self {
request_handlers: Arc::new(Mutex::new(HashMap::default())),
response_handlers: Arc::new(Mutex::new(HashMap::default())),
stdin_writer: Some(stdin_writer),
stdout_reader: Some(stdout_reader),
};
let request_handlers = this.request_handlers.clone();
let response_handlers = this.response_handlers.clone();
let stdout_writer = Arc::new(smol::lock::Mutex::new(stdout_writer));
let stdout_writer = Arc::new(Mutex::new(stdout_writer));
cx.background_spawn(async move {
let mut reader = BufReader::new(stdin_reader);
@@ -873,43 +918,17 @@ impl FakeTransport {
})
.detach();
Ok(this)
}
}
#[cfg(any(test, feature = "test-support"))]
impl Transport for FakeTransport {
fn tcp_arguments(&self) -> Option<TcpArguments> {
None
}
fn connect(
&mut self,
) -> Task<
Result<(
Box<dyn AsyncWrite + Unpin + Send + 'static>,
Box<dyn AsyncRead + Unpin + Send + 'static>,
)>,
> {
let result = util::maybe!({
Ok((
Box::new(self.stdin_writer.take().context("Cannot reconnect")?) as _,
Box::new(self.stdout_reader.take().context("Cannot reconnect")?) as _,
))
});
Task::ready(result)
Ok((
TransportPipe::new(Box::new(stdin_writer), Box::new(stdout_reader), None, None),
this,
))
}
fn has_adapter_logs(&self) -> bool {
false
}
fn kill(&self) {}
#[cfg(any(test, feature = "test-support"))]
fn as_fake(&self) -> &FakeTransport {
self
}
async fn kill(&self) {}
}
struct Child {

View File

@@ -33,7 +33,6 @@ log.workspace = true
paths.workspace = true
serde.workspace = true
serde_json.workspace = true
shlex.workspace = true
task.workspace = true
util.workspace = true
workspace-hack.workspace = true

View File

@@ -19,7 +19,7 @@ pub(crate) struct CodeLldbDebugAdapter {
impl CodeLldbDebugAdapter {
const ADAPTER_NAME: &'static str = "CodeLLDB";
async fn request_args(
fn request_args(
&self,
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
@@ -37,7 +37,7 @@ impl CodeLldbDebugAdapter {
obj.entry("cwd")
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
let request = self.request_kind(&configuration).await?;
let request = self.request_kind(&configuration)?;
Ok(dap::StartDebuggingRequestArguments {
request,
@@ -89,7 +89,7 @@ impl DebugAdapter for CodeLldbDebugAdapter {
DebugAdapterName(Self::ADAPTER_NAME.into())
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
let mut configuration = json!({
"request": match zed_scenario.request {
DebugRequest::Launch(_) => "launch",
@@ -329,7 +329,6 @@ impl DebugAdapter for CodeLldbDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
let mut command = user_installed_path
@@ -365,13 +364,11 @@ impl DebugAdapter for CodeLldbDebugAdapter {
Ok(DebugAdapterBinary {
command: Some(command.unwrap()),
cwd: Some(delegate.worktree_root_path().to_path_buf()),
arguments: user_args.unwrap_or_else(|| {
vec![
"--settings".into(),
json!({"sourceLanguages": ["cpp", "rust"]}).to_string(),
]
}),
request_args: self.request_args(delegate, &config).await?,
arguments: vec![
"--settings".into(),
json!({"sourceLanguages": ["cpp", "rust"]}).to_string(),
],
request_args: self.request_args(delegate, &config)?,
envs: HashMap::default(),
connection: None,
})

View File

@@ -21,7 +21,7 @@ impl DebugAdapter for GdbDebugAdapter {
DebugAdapterName(Self::ADAPTER_NAME.into())
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
let mut obj = serde_json::Map::default();
match &zed_scenario.request {
@@ -159,7 +159,6 @@ impl DebugAdapter for GdbDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<std::path::PathBuf>,
user_args: Option<Vec<String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
let user_setting_path = user_installed_path
@@ -187,12 +186,12 @@ impl DebugAdapter for GdbDebugAdapter {
Ok(DebugAdapterBinary {
command: Some(gdb_path),
arguments: user_args.unwrap_or_else(|| vec!["-i=dap".into()]),
arguments: vec!["-i=dap".into()],
envs: HashMap::default(),
cwd: Some(delegate.worktree_root_path().to_path_buf()),
connection: None,
request_args: StartDebuggingRequestArguments {
request: self.request_kind(&config.config).await?,
request: self.request_kind(&config.config)?,
configuration,
},
})

View File

@@ -343,7 +343,7 @@ impl DebugAdapter for GoDebugAdapter {
},
{
"type": "object",
"required": ["mode"],
"required": ["processId", "mode"],
"properties": attach_properties
}
]
@@ -352,7 +352,7 @@ impl DebugAdapter for GoDebugAdapter {
})
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
let mut args = match &zed_scenario.request {
dap::DebugRequest::Attach(attach_config) => {
json!({
@@ -399,7 +399,6 @@ impl DebugAdapter for GoDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
_cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
@@ -471,10 +470,7 @@ impl DebugAdapter for GoDebugAdapter {
crate::configure_tcp_connection(TcpArgumentsTemplate::default()).await?;
command = Some(minidelve_path.to_string_lossy().into_owned());
connection = None;
arguments = if let Some(mut args) = user_args {
args.insert(0, delve_path);
args
} else if cfg!(windows) {
arguments = if cfg!(windows) {
vec![
delve_path,
"dap".into(),
@@ -499,7 +495,7 @@ impl DebugAdapter for GoDebugAdapter {
connection,
request_args: StartDebuggingRequestArguments {
configuration,
request: self.request_kind(&task_definition.config).await?,
request: self.request_kind(&task_definition.config)?,
},
})
}

View File

@@ -5,7 +5,7 @@ use gpui::AsyncApp;
use serde_json::Value;
use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
use task::DebugRequest;
use util::{ResultExt, maybe};
use util::ResultExt;
use crate::*;
@@ -50,7 +50,6 @@ impl JsDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
let adapter_path = if let Some(user_installed_path) = user_installed_path {
@@ -72,24 +71,6 @@ impl JsDebugAdapter {
let mut configuration = task_definition.config.clone();
if let Some(configuration) = configuration.as_object_mut() {
maybe!({
configuration
.get("type")
.filter(|value| value == &"node-terminal")?;
let command = configuration.get("command")?.as_str()?.to_owned();
let mut args = shlex::split(&command)?.into_iter();
let program = args.next()?;
configuration.insert("program".to_owned(), program.into());
configuration.insert(
"args".to_owned(),
args.map(Value::from).collect::<Vec<_>>().into(),
);
configuration.insert("console".to_owned(), "externalTerminal".into());
Some(())
});
configuration.entry("type").and_modify(normalize_task_type);
if let Some(program) = configuration
.get("program")
.cloned()
@@ -114,39 +95,9 @@ impl JsDebugAdapter {
.entry("cwd")
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
configuration
.entry("console")
.or_insert("externalTerminal".into());
configuration.entry("sourceMaps").or_insert(true.into());
configuration
.entry("pauseForSourceMap")
.or_insert(true.into());
configuration
.entry("sourceMapRenames")
.or_insert(true.into());
configuration.entry("type").and_modify(normalize_task_type);
}
let arguments = if let Some(mut args) = user_args {
args.insert(
0,
adapter_path
.join(Self::ADAPTER_PATH)
.to_string_lossy()
.to_string(),
);
args
} else {
vec![
adapter_path
.join(Self::ADAPTER_PATH)
.to_string_lossy()
.to_string(),
port.to_string(),
host.to_string(),
]
};
Ok(DebugAdapterBinary {
command: Some(
delegate
@@ -156,7 +107,14 @@ impl JsDebugAdapter {
.to_string_lossy()
.into_owned(),
),
arguments,
arguments: vec![
adapter_path
.join(Self::ADAPTER_PATH)
.to_string_lossy()
.to_string(),
port.to_string(),
host.to_string(),
],
cwd: Some(delegate.worktree_root_path().to_path_buf()),
envs: HashMap::default(),
connection: Some(adapters::TcpArguments {
@@ -166,7 +124,7 @@ impl JsDebugAdapter {
}),
request_args: StartDebuggingRequestArguments {
configuration,
request: self.request_kind(&task_definition.config).await?,
request: self.request_kind(&task_definition.config)?,
},
})
}
@@ -178,7 +136,7 @@ impl DebugAdapter for JsDebugAdapter {
DebugAdapterName(Self::ADAPTER_NAME.into())
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
let mut args = json!({
"type": "pwa-node",
"request": match zed_scenario.request {
@@ -308,16 +266,6 @@ impl DebugAdapter for JsDebugAdapter {
"description": "Use JavaScript source maps if they exist",
"default": true
},
"pauseForSourceMap": {
"type": "boolean",
"description": "Wait for source maps to load before setting breakpoints.",
"default": true
},
"sourceMapRenames": {
"type": "boolean",
"description": "Whether to use the \"names\" mapping in sourcemaps.",
"default": true
},
"sourceMapPathOverrides": {
"type": "object",
"description": "Rewrites the locations of source files from what the sourcemap says to their locations on disk",
@@ -495,7 +443,6 @@ impl DebugAdapter for JsDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
if self.checked.set(()).is_ok() {
@@ -513,7 +460,7 @@ impl DebugAdapter for JsDebugAdapter {
}
}
self.get_installed_binary(delegate, &config, user_installed_path, user_args, cx)
self.get_installed_binary(delegate, &config, user_installed_path, cx)
.await
}
@@ -529,7 +476,7 @@ fn normalize_task_type(task_type: &mut Value) {
};
let new_name = match task_type_str {
"node" | "pwa-node" | "node-terminal" => "pwa-node",
"node" | "pwa-node" => "pwa-node",
"chrome" | "pwa-chrome" => "pwa-chrome",
"edge" | "msedge" | "pwa-edge" | "pwa-msedge" => "pwa-msedge",
_ => task_type_str,

View File

@@ -52,7 +52,6 @@ impl PhpDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
let adapter_path = if let Some(user_installed_path) = user_installed_path {
@@ -78,25 +77,6 @@ impl PhpDebugAdapter {
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
}
let arguments = if let Some(mut args) = user_args {
args.insert(
0,
adapter_path
.join(Self::ADAPTER_PATH)
.to_string_lossy()
.to_string(),
);
args
} else {
vec![
adapter_path
.join(Self::ADAPTER_PATH)
.to_string_lossy()
.to_string(),
format!("--server={}", port),
]
};
Ok(DebugAdapterBinary {
command: Some(
delegate
@@ -106,7 +86,13 @@ impl PhpDebugAdapter {
.to_string_lossy()
.into_owned(),
),
arguments,
arguments: vec![
adapter_path
.join(Self::ADAPTER_PATH)
.to_string_lossy()
.to_string(),
format!("--server={}", port),
],
connection: Some(TcpArguments {
port,
host,
@@ -116,8 +102,7 @@ impl PhpDebugAdapter {
envs: HashMap::default(),
request_args: StartDebuggingRequestArguments {
configuration,
request: <Self as DebugAdapter>::request_kind(self, &task_definition.config)
.await?,
request: <Self as DebugAdapter>::request_kind(self, &task_definition.config)?,
},
})
}
@@ -305,14 +290,11 @@ impl DebugAdapter for PhpDebugAdapter {
Some(SharedString::new_static("PHP").into())
}
async fn request_kind(
&self,
_: &serde_json::Value,
) -> Result<StartDebuggingRequestArgumentsRequest> {
fn request_kind(&self, _: &serde_json::Value) -> Result<StartDebuggingRequestArgumentsRequest> {
Ok(StartDebuggingRequestArgumentsRequest::Launch)
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
let obj = match &zed_scenario.request {
dap::DebugRequest::Attach(_) => {
bail!("Php adapter doesn't support attaching")
@@ -340,7 +322,6 @@ impl DebugAdapter for PhpDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
if self.checked.set(()).is_ok() {
@@ -356,13 +337,7 @@ impl DebugAdapter for PhpDebugAdapter {
}
}
self.get_installed_binary(
delegate,
&task_definition,
user_installed_path,
user_args,
cx,
)
.await
self.get_installed_binary(delegate, &task_definition, user_installed_path, cx)
.await
}
}

View File

@@ -32,23 +32,29 @@ impl PythonDebugAdapter {
host: &Ipv4Addr,
port: u16,
user_installed_path: Option<&Path>,
user_args: Option<Vec<String>>,
installed_in_venv: bool,
) -> Result<Vec<String>> {
let mut args = if let Some(user_installed_path) = user_installed_path {
if let Some(user_installed_path) = user_installed_path {
log::debug!(
"Using user-installed debugpy adapter from: {}",
user_installed_path.display()
);
vec![
Ok(vec![
user_installed_path
.join(Self::ADAPTER_PATH)
.to_string_lossy()
.to_string(),
]
format!("--host={}", host),
format!("--port={}", port),
])
} else if installed_in_venv {
log::debug!("Using venv-installed debugpy");
vec!["-m".to_string(), "debugpy.adapter".to_string()]
Ok(vec![
"-m".to_string(),
"debugpy.adapter".to_string(),
format!("--host={}", host),
format!("--port={}", port),
])
} else {
let adapter_path = paths::debug_adapters_dir().join(Self::DEBUG_ADAPTER_NAME.as_ref());
let file_name_prefix = format!("{}_", Self::ADAPTER_NAME);
@@ -64,28 +70,23 @@ impl PythonDebugAdapter {
"Using GitHub-downloaded debugpy adapter from: {}",
debugpy_dir.display()
);
vec![
Ok(vec![
debugpy_dir
.join(Self::ADAPTER_PATH)
.to_string_lossy()
.to_string(),
]
};
args.extend(if let Some(args) = user_args {
args
} else {
vec![format!("--host={}", host), format!("--port={}", port)]
});
Ok(args)
format!("--host={}", host),
format!("--port={}", port),
])
}
}
async fn request_args(
fn request_args(
&self,
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
) -> Result<StartDebuggingRequestArguments> {
let request = self.request_kind(&task_definition.config).await?;
let request = self.request_kind(&task_definition.config)?;
let mut configuration = task_definition.config.clone();
if let Ok(console) = configuration.dot_get_mut("console") {
@@ -150,7 +151,6 @@ impl PythonDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
toolchain: Option<Toolchain>,
installed_in_venv: bool,
) -> Result<DebugAdapterBinary> {
@@ -182,7 +182,6 @@ impl PythonDebugAdapter {
&host,
port,
user_installed_path.as_deref(),
user_args,
installed_in_venv,
)
.await?;
@@ -203,7 +202,7 @@ impl PythonDebugAdapter {
}),
cwd: Some(delegate.worktree_root_path().to_path_buf()),
envs: HashMap::default(),
request_args: self.request_args(delegate, config).await?,
request_args: self.request_args(delegate, config)?,
})
}
}
@@ -218,7 +217,7 @@ impl DebugAdapter for PythonDebugAdapter {
Some(SharedString::new_static("Python").into())
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
let mut args = json!({
"request": match zed_scenario.request {
DebugRequest::Launch(_) => "launch",
@@ -596,7 +595,6 @@ impl DebugAdapter for PythonDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
if let Some(local_path) = &user_installed_path {
@@ -605,14 +603,7 @@ impl DebugAdapter for PythonDebugAdapter {
local_path.display()
);
return self
.get_installed_binary(
delegate,
&config,
Some(local_path.clone()),
user_args,
None,
false,
)
.get_installed_binary(delegate, &config, Some(local_path.clone()), None, false)
.await;
}
@@ -639,7 +630,6 @@ impl DebugAdapter for PythonDebugAdapter {
delegate,
&config,
None,
user_args,
Some(toolchain.clone()),
true,
)
@@ -657,7 +647,7 @@ impl DebugAdapter for PythonDebugAdapter {
}
}
self.get_installed_binary(delegate, &config, None, user_args, toolchain, false)
self.get_installed_binary(delegate, &config, None, toolchain, false)
.await
}
}
@@ -692,22 +682,16 @@ mod tests {
// Case 1: User-defined debugpy path (highest precedence)
let user_path = PathBuf::from("/custom/path/to/debugpy");
let user_args = PythonDebugAdapter::generate_debugpy_arguments(
&host,
port,
Some(&user_path),
None,
false,
)
.await
.unwrap();
// Case 2: Venv-installed debugpy (uses -m debugpy.adapter)
let venv_args =
PythonDebugAdapter::generate_debugpy_arguments(&host, port, None, None, true)
let user_args =
PythonDebugAdapter::generate_debugpy_arguments(&host, port, Some(&user_path), false)
.await
.unwrap();
// Case 2: Venv-installed debugpy (uses -m debugpy.adapter)
let venv_args = PythonDebugAdapter::generate_debugpy_arguments(&host, port, None, true)
.await
.unwrap();
assert!(user_args[0].ends_with("src/debugpy/adapter"));
assert_eq!(user_args[1], "--host=127.0.0.1");
assert_eq!(user_args[2], "--port=5678");
@@ -717,33 +701,6 @@ mod tests {
assert_eq!(venv_args[2], "--host=127.0.0.1");
assert_eq!(venv_args[3], "--port=5678");
// The same cases, with arguments overridden by the user
let user_args = PythonDebugAdapter::generate_debugpy_arguments(
&host,
port,
Some(&user_path),
Some(vec!["foo".into()]),
false,
)
.await
.unwrap();
let venv_args = PythonDebugAdapter::generate_debugpy_arguments(
&host,
port,
None,
Some(vec!["foo".into()]),
true,
)
.await
.unwrap();
assert!(user_args[0].ends_with("src/debugpy/adapter"));
assert_eq!(user_args[1], "foo");
assert_eq!(venv_args[0], "-m");
assert_eq!(venv_args[1], "debugpy.adapter");
assert_eq!(venv_args[2], "foo");
// Note: Case 3 (GitHub-downloaded debugpy) is not tested since this requires mocking the Github API.
}
}

View File

@@ -45,10 +45,7 @@ impl DebugAdapter for RubyDebugAdapter {
Some(SharedString::new_static("Ruby").into())
}
async fn request_kind(
&self,
_: &serde_json::Value,
) -> Result<StartDebuggingRequestArgumentsRequest> {
fn request_kind(&self, _: &serde_json::Value) -> Result<StartDebuggingRequestArgumentsRequest> {
Ok(StartDebuggingRequestArgumentsRequest::Launch)
}
@@ -86,7 +83,7 @@ impl DebugAdapter for RubyDebugAdapter {
})
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
match zed_scenario.request {
DebugRequest::Launch(launch) => {
let config = RubyDebugConfig {
@@ -119,7 +116,6 @@ impl DebugAdapter for RubyDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
definition: &DebugTaskDefinition,
_user_installed_path: Option<PathBuf>,
_user_args: Option<Vec<String>>,
_cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
@@ -200,7 +196,7 @@ impl DebugAdapter for RubyDebugAdapter {
),
envs: ruby_config.env.into_iter().collect(),
request_args: StartDebuggingRequestArguments {
request: self.request_kind(&definition.config).await?,
request: self.request_kind(&definition.config)?,
configuration,
},
})

View File

@@ -1,7 +1,7 @@
mod extension_dap_adapter;
mod extension_locator_adapter;
use std::{path::Path, sync::Arc};
use std::sync::Arc;
use dap::DapRegistry;
use extension::{ExtensionDebugAdapterProviderProxy, ExtensionHostProxy};
@@ -34,11 +34,8 @@ impl ExtensionDebugAdapterProviderProxy for DebugAdapterRegistryProxy {
&self,
extension: Arc<dyn extension::Extension>,
debug_adapter_name: Arc<str>,
schema_path: &Path,
) {
if let Some(adapter) =
ExtensionDapAdapter::new(extension, debug_adapter_name, schema_path).log_err()
{
if let Some(adapter) = ExtensionDapAdapter::new(extension, debug_adapter_name).log_err() {
self.debug_adapter_registry.add_adapter(Arc::new(adapter));
}
}
@@ -54,13 +51,4 @@ impl ExtensionDebugAdapterProviderProxy for DebugAdapterRegistryProxy {
locator_name,
)));
}
fn unregister_debug_adapter(&self, debug_adapter_name: Arc<str>) {
self.debug_adapter_registry
.remove_adapter(&debug_adapter_name);
}
fn unregister_debug_locator(&self, locator_name: Arc<str>) {
self.debug_adapter_registry.remove_locator(&locator_name);
}
}

View File

@@ -6,11 +6,8 @@ use std::{
use anyhow::{Context, Result};
use async_trait::async_trait;
use dap::{
StartDebuggingRequestArgumentsRequest,
adapters::{
DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition,
},
use dap::adapters::{
DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition,
};
use extension::{Extension, WorktreeDelegate};
use gpui::AsyncApp;
@@ -26,13 +23,11 @@ impl ExtensionDapAdapter {
pub(crate) fn new(
extension: Arc<dyn extension::Extension>,
debug_adapter_name: Arc<str>,
schema_path: &Path,
) -> Result<Self> {
let schema = std::fs::read_to_string(&schema_path).with_context(|| {
format!(
"Failed to read debug adapter schema for {debug_adapter_name} (from path: `{schema_path:?}`)"
)
})?;
let schema = std::fs::read_to_string(extension.path_from_extension(
&Path::new("debug_adapter_schemas").join(debug_adapter_name.as_ref()),
))
.with_context(|| format!("Failed to read debug adapter schema for {debug_adapter_name}"))?;
let schema = serde_json::Value::from_str(&schema).with_context(|| {
format!("Debug adapter schema for {debug_adapter_name} is not a valid JSON")
})?;
@@ -88,8 +83,6 @@ impl DebugAdapter for ExtensionDapAdapter {
delegate: &Arc<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
// TODO support user args in the extension API
_user_args: Option<Vec<String>>,
_cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
self.extension
@@ -102,16 +95,7 @@ impl DebugAdapter for ExtensionDapAdapter {
.await
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
self.extension.dap_config_to_scenario(zed_scenario).await
}
async fn request_kind(
&self,
config: &serde_json::Value,
) -> Result<StartDebuggingRequestArgumentsRequest> {
self.extension
.dap_request_kind(self.debug_adapter_name.clone(), config.clone())
.await
fn config_from_zed_format(&self, _zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
Err(anyhow::anyhow!("DAP extensions are not implemented yet"))
}
}

View File

@@ -26,7 +26,6 @@ test-support = [
]
[dependencies]
alacritty_terminal.workspace = true
anyhow.workspace = true
client.workspace = true
collections.workspace = true
@@ -35,6 +34,7 @@ dap.workspace = true
dap_adapters = { workspace = true, optional = true }
db.workspace = true
editor.workspace = true
feature_flags.workspace = true
file_icons.workspace = true
futures.workspace = true
fuzzy.workspace = true
@@ -51,17 +51,14 @@ project.workspace = true
rpc.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
# serde_json_lenient.workspace = true
settings.workspace = true
shlex.workspace = true
sysinfo.workspace = true
task.workspace = true
tasks_ui.workspace = true
telemetry.workspace = true
terminal_view.workspace = true
theme.workspace = true
tree-sitter.workspace = true
tree-sitter-json.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true

View File

@@ -183,7 +183,6 @@ impl PickerDelegate for AttachModalDelegate {
.collect::<Vec<_>>(),
&query,
true,
true,
100,
&Default::default(),
cx.background_executor().clone(),
@@ -229,36 +228,26 @@ impl PickerDelegate for AttachModalDelegate {
}
}
let Some(adapter) = cx.read_global::<DapRegistry, _>(|registry, _| {
registry.adapter(&self.definition.adapter)
let Some(scenario) = cx.read_global::<DapRegistry, _>(|registry, _| {
registry
.adapter(&self.definition.adapter)
.and_then(|adapter| adapter.config_from_zed_format(self.definition.clone()).ok())
}) else {
return;
};
let workspace = self.workspace.clone();
let definition = self.definition.clone();
cx.spawn_in(window, async move |this, cx| {
let Ok(scenario) = adapter.config_from_zed_format(definition).await else {
return;
};
let panel = self
.workspace
.update(cx, |workspace, cx| workspace.panel::<DebugPanel>(cx))
.ok()
.flatten();
if let Some(panel) = panel {
panel.update(cx, |panel, cx| {
panel.start_session(scenario, Default::default(), None, None, window, cx);
});
}
let panel = workspace
.update(cx, |workspace, cx| workspace.panel::<DebugPanel>(cx))
.ok()
.flatten();
if let Some(panel) = panel {
panel
.update_in(cx, |panel, window, cx| {
panel.start_session(scenario, Default::default(), None, None, window, cx);
})
.ok();
}
this.update(cx, |_, cx| {
cx.emit(DismissEvent);
})
.ok();
})
.detach();
cx.emit(DismissEvent);
}
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {

View File

@@ -1,14 +1,13 @@
use crate::persistence::DebuggerPaneItem;
use crate::session::DebugSession;
use crate::session::running::RunningState;
use crate::session::running::breakpoint_list::BreakpointList;
use crate::{
ClearAllBreakpoints, Continue, CopyDebugAdapterArguments, Detach, FocusBreakpointList,
FocusConsole, FocusFrames, FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables,
NewProcessModal, NewProcessMode, Pause, Restart, StepInto, StepOut, StepOver, Stop,
ToggleExpandItem, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
};
use anyhow::{Context as _, Result, anyhow};
use anyhow::Result;
use dap::adapters::DebugAdapterName;
use dap::debugger_settings::DebugPanelDockPosition;
use dap::{
@@ -19,19 +18,17 @@ use dap::{DapRegistry, StartDebuggingRequestArguments};
use gpui::{
Action, App, AsyncWindowContext, ClipboardItem, Context, DismissEvent, Entity, EntityId,
EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task,
WeakEntity, anchored, deferred,
WeakEntity, actions, anchored, deferred,
};
use itertools::Itertools as _;
use language::Buffer;
use project::debugger::session::{Session, SessionStateEvent};
use project::{Fs, ProjectPath, WorktreeId};
use project::{Fs, WorktreeId};
use project::{Project, debugger::session::ThreadStatus};
use rpc::proto::{self};
use settings::Settings;
use std::sync::{Arc, LazyLock};
use std::sync::Arc;
use task::{DebugScenario, TaskContext};
use tree_sitter::{Query, StreamingIterator as _};
use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
use util::maybe;
use workspace::SplitDirection;
@@ -39,7 +36,6 @@ use workspace::{
Pane, Workspace,
dock::{DockPosition, Panel, PanelEvent},
};
use zed_actions::ToggleFocus;
pub enum DebugPanelEvent {
Exited(SessionId),
@@ -58,6 +54,8 @@ pub enum DebugPanelEvent {
CapabilitiesChanged(SessionId),
}
actions!(debug_panel, [ToggleFocus]);
pub struct DebugPanel {
size: Pixels,
sessions: Vec<Entity<DebugSession>>,
@@ -72,7 +70,6 @@ pub struct DebugPanel {
fs: Arc<dyn Fs>,
is_zoomed: bool,
_subscriptions: [Subscription; 1],
breakpoint_list: Entity<BreakpointList>,
}
impl DebugPanel {
@@ -100,7 +97,6 @@ impl DebugPanel {
sessions: vec![],
active_session: None,
focus_handle,
breakpoint_list: BreakpointList::new(None, workspace.weak_handle(), &project, cx),
project,
workspace: workspace.weak_handle(),
context_menu: None,
@@ -862,21 +858,16 @@ impl DebugPanel {
let threads =
running_state.update(cx, |running_state, cx| {
let session = running_state.session();
session.read(cx).is_running().then(|| {
session.update(cx, |session, cx| {
session.threads(cx)
})
})
session
.update(cx, |session, cx| session.threads(cx))
});
threads.and_then(|threads| {
self.render_thread_dropdown(
&running_state,
threads,
window,
cx,
)
})
self.render_thread_dropdown(
&running_state,
threads,
window,
cx,
)
})
.when(!is_side, |this| this.gap_2().child(Divider::vertical()))
},
@@ -961,98 +952,69 @@ impl DebugPanel {
cx.notify();
}
pub(crate) fn save_scenario(
&self,
scenario: &DebugScenario,
worktree_id: WorktreeId,
window: &mut Window,
cx: &mut App,
) -> Task<Result<ProjectPath>> {
self.workspace
.update(cx, |workspace, cx| {
let Some(mut path) = workspace.absolute_path_of_worktree(worktree_id, cx) else {
return Task::ready(Err(anyhow!("Couldn't get worktree path")));
};
// TODO: restore once we have proper comment preserving file edits
// pub(crate) fn save_scenario(
// &self,
// scenario: &DebugScenario,
// worktree_id: WorktreeId,
// window: &mut Window,
// cx: &mut App,
// ) -> Task<Result<ProjectPath>> {
// self.workspace
// .update(cx, |workspace, cx| {
// let Some(mut path) = workspace.absolute_path_of_worktree(worktree_id, cx) else {
// return Task::ready(Err(anyhow!("Couldn't get worktree path")));
// };
let serialized_scenario = serde_json::to_value(scenario);
// let serialized_scenario = serde_json::to_value(scenario);
cx.spawn_in(window, async move |workspace, cx| {
let serialized_scenario = serialized_scenario?;
let fs =
workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
// cx.spawn_in(window, async move |workspace, cx| {
// let serialized_scenario = serialized_scenario?;
// let fs =
// workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
path.push(paths::local_settings_folder_relative_path());
if !fs.is_dir(path.as_path()).await {
fs.create_dir(path.as_path()).await?;
}
path.pop();
// path.push(paths::local_settings_folder_relative_path());
// if !fs.is_dir(path.as_path()).await {
// fs.create_dir(path.as_path()).await?;
// }
// path.pop();
path.push(paths::local_debug_file_relative_path());
let path = path.as_path();
// path.push(paths::local_debug_file_relative_path());
// let path = path.as_path();
if !fs.is_file(path).await {
fs.create_file(path, Default::default()).await?;
fs.write(
path,
settings::initial_local_debug_tasks_content()
.to_string()
.as_bytes(),
)
.await?;
}
// if !fs.is_file(path).await {
// fs.create_file(path, Default::default()).await?;
// fs.write(
// path,
// initial_local_debug_tasks_content().to_string().as_bytes(),
// )
// .await?;
// }
let mut content = fs.load(path).await?;
let new_scenario = serde_json_lenient::to_string_pretty(&serialized_scenario)?
.lines()
.map(|l| format!(" {l}"))
.join("\n");
// let content = fs.load(path).await?;
// let mut values =
// serde_json_lenient::from_str::<Vec<serde_json::Value>>(&content)?;
// values.push(serialized_scenario);
// fs.save(
// path,
// &serde_json_lenient::to_string_pretty(&values).map(Into::into)?,
// Default::default(),
// )
// .await?;
static ARRAY_QUERY: LazyLock<Query> = LazyLock::new(|| {
Query::new(
&tree_sitter_json::LANGUAGE.into(),
"(document (array (object) @object))", // TODO: use "." anchor to only match last object
)
.expect("Failed to create ARRAY_QUERY")
});
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&tree_sitter_json::LANGUAGE.into())
.unwrap();
let mut cursor = tree_sitter::QueryCursor::new();
let syntax_tree = parser.parse(&content, None).unwrap();
let mut matches =
cursor.matches(&ARRAY_QUERY, syntax_tree.root_node(), content.as_bytes());
// we don't have `.last()` since it's a lending iterator, so loop over
// the whole thing to find the last one
let mut last_offset = None;
while let Some(mat) = matches.next() {
if let Some(pos) = mat.captures.first().map(|m| m.node.byte_range().end) {
last_offset = Some(pos)
}
}
if let Some(pos) = last_offset {
content.insert_str(pos, &new_scenario);
content.insert_str(pos, ",\n");
}
fs.write(path, content.as_bytes()).await?;
workspace.update(cx, |workspace, cx| {
workspace
.project()
.read(cx)
.project_path_for_absolute_path(&path, cx)
.context(
"Couldn't get project path for .zed/debug.json in active worktree",
)
})?
})
})
.unwrap_or_else(|err| Task::ready(Err(err)))
}
// workspace.update(cx, |workspace, cx| {
// workspace
// .project()
// .read(cx)
// .project_path_for_absolute_path(&path, cx)
// .context(
// "Couldn't get project path for .zed/debug.json in active worktree",
// )
// })?
// })
// })
// .unwrap_or_else(|err| Task::ready(Err(err)))
// }
pub(crate) fn toggle_thread_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.thread_picker_menu_handle.toggle(window, cx);
@@ -1141,9 +1103,7 @@ async fn register_session_inner(
let debug_session = DebugSession::running(
this.project.clone(),
this.workspace.clone(),
parent_session
.as_ref()
.map(|p| p.read(cx).running_state().read(cx).debug_terminal.clone()),
parent_session.map(|p| p.read(cx).running_state().read(cx).debug_terminal.clone()),
session,
serialized_layout,
this.position(window, cx).axis(),
@@ -1158,14 +1118,8 @@ async fn register_session_inner(
|_, _, cx| cx.notify(),
)
.detach();
let insert_position = this
.sessions
.iter()
.position(|session| Some(session) == parent_session.as_ref())
.map(|position| position + 1)
.unwrap_or(this.sessions.len());
// Maintain topological sort order of sessions
this.sessions.insert(insert_position, debug_session.clone());
this.sessions.push(debug_session.clone());
debug_session
})?;
@@ -1469,65 +1423,21 @@ impl Render for DebugPanel {
.items_center()
.justify_center()
.child(
h_flex().size_full()
.items_start()
.child(v_flex().group("base-breakpoint-list").items_start().min_w_1_3().h_full().p_1()
.child(h_flex().pl_1().w_full().justify_between()
.child(Label::new("Breakpoints").size(LabelSize::Small))
.child(h_flex().visible_on_hover("base-breakpoint-list").child(self.breakpoint_list.read(cx).render_control_strip())))
.child(Divider::horizontal())
.child(self.breakpoint_list.clone()))
.child(Divider::vertical())
.child(
v_flex().w_2_3().h_full().items_center().justify_center()
.gap_2()
.pr_8()
.child(
Button::new("spawn-new-session-empty-state", "New Session")
.icon(IconName::Plus)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(crate::Start.boxed_clone(), cx);
})
)
.child(
Button::new("edit-debug-settings", "Edit debug.json")
.icon(IconName::Code)
.icon_size(IconSize::XSmall)
.color(Color::Muted)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(zed_actions::OpenProjectDebugTasks.boxed_clone(), cx);
})
)
.child(
Button::new("open-debugger-docs", "Debugger Docs")
.icon(IconName::Book)
.color(Color::Muted)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, _, cx| {
cx.open_url("https://zed.dev/docs/debugger")
})
)
.child(
Button::new("spawn-new-session-install-extensions", "Debugger Extensions")
.icon(IconName::Blocks)
.color(Color::Muted)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(zed_actions::Extensions { category_filter: Some(zed_actions::ExtensionCategoryFilter::DebugAdapters)}.boxed_clone(), cx);
})
)
)
h_flex().child(
Label::new("No Debugging Sessions")
.size(LabelSize::Small)
.color(Color::Muted),
),
)
.child(
h_flex().flex_shrink().child(
Button::new("spawn-new-session-empty-state", "New Session")
.size(ButtonSize::Large)
.on_click(|_, window, cx| {
window.dispatch_action(crate::Start.boxed_clone(), cx);
}),
),
),
)
}
})

View File

@@ -1,11 +1,11 @@
use std::any::TypeId;
use dap::debugger_settings::DebuggerSettings;
use debugger_panel::DebugPanel;
use debugger_panel::{DebugPanel, ToggleFocus};
use editor::Editor;
use feature_flags::{DebuggerFeatureFlag, FeatureFlagViewExt};
use gpui::{App, DispatchPhase, EntityInputHandler, actions};
use new_process_modal::{NewProcessModal, NewProcessMode};
use onboarding_modal::DebuggerOnboardingModal;
use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus};
use session::DebugSession;
use settings::Settings;
@@ -14,14 +14,11 @@ use tasks_ui::{Spawn, TaskOverrides};
use ui::{FluentBuilder, InteractiveElement};
use util::maybe;
use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace};
use zed_actions::ToggleFocus;
use zed_actions::debugger::OpenOnboardingModal;
pub mod attach_modal;
pub mod debugger_panel;
mod dropdown_menus;
mod new_process_modal;
mod onboarding_modal;
mod persistence;
pub(crate) mod session;
mod stack_trace_view;
@@ -65,166 +62,173 @@ pub fn init(cx: &mut App) {
DebuggerSettings::register(cx);
workspace::FollowableViewRegistry::register::<DebugSession>(cx);
cx.observe_new(|workspace: &mut Workspace, _, _| {
workspace
.register_action(spawn_task_or_modal)
.register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<DebugPanel>(window, cx);
})
.register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
})
.register_action(
|workspace: &mut Workspace, _: &RerunLastSession, window, cx| {
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
return;
};
cx.observe_new(|_: &mut Workspace, window, cx| {
let Some(window) = window else {
return;
};
debug_panel.update(cx, |debug_panel, cx| {
debug_panel.rerun_last_session(workspace, window, cx);
})
},
)
.register_action(
|workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
workspace.project().update(cx, |project, cx| {
project.dap_store().update(cx, |store, cx| {
store.shutdown_sessions(cx).detach();
})
})
},
)
.register_action(|workspace, _: &OpenOnboardingModal, window, cx| {
DebuggerOnboardingModal::toggle(workspace, window, cx)
})
.register_action_renderer(|div, workspace, _, cx| {
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
return div;
};
let Some(active_item) = debug_panel
.read(cx)
.active_session()
.map(|session| session.read(cx).running_state().clone())
else {
return div;
};
let running_state = active_item.read(cx);
if running_state.session().read(cx).is_terminated() {
return div;
}
let caps = running_state.capabilities(cx);
let supports_step_back = caps.supports_step_back.unwrap_or_default();
let supports_detach = running_state.session().read(cx).is_attached();
let status = running_state.thread_status(cx);
let active_item = active_item.downgrade();
div.when(status == Some(ThreadStatus::Running), |div| {
let active_item = active_item.clone();
div.on_action(move |_: &Pause, _, cx| {
active_item
.update(cx, |item, cx| item.pause_thread(cx))
.ok();
})
cx.when_flag_enabled::<DebuggerFeatureFlag>(window, |workspace, _, _| {
workspace
.register_action(spawn_task_or_modal)
.register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<DebugPanel>(window, cx);
})
.when(status == Some(ThreadStatus::Stopped), |div| {
div.on_action({
.register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
})
.register_action(
|workspace: &mut Workspace, _: &RerunLastSession, window, cx| {
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
return;
};
debug_panel.update(cx, |debug_panel, cx| {
debug_panel.rerun_last_session(workspace, window, cx);
})
},
)
.register_action(
|workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
workspace.project().update(cx, |project, cx| {
project.dap_store().update(cx, |store, cx| {
store.shutdown_sessions(cx).detach();
})
})
},
)
.register_action_renderer(|div, workspace, _, cx| {
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
return div;
};
let Some(active_item) = debug_panel
.read(cx)
.active_session()
.map(|session| session.read(cx).running_state().clone())
else {
return div;
};
let running_state = active_item.read(cx);
if running_state.session().read(cx).is_terminated() {
return div;
}
let caps = running_state.capabilities(cx);
let supports_step_back = caps.supports_step_back.unwrap_or_default();
let supports_detach = running_state.session().read(cx).is_attached();
let status = running_state.thread_status(cx);
let active_item = active_item.downgrade();
div.when(status == Some(ThreadStatus::Running), |div| {
let active_item = active_item.clone();
move |_: &StepInto, _, cx| {
active_item.update(cx, |item, cx| item.step_in(cx)).ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &StepOver, _, cx| {
active_item.update(cx, |item, cx| item.step_over(cx)).ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &StepOut, _, cx| {
active_item.update(cx, |item, cx| item.step_out(cx)).ok();
}
})
.when(supports_step_back, |div| {
let active_item = active_item.clone();
div.on_action(move |_: &StepBack, _, cx| {
active_item.update(cx, |item, cx| item.step_back(cx)).ok();
div.on_action(move |_: &Pause, _, cx| {
active_item
.update(cx, |item, cx| item.pause_thread(cx))
.ok();
})
})
.on_action({
let active_item = active_item.clone();
move |_: &Continue, _, cx| {
active_item
.update(cx, |item, cx| item.continue_thread(cx))
.ok();
}
})
.on_action(cx.listener(
|workspace, _: &ShowStackTrace, window, cx| {
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
return;
};
if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx) {
let is_active = workspace
.active_item(cx)
.is_some_and(|item| item.item_id() == existing.item_id());
workspace.activate_item(&existing, true, !is_active, window, cx);
} else {
let Some(active_session) = debug_panel.read(cx).active_session()
else {
.when(status == Some(ThreadStatus::Stopped), |div| {
div.on_action({
let active_item = active_item.clone();
move |_: &StepInto, _, cx| {
active_item.update(cx, |item, cx| item.step_in(cx)).ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &StepOver, _, cx| {
active_item.update(cx, |item, cx| item.step_over(cx)).ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &StepOut, _, cx| {
active_item.update(cx, |item, cx| item.step_out(cx)).ok();
}
})
.when(supports_step_back, |div| {
let active_item = active_item.clone();
div.on_action(move |_: &StepBack, _, cx| {
active_item.update(cx, |item, cx| item.step_back(cx)).ok();
})
})
.on_action({
let active_item = active_item.clone();
move |_: &Continue, _, cx| {
active_item
.update(cx, |item, cx| item.continue_thread(cx))
.ok();
}
})
.on_action(cx.listener(
|workspace, _: &ShowStackTrace, window, cx| {
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
return;
};
let project = workspace.project();
if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx)
{
let is_active = workspace
.active_item(cx)
.is_some_and(|item| item.item_id() == existing.item_id());
workspace
.activate_item(&existing, true, !is_active, window, cx);
} else {
let Some(active_session) =
debug_panel.read(cx).active_session()
else {
return;
};
let stack_trace_view = active_session.update(cx, |session, cx| {
session.stack_trace_view(project, window, cx).clone()
});
let project = workspace.project();
workspace.add_item_to_active_pane(
Box::new(stack_trace_view),
None,
true,
window,
cx,
);
}
},
))
})
.when(supports_detach, |div| {
let active_item = active_item.clone();
div.on_action(move |_: &Detach, _, cx| {
active_item
.update(cx, |item, cx| item.detach_client(cx))
.ok();
let stack_trace_view =
active_session.update(cx, |session, cx| {
session.stack_trace_view(project, window, cx).clone()
});
workspace.add_item_to_active_pane(
Box::new(stack_trace_view),
None,
true,
window,
cx,
);
}
},
))
})
})
.on_action({
let active_item = active_item.clone();
move |_: &Restart, _, cx| {
active_item
.update(cx, |item, cx| item.restart_session(cx))
.ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &Stop, _, cx| {
active_item.update(cx, |item, cx| item.stop_thread(cx)).ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &ToggleIgnoreBreakpoints, _, cx| {
active_item
.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
.ok();
}
})
});
.when(supports_detach, |div| {
let active_item = active_item.clone();
div.on_action(move |_: &Detach, _, cx| {
active_item
.update(cx, |item, cx| item.detach_client(cx))
.ok();
})
})
.on_action({
let active_item = active_item.clone();
move |_: &Restart, _, cx| {
active_item
.update(cx, |item, cx| item.restart_session(cx))
.ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &Stop, _, cx| {
active_item.update(cx, |item, cx| item.stop_thread(cx)).ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &ToggleIgnoreBreakpoints, _, cx| {
active_item
.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
.ok();
}
})
});
})
})
.detach();

View File

@@ -1,6 +1,5 @@
use std::time::Duration;
use collections::HashMap;
use gpui::{Animation, AnimationExt as _, Entity, Transformation, percentage};
use project::debugger::session::{ThreadId, ThreadStatus};
use ui::{ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
@@ -73,21 +72,10 @@ impl DebugPanel {
trigger,
ContextMenu::build(window, cx, move |mut this, _, cx| {
let context_menu = cx.weak_entity();
let mut session_depths = HashMap::default();
for session in sessions.into_iter() {
let weak_session = session.downgrade();
let weak_session_id = weak_session.entity_id();
let session_id = session.read(cx).session_id(cx);
let parent_depth = session
.read(cx)
.session(cx)
.read(cx)
.parent_id(cx)
.and_then(|parent_id| session_depths.get(&parent_id).cloned());
let self_depth =
*session_depths.entry(session_id).or_insert_with(|| {
parent_depth.map(|depth| depth + 1).unwrap_or(0usize)
});
this = this.custom_entry(
{
let weak = weak.clone();
@@ -96,16 +84,16 @@ impl DebugPanel {
weak_session
.read_with(cx, |session, cx| {
let context_menu = context_menu.clone();
let id: SharedString =
format!("debug-session-{}", session_id.0)
.into();
let id: SharedString = format!(
"debug-session-{}",
session.session_id(cx).0
)
.into();
h_flex()
.w_full()
.group(id.clone())
.justify_between()
.child(session.label_element(self_depth, cx))
.child(session.label_element(cx))
.child(
IconButton::new(
"close-debug-session",

View File

@@ -1,4 +1,3 @@
use anyhow::bail;
use collections::{FxHashMap, HashMap};
use language::LanguageRegistry;
use paths::local_debug_file_relative_path;
@@ -6,7 +5,6 @@ use std::{
borrow::Cow,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
usize,
};
use tasks_ui::{TaskOverrides, TasksModal};
@@ -28,22 +26,23 @@ use settings::{Settings, initial_local_debug_tasks_content};
use task::{DebugScenario, RevealTarget, ZedDebugConfig};
use theme::ThemeSettings;
use ui::{
ActiveTheme, CheckboxWithLabel, Clickable, Context, ContextMenu, Disableable, DropdownMenu,
FluentBuilder, IconWithIndicator, Indicator, IntoElement, KeyBinding, ListItem,
ListItemSpacing, ParentElement, StyledExt, ToggleButton, ToggleState, Toggleable, Tooltip,
Window, div, prelude::*, px, relative, rems,
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
ContextMenu, Disableable, DropdownMenu, FluentBuilder, Icon, IconName, IconSize,
IconWithIndicator, Indicator, InteractiveElement, IntoElement, KeyBinding, Label,
LabelCommon as _, LabelSize, ListItem, ListItemSpacing, ParentElement, RenderOnce,
SharedString, Styled, StyledExt, StyledTypography, ToggleButton, ToggleState, Toggleable,
Tooltip, Window, div, h_flex, px, relative, rems, v_flex,
};
use util::ResultExt;
use workspace::{ModalView, Workspace, pane};
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
#[allow(unused)]
enum SaveScenarioState {
Saving,
Saved((ProjectPath, SharedString)),
Failed(SharedString),
}
// enum SaveScenarioState {
// Saving,
// Saved((ProjectPath, SharedString)),
// Failed(SharedString),
// }
pub(super) struct NewProcessModal {
workspace: WeakEntity<Workspace>,
@@ -54,7 +53,7 @@ pub(super) struct NewProcessModal {
configure_mode: Entity<ConfigureMode>,
task_mode: TaskMode,
debugger: Option<DebugAdapterName>,
save_scenario_state: Option<SaveScenarioState>,
// save_scenario_state: Option<SaveScenarioState>,
_subscriptions: [Subscription; 3],
}
@@ -265,7 +264,7 @@ impl NewProcessModal {
mode,
debug_panel: debug_panel.downgrade(),
workspace: workspace_handle,
save_scenario_state: None,
// save_scenario_state: None,
_subscriptions,
}
});
@@ -308,16 +307,16 @@ impl NewProcessModal {
}
}
fn debug_scenario(&self, debugger: &str, cx: &App) -> Task<Option<DebugScenario>> {
fn debug_scenario(&self, debugger: &str, cx: &App) -> Option<DebugScenario> {
let request = match self.mode {
NewProcessMode::Launch => {
DebugRequest::Launch(self.configure_mode.read(cx).debug_request(cx))
}
NewProcessMode::Attach => {
DebugRequest::Attach(self.attach_mode.read(cx).debug_request())
}
_ => return Task::ready(None),
};
NewProcessMode::Launch => Some(DebugRequest::Launch(
self.configure_mode.read(cx).debug_request(cx),
)),
NewProcessMode::Attach => Some(DebugRequest::Attach(
self.attach_mode.read(cx).debug_request(),
)),
_ => None,
}?;
let label = suggested_label(&request, debugger);
let stop_on_entry = if let NewProcessMode::Launch = &self.mode {
@@ -329,15 +328,13 @@ impl NewProcessModal {
let session_scenario = ZedDebugConfig {
adapter: debugger.to_owned().into(),
label,
request,
request: request,
stop_on_entry,
};
let adapter = cx
.global::<DapRegistry>()
.adapter(&session_scenario.adapter);
cx.spawn(async move |_| adapter?.config_from_zed_format(session_scenario).await.ok())
cx.global::<DapRegistry>()
.adapter(&session_scenario.adapter)
.and_then(|adapter| adapter.config_from_zed_format(session_scenario).ok())
}
fn start_new_session(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -352,13 +349,19 @@ impl NewProcessModal {
return;
}
if let NewProcessMode::Launch = &self.mode {
if self.configure_mode.read(cx).save_to_debug_json.selected() {
self.save_debug_scenario(window, cx);
}
}
// TODO: Restore once we have proper, comment preserving edits
// if let NewProcessMode::Launch = &self.mode {
// if self.launch_mode.read(cx).save_to_debug_json.selected() {
// self.save_debug_scenario(window, cx);
// }
// }
let Some(debugger) = self.debugger.clone() else {
let Some(debugger) = self.debugger.as_ref() else {
return;
};
let Some(config) = self.debug_scenario(debugger, cx) else {
log::error!("debug config not found in mode: {}", self.mode);
return;
};
@@ -366,20 +369,11 @@ impl NewProcessModal {
let Some(task_contexts) = self.task_contexts(cx) else {
return;
};
send_telemetry(&config, TelemetrySpawnLocation::Custom, cx);
let task_context = task_contexts.active_context().cloned().unwrap_or_default();
let worktree_id = task_contexts.worktree();
let mode = self.mode;
cx.spawn_in(window, async move |this, cx| {
let Some(config) = this
.update(cx, |this, cx| this.debug_scenario(&debugger, cx))?
.await
else {
bail!("debug config not found in mode: {mode}");
};
debug_panel.update_in(cx, |debug_panel, window, cx| {
send_telemetry(&config, TelemetrySpawnLocation::Custom, cx);
debug_panel.start_session(config, task_context, None, worktree_id, window, cx)
})?;
this.update(cx, |_, cx| {
@@ -417,64 +411,47 @@ impl NewProcessModal {
self.debug_picker.read(cx).delegate.task_contexts.clone()
}
fn save_debug_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let task_contents = self.task_contexts(cx);
let Some(adapter) = self.debugger.as_ref() else {
return;
};
let scenario = self.debug_scenario(&adapter, cx);
// fn save_debug_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) {
// let Some((save_scenario, scenario_label)) = self
// .debugger
// .as_ref()
// .and_then(|debugger| self.debug_scenario(&debugger, cx))
// .zip(self.task_contexts(cx).and_then(|tcx| tcx.worktree()))
// .and_then(|(scenario, worktree_id)| {
// self.debug_panel
// .update(cx, |panel, cx| {
// panel.save_scenario(&scenario, worktree_id, window, cx)
// })
// .ok()
// .zip(Some(scenario.label.clone()))
// })
// else {
// return;
// };
self.save_scenario_state = Some(SaveScenarioState::Saving);
// self.save_scenario_state = Some(SaveScenarioState::Saving);
cx.spawn_in(window, async move |this, cx| {
let Some((scenario, worktree_id)) = scenario
.await
.zip(task_contents.and_then(|tcx| tcx.worktree()))
else {
this.update(cx, |this, _| {
this.save_scenario_state = Some(SaveScenarioState::Failed(
"Couldn't get scenario or task contents".into(),
))
})
.ok();
return;
};
// cx.spawn(async move |this, cx| {
// let res = save_scenario.await;
let Some(save_scenario) = this
.update_in(cx, |this, window, cx| {
this.debug_panel
.update(cx, |panel, cx| {
panel.save_scenario(&scenario, worktree_id, window, cx)
})
.ok()
})
.ok()
.flatten()
else {
return;
};
let res = save_scenario.await;
// this.update(cx, |this, _| match res {
// Ok(saved_file) => {
// this.save_scenario_state =
// Some(SaveScenarioState::Saved((saved_file, scenario_label)))
// }
// Err(error) => {
// this.save_scenario_state =
// Some(SaveScenarioState::Failed(error.to_string().into()))
// }
// })
// .ok();
this.update(cx, |this, _| match res {
Ok(saved_file) => {
this.save_scenario_state = Some(SaveScenarioState::Saved((
saved_file,
scenario.label.clone(),
)))
}
Err(error) => {
this.save_scenario_state =
Some(SaveScenarioState::Failed(error.to_string().into()))
}
})
.ok();
cx.background_executor().timer(Duration::from_secs(3)).await;
this.update(cx, |this, _| this.save_scenario_state.take())
.ok();
})
.detach();
}
// cx.background_executor().timer(Duration::from_secs(3)).await;
// this.update(cx, |this, _| this.save_scenario_state.take())
// .ok();
// })
// .detach();
// }
fn adapter_drop_down_menu(
&mut self,
@@ -609,7 +586,7 @@ impl NewProcessModal {
static SELECT_DEBUGGER_LABEL: SharedString = SharedString::new_static("Select Debugger");
#[derive(Clone, Copy)]
#[derive(Clone)]
pub(crate) enum NewProcessMode {
Task,
Launch,
@@ -681,16 +658,16 @@ impl Render for NewProcessModal {
cx: &mut ui::Context<Self>,
) -> impl ui::IntoElement {
v_flex()
.size_full()
.w(rems(34.))
.key_context({
let mut key_context = KeyContext::new_with_defaults();
key_context.add("Pane");
key_context.add("RunModal");
key_context
})
.size_full()
.w(rems(34.))
.elevation_3(cx)
.overflow_hidden()
.bg(cx.theme().colors().elevated_surface_background)
.on_action(cx.listener(|_, _: &menu::Cancel, _, cx| {
cx.emit(DismissEvent);
}))
@@ -718,93 +695,100 @@ impl Render for NewProcessModal {
)
.child(
h_flex()
.p_2()
.w_full()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.justify_around()
.p_2()
.child(
ToggleButton::new(
"debugger-session-ui-tasks-button",
NewProcessMode::Task.to_string(),
)
.size(ButtonSize::Default)
.toggle_state(matches!(self.mode, NewProcessMode::Task))
.style(ui::ButtonStyle::Subtle)
.on_click(cx.listener(|this, _, window, cx| {
this.mode = NewProcessMode::Task;
this.mode_focus_handle(cx).focus(window);
cx.notify();
}))
.tooltip(Tooltip::text("Run predefined task"))
.first(),
)
.child(
ToggleButton::new(
"debugger-session-ui-launch-button",
NewProcessMode::Debug.to_string(),
)
.size(ButtonSize::Default)
.style(ui::ButtonStyle::Subtle)
.toggle_state(matches!(self.mode, NewProcessMode::Debug))
.on_click(cx.listener(|this, _, window, cx| {
this.mode = NewProcessMode::Debug;
this.mode_focus_handle(cx).focus(window);
cx.notify();
}))
.tooltip(Tooltip::text("Start a predefined debug scenario"))
.middle(),
)
.child(
ToggleButton::new(
"debugger-session-ui-attach-button",
NewProcessMode::Attach.to_string(),
)
.size(ButtonSize::Default)
.toggle_state(matches!(self.mode, NewProcessMode::Attach))
.style(ui::ButtonStyle::Subtle)
.on_click(cx.listener(|this, _, window, cx| {
this.mode = NewProcessMode::Attach;
h_flex()
.justify_start()
.w_full()
.child(
ToggleButton::new(
"debugger-session-ui-tasks-button",
NewProcessMode::Task.to_string(),
)
.size(ButtonSize::Default)
.toggle_state(matches!(self.mode, NewProcessMode::Task))
.style(ui::ButtonStyle::Subtle)
.on_click(cx.listener(|this, _, window, cx| {
this.mode = NewProcessMode::Task;
this.mode_focus_handle(cx).focus(window);
cx.notify();
}))
.tooltip(Tooltip::text("Run predefined task"))
.first(),
)
.child(
ToggleButton::new(
"debugger-session-ui-launch-button",
NewProcessMode::Debug.to_string(),
)
.size(ButtonSize::Default)
.style(ui::ButtonStyle::Subtle)
.toggle_state(matches!(self.mode, NewProcessMode::Debug))
.on_click(cx.listener(|this, _, window, cx| {
this.mode = NewProcessMode::Debug;
this.mode_focus_handle(cx).focus(window);
cx.notify();
}))
.tooltip(Tooltip::text("Start a predefined debug scenario"))
.middle(),
)
.child(
ToggleButton::new(
"debugger-session-ui-attach-button",
NewProcessMode::Attach.to_string(),
)
.size(ButtonSize::Default)
.toggle_state(matches!(self.mode, NewProcessMode::Attach))
.style(ui::ButtonStyle::Subtle)
.on_click(cx.listener(|this, _, window, cx| {
this.mode = NewProcessMode::Attach;
if let Some(debugger) = this.debugger.as_ref() {
Self::update_attach_picker(
&this.attach_mode,
&debugger,
window,
cx,
);
}
this.mode_focus_handle(cx).focus(window);
cx.notify();
}))
.tooltip(Tooltip::text("Attach the debugger to a running process"))
.middle(),
if let Some(debugger) = this.debugger.as_ref() {
Self::update_attach_picker(
&this.attach_mode,
&debugger,
window,
cx,
);
}
this.mode_focus_handle(cx).focus(window);
cx.notify();
}))
.tooltip(Tooltip::text("Attach the debugger to a running process"))
.middle(),
)
.child(
ToggleButton::new(
"debugger-session-ui-custom-button",
NewProcessMode::Launch.to_string(),
)
.size(ButtonSize::Default)
.toggle_state(matches!(self.mode, NewProcessMode::Launch))
.style(ui::ButtonStyle::Subtle)
.on_click(cx.listener(|this, _, window, cx| {
this.mode = NewProcessMode::Launch;
this.mode_focus_handle(cx).focus(window);
cx.notify();
}))
.tooltip(Tooltip::text("Launch a new process with a debugger"))
.last(),
),
)
.child(
ToggleButton::new(
"debugger-session-ui-custom-button",
NewProcessMode::Launch.to_string(),
)
.size(ButtonSize::Default)
.toggle_state(matches!(self.mode, NewProcessMode::Launch))
.style(ui::ButtonStyle::Subtle)
.on_click(cx.listener(|this, _, window, cx| {
this.mode = NewProcessMode::Launch;
this.mode_focus_handle(cx).focus(window);
cx.notify();
}))
.tooltip(Tooltip::text("Launch a new process with a debugger"))
.last(),
),
.justify_between()
.border_color(cx.theme().colors().border_variant)
.border_b_1(),
)
.child(v_flex().child(self.render_mode(window, cx)))
.map(|el| {
let container = h_flex()
.w_full()
.p_1p5()
.gap_2()
.justify_between()
.gap_2()
.p_2()
.border_color(cx.theme().colors().border_variant)
.border_t_1()
.border_color(cx.theme().colors().border_variant);
.w_full();
match self.mode {
NewProcessMode::Launch => el.child(
container
@@ -816,7 +800,7 @@ impl Render for NewProcessModal {
InteractiveText::new(
"open-debug-json",
StyledText::new(
"Open .zed/debug.json for advanced configuration.",
"Open .zed/debug.json for advanced configuration",
)
.with_highlights([(
5..20,
@@ -912,7 +896,7 @@ pub(super) struct ConfigureMode {
program: Entity<Editor>,
cwd: Entity<Editor>,
stop_on_entry: ToggleState,
save_to_debug_json: ToggleState,
// save_to_debug_json: ToggleState,
}
impl ConfigureMode {
@@ -931,7 +915,7 @@ impl ConfigureMode {
program,
cwd,
stop_on_entry: ToggleState::Unselected,
save_to_debug_json: ToggleState::Unselected,
// save_to_debug_json: ToggleState::Unselected,
})
}
@@ -994,43 +978,35 @@ impl ConfigureMode {
v_flex()
.p_2()
.w_full()
.gap_2()
.gap_3()
.track_focus(&self.program.focus_handle(cx))
.child(
h_flex()
.gap_2()
.child(
Label::new("Debugger")
.size(LabelSize::Small)
.size(ui::LabelSize::Small)
.color(Color::Muted),
)
.gap(ui::DynamicSpacing::Base08.rems(cx))
.child(adapter_menu),
)
.child(
v_flex()
.gap_0p5()
.child(
Label::new("Program")
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(render_editor(&self.program, window, cx)),
Label::new("Program")
.size(ui::LabelSize::Small)
.color(Color::Muted),
)
.child(render_editor(&self.program, window, cx))
.child(
v_flex()
.gap_0p5()
.child(
Label::new("Working Directory")
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(render_editor(&self.cwd, window, cx)),
Label::new("Working Directory")
.size(ui::LabelSize::Small)
.color(Color::Muted),
)
.child(render_editor(&self.cwd, window, cx))
.child(
CheckboxWithLabel::new(
"debugger-stop-on-entry",
Label::new("Stop on Entry")
.size(LabelSize::Small)
.size(ui::LabelSize::Small)
.color(Color::Muted),
self.stop_on_entry,
{
@@ -1045,25 +1021,27 @@ impl ConfigureMode {
)
.checkbox_position(ui::IconPosition::End),
)
.child(
CheckboxWithLabel::new(
"debugger-save-to-debug-json",
Label::new("Save to debug.json")
.size(LabelSize::Small)
.color(Color::Muted),
self.save_to_debug_json,
{
let this = cx.weak_entity();
move |state, _, cx| {
this.update(cx, |this, _| {
this.save_to_debug_json = *state;
})
.ok();
}
},
)
.checkbox_position(ui::IconPosition::End),
)
// TODO: restore once we have proper, comment preserving
// file edits.
// .child(
// CheckboxWithLabel::new(
// "debugger-save-to-debug-json",
// Label::new("Save to debug.json")
// .size(ui::LabelSize::Small)
// .color(Color::Muted),
// self.save_to_debug_json,
// {
// let this = cx.weak_entity();
// move |state, _, cx| {
// this.update(cx, |this, _| {
// this.save_to_debug_json = *state;
// })
// .ok();
// }
// },
// )
// .checkbox_position(ui::IconPosition::End),
// )
}
}
@@ -1278,7 +1256,6 @@ impl PickerDelegate for DebugDelegate {
&candidates,
&query,
true,
true,
1000,
&Default::default(),
cx.background_executor().clone(),
@@ -1471,14 +1448,17 @@ impl PickerDelegate for DebugDelegate {
let current_modifiers = window.modifiers();
let footer = h_flex()
.w_full()
.p_1p5()
.justify_end()
.h_8()
.p_2()
.justify_between()
.rounded_b_sm()
.bg(cx.theme().colors().ghost_element_selected)
.border_t_1()
.border_color(cx.theme().colors().border_variant)
// .child(
// // TODO: add button to open selected task in debug.json
// h_flex().into_any_element(),
// )
.child(
// TODO: add button to open selected task in debug.json
h_flex().into_any_element(),
)
.map(|this| {
if (current_modifiers.alt || self.matches.is_empty()) && !self.prompt.is_empty() {
let action = picker::ConfirmInput {
@@ -1487,6 +1467,7 @@ impl PickerDelegate for DebugDelegate {
.boxed_clone();
this.children(KeyBinding::for_action(&*action, window, cx).map(|keybind| {
Button::new("launch-custom", "Launch Custom")
.label_size(LabelSize::Small)
.key_binding(keybind)
.on_click(move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx)
@@ -1501,6 +1482,7 @@ impl PickerDelegate for DebugDelegate {
if is_recent_selected { "Rerun" } else { "Spawn" };
Button::new("spawn", run_entry_label)
.label_size(LabelSize::Small)
.key_binding(keybind)
.on_click(|_, window, cx| {
window.dispatch_action(menu::Confirm.boxed_clone(), cx);

View File

@@ -1,166 +0,0 @@
use gpui::{
ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseDownEvent, Render,
};
use ui::{TintColor, Vector, VectorName, prelude::*};
use workspace::{ModalView, Workspace};
use crate::DebugPanel;
macro_rules! debugger_onboarding_event {
($name:expr) => {
telemetry::event!($name, source = "Debugger Onboarding");
};
($name:expr, $($key:ident $(= $value:expr)?),+ $(,)?) => {
telemetry::event!($name, source = "Debugger Onboarding", $($key $(= $value)?),+);
};
}
pub struct DebuggerOnboardingModal {
focus_handle: FocusHandle,
workspace: Entity<Workspace>,
}
impl DebuggerOnboardingModal {
pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
let workspace_entity = cx.entity();
workspace.toggle_modal(window, cx, |_window, cx| Self {
workspace: workspace_entity,
focus_handle: cx.focus_handle(),
});
}
fn open_panel(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
self.workspace.update(cx, |workspace, cx| {
workspace.focus_panel::<DebugPanel>(window, cx);
});
cx.emit(DismissEvent);
debugger_onboarding_event!("Open Panel Clicked");
}
fn view_blog(&mut self, _: &ClickEvent, _: &mut Window, cx: &mut Context<Self>) {
cx.open_url("http://zed.dev/blog/debugger");
cx.notify();
debugger_onboarding_event!("Blog Link Clicked");
}
fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(DismissEvent);
}
}
impl EventEmitter<DismissEvent> for DebuggerOnboardingModal {}
impl Focusable for DebuggerOnboardingModal {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl ModalView for DebuggerOnboardingModal {}
impl Render for DebuggerOnboardingModal {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let window_height = window.viewport_size().height;
let max_height = window_height - px(200.);
let base = v_flex()
.id("debugger-onboarding")
.key_context("DebuggerOnboardingModal")
.relative()
.w(px(450.))
.h_full()
.max_h(max_height)
.p_4()
.gap_2()
.elevation_3(cx)
.track_focus(&self.focus_handle(cx))
.overflow_hidden()
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(|_, _: &menu::Cancel, _window, cx| {
debugger_onboarding_event!("Canceled", trigger = "Action");
cx.emit(DismissEvent);
}))
.on_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, window, _cx| {
this.focus_handle.focus(window);
}))
.child(
div()
.absolute()
.top(px(-8.0))
.right_0()
.w(px(400.))
.h(px(92.))
.child(
Vector::new(
VectorName::DebuggerGrid,
rems_from_px(400.),
rems_from_px(92.),
)
.color(ui::Color::Custom(cx.theme().colors().text.alpha(0.32))),
),
)
.child(
div()
.absolute()
.inset_0()
.size_full()
.bg(gpui::linear_gradient(
175.,
gpui::linear_color_stop(
cx.theme().colors().elevated_surface_background,
0.,
),
gpui::linear_color_stop(
cx.theme().colors().elevated_surface_background.opacity(0.),
0.8,
),
)),
)
.child(
v_flex()
.w_full()
.gap_1()
.child(
Label::new("Introducing")
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(Headline::new("Zed's Debugger").size(HeadlineSize::Large)),
)
.child(h_flex().absolute().top_2().right_2().child(
IconButton::new("cancel", IconName::X).on_click(cx.listener(
|_, _: &ClickEvent, _window, cx| {
debugger_onboarding_event!("Cancelled", trigger = "X click");
cx.emit(DismissEvent);
},
)),
));
let open_panel_button = Button::new("open-panel", "Get Started with the Debugger")
.icon_size(IconSize::Indicator)
.style(ButtonStyle::Tinted(TintColor::Accent))
.full_width()
.on_click(cx.listener(Self::open_panel));
let blog_post_button = Button::new("view-blog", "Check out the Blog Post")
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::Indicator)
.icon_color(Color::Muted)
.full_width()
.on_click(cx.listener(Self::view_blog));
let copy = "It's finally here: Native support for debugging across multiple programming languages.";
base.child(Label::new(copy).color(Color::Muted)).child(
v_flex()
.w_full()
.mt_2()
.gap_2()
.child(open_panel_button)
.child(blog_post_button),
)
}
}

View File

@@ -265,37 +265,56 @@ pub(crate) fn deserialize_pane_layout(
stack_frame_list.focus_handle(cx),
stack_frame_list.clone().into(),
DebuggerPaneItem::Frames,
None,
cx,
)),
DebuggerPaneItem::Variables => Box::new(SubView::new(
variable_list.focus_handle(cx),
variable_list.clone().into(),
DebuggerPaneItem::Variables,
None,
cx,
)),
DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
breakpoint_list.focus_handle(cx),
breakpoint_list.clone().into(),
DebuggerPaneItem::BreakpointList,
None,
cx,
)),
DebuggerPaneItem::BreakpointList => {
Box::new(SubView::breakpoint_list(breakpoint_list.clone(), cx))
}
DebuggerPaneItem::Modules => Box::new(SubView::new(
module_list.focus_handle(cx),
module_list.clone().into(),
DebuggerPaneItem::Modules,
None,
cx,
)),
DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
loaded_sources.focus_handle(cx),
loaded_sources.clone().into(),
DebuggerPaneItem::LoadedSources,
None,
cx,
)),
DebuggerPaneItem::Console => Box::new(SubView::new(
console.focus_handle(cx),
console.clone().into(),
DebuggerPaneItem::Console,
Some(Box::new({
let console = console.clone().downgrade();
move |cx| {
console
.read_with(cx, |console, cx| console.show_indicator(cx))
.unwrap_or_default()
}
})),
cx,
)),
DebuggerPaneItem::Console => {
let view = SubView::console(console.clone(), cx);
Box::new(view)
}
DebuggerPaneItem::Terminal => Box::new(SubView::new(
terminal.focus_handle(cx),
terminal.clone().into(),
DebuggerPaneItem::Terminal,
None,
cx,
)),
})

View File

@@ -125,7 +125,7 @@ impl DebugSession {
&self.running_state
}
pub(crate) fn label_element(&self, depth: usize, cx: &App) -> AnyElement {
pub(crate) fn label_element(&self, cx: &App) -> AnyElement {
let label = self.label(cx);
let is_terminated = self
@@ -153,7 +153,6 @@ impl DebugSession {
};
h_flex()
.ml(depth * px(16.0))
.gap_2()
.when_some(icon, |this, indicator| this.child(indicator))
.justify_between()

View File

@@ -135,7 +135,6 @@ pub(crate) struct SubView {
item_focus_handle: FocusHandle,
kind: DebuggerPaneItem,
show_indicator: Box<dyn Fn(&App) -> bool>,
actions: Option<Box<dyn FnMut(&mut Window, &mut App) -> AnyElement>>,
hovered: bool,
}
@@ -144,68 +143,21 @@ impl SubView {
item_focus_handle: FocusHandle,
view: AnyView,
kind: DebuggerPaneItem,
show_indicator: Option<Box<dyn Fn(&App) -> bool>>,
cx: &mut App,
) -> Entity<Self> {
cx.new(|_| Self {
kind,
inner: view,
item_focus_handle,
show_indicator: Box::new(|_| false),
actions: None,
show_indicator: show_indicator.unwrap_or(Box::new(|_| false)),
hovered: false,
})
}
pub(crate) fn console(console: Entity<Console>, cx: &mut App) -> Entity<Self> {
let weak_console = console.downgrade();
let this = Self::new(
console.focus_handle(cx),
console.into(),
DebuggerPaneItem::Console,
cx,
);
this.update(cx, |this, _| {
this.with_indicator(Box::new(move |cx| {
weak_console
.read_with(cx, |console, cx| console.show_indicator(cx))
.unwrap_or_default()
}))
});
this
}
pub(crate) fn breakpoint_list(list: Entity<BreakpointList>, cx: &mut App) -> Entity<Self> {
let weak_list = list.downgrade();
let focus_handle = list.focus_handle(cx);
let this = Self::new(
focus_handle.clone(),
list.into(),
DebuggerPaneItem::BreakpointList,
cx,
);
this.update(cx, |this, _| {
this.with_actions(Box::new(move |_, cx| {
weak_list
.update(cx, |this, _| this.render_control_strip())
.unwrap_or_else(|_| div().into_any_element())
}));
});
this
}
pub(crate) fn view_kind(&self) -> DebuggerPaneItem {
self.kind
}
pub(crate) fn with_indicator(&mut self, indicator: Box<dyn Fn(&App) -> bool>) {
self.show_indicator = indicator;
}
pub(crate) fn with_actions(
&mut self,
actions: Box<dyn FnMut(&mut Window, &mut App) -> AnyElement>,
) {
self.actions = Some(actions);
}
}
impl Focusable for SubView {
fn focus_handle(&self, _: &App) -> FocusHandle {
@@ -407,13 +359,10 @@ pub(crate) fn new_debugger_pane(
let active_pane_item = pane.active_item();
let pane_group_id: SharedString =
format!("pane-zoom-button-hover-{}", cx.entity_id()).into();
let as_subview = active_pane_item
.as_ref()
.and_then(|item| item.downcast::<SubView>());
let is_hovered = as_subview
.as_ref()
.map_or(false, |item| item.read(cx).hovered);
let is_hovered = active_pane_item.as_ref().map_or(false, |item| {
item.downcast::<SubView>()
.map_or(false, |this| this.read(cx).hovered)
});
h_flex()
.group(pane_group_id.clone())
.justify_between()
@@ -510,17 +459,9 @@ pub(crate) fn new_debugger_pane(
)
.child({
let zoomed = pane.is_zoomed();
h_flex()
div()
.visible_on_hover(pane_group_id)
.when(is_hovered, |this| this.visible())
.when_some(as_subview.as_ref(), |this, subview| {
subview.update(cx, |view, cx| {
let Some(additional_actions) = view.actions.as_mut() else {
return this;
};
this.child(additional_actions(window, cx))
})
})
.child(
IconButton::new(
SharedString::from(format!(
@@ -697,8 +638,7 @@ impl RunningState {
)
});
let breakpoint_list =
BreakpointList::new(Some(session.clone()), workspace.clone(), &project, cx);
let breakpoint_list = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
let _subscriptions = vec![
cx.observe(&module_list, |_, _, cx| cx.notify()),
@@ -876,13 +816,10 @@ impl RunningState {
Self::relativize_paths(None, &mut config, &task_context);
Self::substitute_variables_in_config(&mut config, &task_context);
let request_type = match dap_registry
let request_type = dap_registry
.adapter(&adapter)
.with_context(|| format!("{}: is not a valid adapter name", &adapter)) {
Ok(adapter) => adapter.request_kind(&config).await,
Err(e) => Err(e)
};
.with_context(|| format!("{}: is not a valid adapter name", &adapter))
.and_then(|adapter| adapter.request_kind(&config));
let config_is_valid = request_type.is_ok();
@@ -1021,8 +958,8 @@ impl RunningState {
let scenario = dap_registry
.adapter(&adapter)
.with_context(|| anyhow!("{}: is not a valid adapter name", &adapter))?.config_from_zed_format(zed_config)
.await?;
.with_context(|| anyhow!("{}: is not a valid adapter name", &adapter))
.map(|adapter| adapter.config_from_zed_format(zed_config))??;
config = scenario.config;
Self::substitute_variables_in_config(&mut config, &task_context);
} else {
@@ -1154,38 +1091,61 @@ impl RunningState {
cx: &mut Context<Self>,
) -> Box<dyn ItemHandle> {
match item_kind {
DebuggerPaneItem::Console => Box::new(SubView::console(self.console.clone(), cx)),
DebuggerPaneItem::Console => {
let weak_console = self.console.clone().downgrade();
Box::new(SubView::new(
self.console.focus_handle(cx),
self.console.clone().into(),
item_kind,
Some(Box::new(move |cx| {
weak_console
.read_with(cx, |console, cx| console.show_indicator(cx))
.unwrap_or_default()
})),
cx,
))
}
DebuggerPaneItem::Variables => Box::new(SubView::new(
self.variable_list.focus_handle(cx),
self.variable_list.clone().into(),
item_kind,
None,
cx,
)),
DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
self.breakpoint_list.focus_handle(cx),
self.breakpoint_list.clone().into(),
item_kind,
None,
cx,
)),
DebuggerPaneItem::BreakpointList => {
Box::new(SubView::breakpoint_list(self.breakpoint_list.clone(), cx))
}
DebuggerPaneItem::Frames => Box::new(SubView::new(
self.stack_frame_list.focus_handle(cx),
self.stack_frame_list.clone().into(),
item_kind,
None,
cx,
)),
DebuggerPaneItem::Modules => Box::new(SubView::new(
self.module_list.focus_handle(cx),
self.module_list.clone().into(),
item_kind,
None,
cx,
)),
DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
self.loaded_sources_list.focus_handle(cx),
self.loaded_sources_list.clone().into(),
item_kind,
None,
cx,
)),
DebuggerPaneItem::Terminal => Box::new(SubView::new(
self.debug_terminal.focus_handle(cx),
self.debug_terminal.clone().into(),
item_kind,
None,
cx,
)),
}
@@ -1594,6 +1554,7 @@ impl RunningState {
this.focus_handle(cx),
stack_frame_list.clone().into(),
DebuggerPaneItem::Frames,
None,
cx,
)),
true,
@@ -1603,7 +1564,13 @@ impl RunningState {
cx,
);
this.add_item(
Box::new(SubView::breakpoint_list(breakpoints.clone(), cx)),
Box::new(SubView::new(
breakpoints.focus_handle(cx),
breakpoints.clone().into(),
DebuggerPaneItem::BreakpointList,
None,
cx,
)),
true,
false,
None,
@@ -1615,15 +1582,32 @@ impl RunningState {
let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
center_pane.update(cx, |this, cx| {
let view = SubView::console(console.clone(), cx);
this.add_item(Box::new(view), true, false, None, window, cx);
let weak_console = console.downgrade();
this.add_item(
Box::new(SubView::new(
console.focus_handle(cx),
console.clone().into(),
DebuggerPaneItem::Console,
Some(Box::new(move |cx| {
weak_console
.read_with(cx, |console, cx| console.show_indicator(cx))
.unwrap_or_default()
})),
cx,
)),
true,
false,
None,
window,
cx,
);
this.add_item(
Box::new(SubView::new(
variable_list.focus_handle(cx),
variable_list.clone().into(),
DebuggerPaneItem::Variables,
None,
cx,
)),
true,
@@ -1642,6 +1626,7 @@ impl RunningState {
debug_terminal.focus_handle(cx),
debug_terminal.clone().into(),
DebuggerPaneItem::Terminal,
None,
cx,
)),
false,

View File

@@ -8,8 +8,8 @@ use std::{
use dap::ExceptionBreakpointsFilter;
use editor::Editor;
use gpui::{
Action, AppContext, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy, Stateful,
Task, UniformListScrollHandle, WeakEntity, uniform_list,
AppContext, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy, Stateful, Task,
UniformListScrollHandle, WeakEntity, uniform_list,
};
use language::Point;
use project::{
@@ -21,28 +21,22 @@ use project::{
worktree_store::WorktreeStore,
};
use ui::{
AnyElement, App, ButtonCommon, Clickable, Color, Context, Disableable, Div, FluentBuilder as _,
Icon, IconButton, IconName, IconSize, Indicator, InteractiveElement, IntoElement, Label,
LabelCommon, LabelSize, ListItem, ParentElement, Render, Scrollbar, ScrollbarState,
SharedString, StatefulInteractiveElement, Styled, Toggleable, Tooltip, Window, div, h_flex, px,
v_flex,
App, ButtonCommon, Clickable, Color, Context, Div, FluentBuilder as _, Icon, IconButton,
IconName, Indicator, InteractiveElement, IntoElement, Label, LabelCommon, LabelSize, ListItem,
ParentElement, Render, Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement,
Styled, Toggleable, Tooltip, Window, div, h_flex, px, v_flex,
};
use util::ResultExt;
use workspace::Workspace;
use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint};
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum SelectedBreakpointKind {
Source,
Exception,
}
pub(crate) struct BreakpointList {
workspace: WeakEntity<Workspace>,
breakpoint_store: Entity<BreakpointStore>,
worktree_store: Entity<WorktreeStore>,
scrollbar_state: ScrollbarState,
breakpoints: Vec<BreakpointEntry>,
session: Option<Entity<Session>>,
session: Entity<Session>,
hide_scrollbar_task: Option<Task<()>>,
show_scrollbar: bool,
focus_handle: FocusHandle,
@@ -57,8 +51,8 @@ impl Focusable for BreakpointList {
}
impl BreakpointList {
pub(crate) fn new(
session: Option<Entity<Session>>,
pub(super) fn new(
session: Entity<Session>,
workspace: WeakEntity<Workspace>,
project: &Entity<Project>,
cx: &mut App,
@@ -70,18 +64,21 @@ impl BreakpointList {
let scroll_handle = UniformListScrollHandle::new();
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
cx.new(|_| Self {
breakpoint_store,
worktree_store,
scrollbar_state,
breakpoints: Default::default(),
hide_scrollbar_task: None,
show_scrollbar: false,
workspace,
session,
focus_handle,
scroll_handle,
selected_ix: None,
cx.new(|_| {
Self {
breakpoint_store,
worktree_store,
scrollbar_state,
// list_state,
breakpoints: Default::default(),
hide_scrollbar_task: None,
show_scrollbar: false,
workspace,
session,
focus_handle,
scroll_handle,
selected_ix: None,
}
})
}
@@ -133,21 +130,6 @@ impl BreakpointList {
.detach();
}
pub(crate) fn selection_kind(&self) -> Option<(SelectedBreakpointKind, bool)> {
self.selected_ix.and_then(|ix| {
self.breakpoints.get(ix).map(|bp| match &bp.kind {
BreakpointEntryKind::LineBreakpoint(bp) => (
SelectedBreakpointKind::Source,
bp.breakpoint.state
== project::debugger::breakpoint_store::BreakpointState::Enabled,
),
BreakpointEntryKind::ExceptionBreakpoint(bp) => {
(SelectedBreakpointKind::Exception, bp.is_enabled)
}
})
})
}
fn select_ix(&mut self, ix: Option<usize>, cx: &mut Context<Self>) {
self.selected_ix = ix;
if let Some(ix) = ix {
@@ -247,12 +229,10 @@ impl BreakpointList {
self.edit_line_breakpoint(path, row, BreakpointEditAction::InvertState, cx);
}
BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => {
if let Some(session) = &self.session {
let id = exception_breakpoint.id.clone();
session.update(cx, |session, cx| {
session.toggle_exception_breakpoint(&id, cx);
});
}
let id = exception_breakpoint.id.clone();
self.session.update(cx, |session, cx| {
session.toggle_exception_breakpoint(&id, cx);
});
}
}
cx.notify();
@@ -354,93 +334,7 @@ impl BreakpointList {
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
)
}
pub(crate) fn render_control_strip(&self) -> AnyElement {
let selection_kind = self.selection_kind();
let focus_handle = self.focus_handle.clone();
let remove_breakpoint_tooltip = selection_kind.map(|(kind, _)| match kind {
SelectedBreakpointKind::Source => "Remove breakpoint from a breakpoint list",
SelectedBreakpointKind::Exception => {
"Exception Breakpoints cannot be removed from the breakpoint list"
}
});
let toggle_label = selection_kind.map(|(_, is_enabled)| {
if is_enabled {
(
"Disable Breakpoint",
"Disable a breakpoint without removing it from the list",
)
} else {
("Enable Breakpoint", "Re-enable a breakpoint")
}
});
h_flex()
.gap_2()
.child(
IconButton::new(
"disable-breakpoint-breakpoint-list",
IconName::DebugDisabledBreakpoint,
)
.icon_size(IconSize::XSmall)
.when_some(toggle_label, |this, (label, meta)| {
this.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::with_meta_in(
label,
Some(&ToggleEnableBreakpoint),
meta,
&focus_handle,
window,
cx,
)
}
})
})
.disabled(selection_kind.is_none())
.on_click({
let focus_handle = focus_handle.clone();
move |_, window, cx| {
focus_handle.focus(window);
window.dispatch_action(ToggleEnableBreakpoint.boxed_clone(), cx)
}
}),
)
.child(
IconButton::new("remove-breakpoint-breakpoint-list", IconName::X)
.icon_size(IconSize::XSmall)
.icon_color(ui::Color::Error)
.when_some(remove_breakpoint_tooltip, |this, tooltip| {
this.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::with_meta_in(
"Remove Breakpoint",
Some(&UnsetBreakpoint),
tooltip,
&focus_handle,
window,
cx,
)
}
})
})
.disabled(
selection_kind.map(|kind| kind.0) != Some(SelectedBreakpointKind::Source),
)
.on_click({
let focus_handle = focus_handle.clone();
move |_, window, cx| {
focus_handle.focus(window);
window.dispatch_action(UnsetBreakpoint.boxed_clone(), cx)
}
}),
)
.mr_2()
.into_any_element()
}
}
impl Render for BreakpointList {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
// let old_len = self.breakpoints.len();
@@ -491,8 +385,8 @@ impl Render for BreakpointList {
})
})
});
let exception_breakpoints = self.session.as_ref().into_iter().flat_map(|session| {
session
let exception_breakpoints =
self.session
.read(cx)
.exception_breakpoints()
.map(|(data, is_enabled)| BreakpointEntry {
@@ -502,8 +396,7 @@ impl Render for BreakpointList {
is_enabled: *is_enabled,
}),
weak: weak.clone(),
})
});
});
self.breakpoints
.extend(breakpoints.chain(exception_breakpoints));
v_flex()
@@ -612,11 +505,45 @@ impl LineBreakpoint {
.on_secondary_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.end_hover_slot(
IconButton::new(
SharedString::from(format!(
"breakpoint-ui-on-click-go-to-line-remove-{:?}/{}:{}",
self.dir, self.name, self.line
)),
IconName::Close,
)
.on_click({
let weak = weak.clone();
let path = path.clone();
move |_, _, cx| {
weak.update(cx, |breakpoint_list, cx| {
breakpoint_list.edit_line_breakpoint(
path.clone(),
row,
BreakpointEditAction::Toggle,
cx,
);
})
.ok();
}
})
.tooltip(move |window, cx| {
Tooltip::for_action_in(
"Unset Breakpoint",
&UnsetBreakpoint,
&focus_handle,
window,
cx,
)
})
.icon_size(ui::IconSize::Indicator),
)
.child(
v_flex()
.py_1()
.gap_1()
.min_h(px(26.))
.min_h(px(22.))
.justify_center()
.id(SharedString::from(format!(
"breakpoint-ui-on-click-go-to-line-{:?}/{}:{}",
@@ -708,12 +635,10 @@ impl ExceptionBreakpoint {
let list = list.clone();
move |_, _, cx| {
list.update(cx, |this, cx| {
if let Some(session) = &this.session {
session.update(cx, |this, cx| {
this.toggle_exception_breakpoint(&id, cx);
});
cx.notify();
}
this.session.update(cx, |this, cx| {
this.toggle_exception_breakpoint(&id, cx);
});
cx.notify();
})
.ok();
}
@@ -725,7 +650,7 @@ impl ExceptionBreakpoint {
v_flex()
.py_1()
.gap_1()
.min_h(px(26.))
.min_h(px(22.))
.justify_center()
.id(("exception-breakpoint-label", ix))
.child(

View File

@@ -2,15 +2,13 @@ use super::{
stack_frame_list::{StackFrameList, StackFrameListEvent},
variable_list::VariableList,
};
use alacritty_terminal::vte::ansi;
use anyhow::Result;
use collections::HashMap;
use dap::OutputEvent;
use editor::{Bias, CompletionProvider, Editor, EditorElement, EditorStyle, ExcerptId};
use fuzzy::StringMatchCandidate;
use gpui::{
Context, Entity, FocusHandle, Focusable, HighlightStyle, Hsla, Render, Subscription, Task,
TextStyle, WeakEntity,
Context, Entity, FocusHandle, Focusable, Render, Subscription, Task, TextStyle, WeakEntity,
};
use language::{Buffer, CodeLabel, ToOffset};
use menu::Confirm;
@@ -19,8 +17,8 @@ use project::{
debugger::session::{CompletionsQuery, OutputToken, Session, SessionEvent},
};
use settings::Settings;
use std::{cell::RefCell, ops::Range, rc::Rc, usize};
use theme::{Theme, ThemeSettings};
use std::{cell::RefCell, rc::Rc, usize};
use theme::ThemeSettings;
use ui::{Divider, prelude::*};
pub struct Console {
@@ -138,193 +136,18 @@ impl Console {
cx: &mut App,
) {
self.console.update(cx, |console, cx| {
console.set_read_only(false);
let mut to_insert = String::default();
for event in events {
let to_insert = format!("{}\n", event.output.trim_end());
use std::fmt::Write;
let mut ansi_handler = ConsoleHandler::default();
let mut ansi_processor = ansi::Processor::<ansi::StdSyncHandler>::default();
let len = console.buffer().read(cx).len(cx);
ansi_processor.advance(&mut ansi_handler, to_insert.as_bytes());
let output = std::mem::take(&mut ansi_handler.output);
let mut spans = std::mem::take(&mut ansi_handler.spans);
let mut background_spans = std::mem::take(&mut ansi_handler.background_spans);
if ansi_handler.current_range_start < output.len() {
spans.push((
ansi_handler.current_range_start..output.len(),
ansi_handler.current_color,
));
}
if ansi_handler.current_background_range_start < output.len() {
background_spans.push((
ansi_handler.current_background_range_start..output.len(),
ansi_handler.current_background_color,
));
}
console.move_to_end(&editor::actions::MoveToEnd, window, cx);
console.insert(&output, window, cx);
let buffer = console.buffer().read(cx).snapshot(cx);
struct ConsoleAnsiHighlight;
for (range, color) in spans {
let Some(color) = color else { continue };
let start_offset = len + range.start;
let range = start_offset..len + range.end;
let range = buffer.anchor_after(range.start)..buffer.anchor_before(range.end);
let style = HighlightStyle {
color: Some(terminal_view::terminal_element::convert_color(
&color,
cx.theme(),
)),
..Default::default()
};
console.highlight_text_key::<ConsoleAnsiHighlight>(
start_offset,
vec![range],
style,
cx,
);
}
for (range, color) in background_spans {
let Some(color) = color else { continue };
let start_offset = len + range.start;
let range = start_offset..len + range.end;
let range = buffer.anchor_after(range.start)..buffer.anchor_before(range.end);
let color_fetcher: fn(&Theme) -> Hsla = match color {
// Named and theme defined colors
ansi::Color::Named(n) => match n {
ansi::NamedColor::Black => |theme| theme.colors().terminal_ansi_black,
ansi::NamedColor::Red => |theme| theme.colors().terminal_ansi_red,
ansi::NamedColor::Green => |theme| theme.colors().terminal_ansi_green,
ansi::NamedColor::Yellow => |theme| theme.colors().terminal_ansi_yellow,
ansi::NamedColor::Blue => |theme| theme.colors().terminal_ansi_blue,
ansi::NamedColor::Magenta => {
|theme| theme.colors().terminal_ansi_magenta
}
ansi::NamedColor::Cyan => |theme| theme.colors().terminal_ansi_cyan,
ansi::NamedColor::White => |theme| theme.colors().terminal_ansi_white,
ansi::NamedColor::BrightBlack => {
|theme| theme.colors().terminal_ansi_bright_black
}
ansi::NamedColor::BrightRed => {
|theme| theme.colors().terminal_ansi_bright_red
}
ansi::NamedColor::BrightGreen => {
|theme| theme.colors().terminal_ansi_bright_green
}
ansi::NamedColor::BrightYellow => {
|theme| theme.colors().terminal_ansi_bright_yellow
}
ansi::NamedColor::BrightBlue => {
|theme| theme.colors().terminal_ansi_bright_blue
}
ansi::NamedColor::BrightMagenta => {
|theme| theme.colors().terminal_ansi_bright_magenta
}
ansi::NamedColor::BrightCyan => {
|theme| theme.colors().terminal_ansi_bright_cyan
}
ansi::NamedColor::BrightWhite => {
|theme| theme.colors().terminal_ansi_bright_white
}
ansi::NamedColor::Foreground => {
|theme| theme.colors().terminal_foreground
}
ansi::NamedColor::Background => {
|theme| theme.colors().terminal_background
}
ansi::NamedColor::Cursor => |theme| theme.players().local().cursor,
ansi::NamedColor::DimBlack => {
|theme| theme.colors().terminal_ansi_dim_black
}
ansi::NamedColor::DimRed => {
|theme| theme.colors().terminal_ansi_dim_red
}
ansi::NamedColor::DimGreen => {
|theme| theme.colors().terminal_ansi_dim_green
}
ansi::NamedColor::DimYellow => {
|theme| theme.colors().terminal_ansi_dim_yellow
}
ansi::NamedColor::DimBlue => {
|theme| theme.colors().terminal_ansi_dim_blue
}
ansi::NamedColor::DimMagenta => {
|theme| theme.colors().terminal_ansi_dim_magenta
}
ansi::NamedColor::DimCyan => {
|theme| theme.colors().terminal_ansi_dim_cyan
}
ansi::NamedColor::DimWhite => {
|theme| theme.colors().terminal_ansi_dim_white
}
ansi::NamedColor::BrightForeground => {
|theme| theme.colors().terminal_bright_foreground
}
ansi::NamedColor::DimForeground => {
|theme| theme.colors().terminal_dim_foreground
}
},
// 'True' colors
ansi::Color::Spec(_) => |theme| theme.colors().editor_background,
// 8 bit, indexed colors
ansi::Color::Indexed(i) => {
match i {
// 0-15 are the same as the named colors above
0 => |theme| theme.colors().terminal_ansi_black,
1 => |theme| theme.colors().terminal_ansi_red,
2 => |theme| theme.colors().terminal_ansi_green,
3 => |theme| theme.colors().terminal_ansi_yellow,
4 => |theme| theme.colors().terminal_ansi_blue,
5 => |theme| theme.colors().terminal_ansi_magenta,
6 => |theme| theme.colors().terminal_ansi_cyan,
7 => |theme| theme.colors().terminal_ansi_white,
8 => |theme| theme.colors().terminal_ansi_bright_black,
9 => |theme| theme.colors().terminal_ansi_bright_red,
10 => |theme| theme.colors().terminal_ansi_bright_green,
11 => |theme| theme.colors().terminal_ansi_bright_yellow,
12 => |theme| theme.colors().terminal_ansi_bright_blue,
13 => |theme| theme.colors().terminal_ansi_bright_magenta,
14 => |theme| theme.colors().terminal_ansi_bright_cyan,
15 => |theme| theme.colors().terminal_ansi_bright_white,
// 16-231 are a 6x6x6 RGB color cube, mapped to 0-255 using steps defined by XTerm.
// See: https://github.com/xterm-x11/xterm-snapshots/blob/master/256colres.pl
// 16..=231 => {
// let (r, g, b) = rgb_for_index(index as u8);
// rgba_color(
// if r == 0 { 0 } else { r * 40 + 55 },
// if g == 0 { 0 } else { g * 40 + 55 },
// if b == 0 { 0 } else { b * 40 + 55 },
// )
// }
// 232-255 are a 24-step grayscale ramp from (8, 8, 8) to (238, 238, 238).
// 232..=255 => {
// let i = index as u8 - 232; // Align index to 0..24
// let value = i * 10 + 8;
// rgba_color(value, value, value)
// }
// For compatibility with the alacritty::Colors interface
// See: https://github.com/alacritty/alacritty/blob/master/alacritty_terminal/src/term/color.rs
_ => |_| gpui::black(),
}
}
};
console.highlight_background_key::<ConsoleAnsiHighlight>(
start_offset,
&[range],
color_fetcher,
cx,
);
}
_ = write!(to_insert, "{}\n", event.output.trim_end());
}
console.set_read_only(false);
console.move_to_end(&editor::actions::MoveToEnd, window, cx);
console.insert(&to_insert, window, cx);
console.set_read_only(true);
cx.notify();
});
}
@@ -527,7 +350,6 @@ impl ConsoleQueryBarCompletionProvider {
&string_matches,
&query,
true,
true,
LIMIT,
&Default::default(),
cx.background_executor().clone(),
@@ -637,69 +459,3 @@ impl ConsoleQueryBarCompletionProvider {
})
}
}
#[derive(Default)]
struct ConsoleHandler {
output: String,
spans: Vec<(Range<usize>, Option<ansi::Color>)>,
background_spans: Vec<(Range<usize>, Option<ansi::Color>)>,
current_range_start: usize,
current_background_range_start: usize,
current_color: Option<ansi::Color>,
current_background_color: Option<ansi::Color>,
pos: usize,
}
impl ConsoleHandler {
fn break_span(&mut self, color: Option<ansi::Color>) {
self.spans.push((
self.current_range_start..self.output.len(),
self.current_color,
));
self.current_color = color;
self.current_range_start = self.pos;
}
fn break_background_span(&mut self, color: Option<ansi::Color>) {
self.background_spans.push((
self.current_background_range_start..self.output.len(),
self.current_background_color,
));
self.current_background_color = color;
self.current_background_range_start = self.pos;
}
}
impl ansi::Handler for ConsoleHandler {
fn input(&mut self, c: char) {
self.output.push(c);
self.pos += c.len_utf8();
}
fn linefeed(&mut self) {
self.output.push('\n');
self.pos += 1;
}
fn put_tab(&mut self, count: u16) {
self.output
.extend(std::iter::repeat('\t').take(count as usize));
self.pos += count as usize;
}
fn terminal_attribute(&mut self, attr: ansi::Attr) {
match attr {
ansi::Attr::Foreground(color) => {
self.break_span(Some(color));
}
ansi::Attr::Background(color) => {
self.break_background_span(Some(color));
}
ansi::Attr::Reset => {
self.break_span(None);
self.break_background_span(None);
}
_ => {}
}
}
}

View File

@@ -3,7 +3,6 @@ use crate::{
*,
};
use dap::requests::StackTrace;
use editor::{DisplayPoint, display_map::DisplayRow};
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use project::{FakeFs, Project};
use serde_json::json;
@@ -111,7 +110,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
client
.fake_event(dap::messages::Events::Output(dap::OutputEvent {
category: Some(dap::OutputEventCategory::Stdout),
output: "\tSecond output line after thread stopped!".to_string(),
output: "Second output line after thread stopped!".to_string(),
data: None,
variables_reference: None,
source: None,
@@ -125,7 +124,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
client
.fake_event(dap::messages::Events::Output(dap::OutputEvent {
category: Some(dap::OutputEventCategory::Console),
output: "\tSecond console output line after thread stopped!".to_string(),
output: "Second console output line after thread stopped!".to_string(),
data: None,
variables_reference: None,
source: None,
@@ -151,209 +150,13 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
.unwrap();
assert_eq!(
"First console output line before thread stopped!\nFirst output line before thread stopped!\n\tSecond output line after thread stopped!\n\tSecond console output line after thread stopped!\n",
"First console output line before thread stopped!\nFirst output line before thread stopped!\nSecond output line after thread stopped!\nSecond console output line after thread stopped!\n",
active_session_panel.read(cx).running_state().read(cx).console().read(cx).editor().read(cx).text(cx).as_str()
);
})
.unwrap();
}
#[gpui::test]
async fn test_escape_code_processing(executor: BackgroundExecutor, cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(executor.clone());
fs.insert_tree(
path!("/project"),
json!({
"main.rs": "First line\nSecond line\nThird line\nFourth line",
}),
)
.await;
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
workspace
.update(cx, |workspace, window, cx| {
workspace.focus_panel::<DebugPanel>(window, cx);
})
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.read_with(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<StackTrace, _>(move |_, _| {
Ok(dap::StackTraceResponse {
stack_frames: Vec::default(),
total_frames: None,
})
});
client
.fake_event(dap::messages::Events::Output(dap::OutputEvent {
category: None,
output: "Checking latest version of JavaScript...".to_string(),
data: None,
variables_reference: None,
source: None,
line: None,
column: None,
group: None,
location_reference: None,
}))
.await;
client
.fake_event(dap::messages::Events::Output(dap::OutputEvent {
category: None,
output: " \u{1b}[1m\u{1b}[38;2;173;127;168m▲ Next.js 15.1.5\u{1b}[39m\u{1b}[22m"
.to_string(),
data: None,
variables_reference: None,
source: None,
line: None,
column: None,
group: None,
location_reference: None,
}))
.await;
client
.fake_event(dap::messages::Events::Output(dap::OutputEvent {
category: None,
output: " - Local: http://localhost:3000\n - Network: http://192.168.1.144:3000\n\n \u{1b}[32m\u{1b}[1m✓\u{1b}[22m\u{1b}[39m Starting..."
.to_string(),
data: None,
variables_reference: None,
source: None,
line: None,
column: None,
group: None,
location_reference: None,
}))
.await;
// [crates/debugger_ui/src/session/running/console.rs:147:9] &to_insert = "Could not read source map for file:///Users/cole/roles-at/node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/typescript.js: ENOENT: no such file or directory, open '/Users/cole/roles-at/node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/typescript.js.map'\n"
client
.fake_event(dap::messages::Events::Output(dap::OutputEvent {
category: None,
output: "Something else...".to_string(),
data: None,
variables_reference: None,
source: None,
line: None,
column: None,
group: None,
location_reference: None,
}))
.await;
client
.fake_event(dap::messages::Events::Output(dap::OutputEvent {
category: None,
output: " \u{1b}[32m\u{1b}[1m✓\u{1b}[22m\u{1b}[39m Ready in 1009ms\n".to_string(),
data: None,
variables_reference: None,
source: None,
line: None,
column: None,
group: None,
location_reference: None,
}))
.await;
// introduce some background highlight
client
.fake_event(dap::messages::Events::Output(dap::OutputEvent {
category: None,
output: "\u{1b}[41m\u{1b}[37mBoth background and foreground!".to_string(),
data: None,
variables_reference: None,
source: None,
line: None,
column: None,
group: None,
location_reference: None,
}))
.await;
// another random line
client
.fake_event(dap::messages::Events::Output(dap::OutputEvent {
category: None,
output: "Even more...".to_string(),
data: None,
variables_reference: None,
source: None,
line: None,
column: None,
group: None,
location_reference: None,
}))
.await;
cx.run_until_parked();
let _running_state =
active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
cx.focus_self(window);
item.running_state().clone()
});
cx.run_until_parked();
workspace
.update(cx, |workspace, window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
let active_debug_session_panel = debug_panel
.update(cx, |this, _| this.active_session())
.unwrap();
let editor =
active_debug_session_panel
.read(cx)
.running_state()
.read(cx)
.console()
.read(cx)
.editor().clone();
assert_eq!(
"Checking latest version of JavaScript...\n ▲ Next.js 15.1.5\n - Local: http://localhost:3000\n - Network: http://192.168.1.144:3000\n\n ✓ Starting...\nSomething else...\n ✓ Ready in 1009ms\nBoth background and foreground!\nEven more...\n",
editor
.read(cx)
.text(cx)
.as_str()
);
let text_highlights = editor.update(cx, |editor, cx| {
let mut text_highlights = editor.all_text_highlights(window, cx).into_iter().flat_map(|(_, ranges)| ranges).collect::<Vec<_>>();
text_highlights.sort_by(|a, b| a.start.cmp(&b.start));
text_highlights
});
pretty_assertions::assert_eq!(
text_highlights,
[
DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 21),
DisplayPoint::new(DisplayRow(1), 21)..DisplayPoint::new(DisplayRow(2), 0),
DisplayPoint::new(DisplayRow(5), 1)..DisplayPoint::new(DisplayRow(5), 4),
DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(6), 0),
DisplayPoint::new(DisplayRow(7), 1)..DisplayPoint::new(DisplayRow(7), 4),
DisplayPoint::new(DisplayRow(7), 4)..DisplayPoint::new(DisplayRow(8), 0),
DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(9), 0),
]
);
let background_highlights = editor.update(cx, |editor, cx| {
editor.all_text_background_highlights(window, cx).into_iter().map(|(range, _)| range).collect::<Vec<_>>()
});
pretty_assertions::assert_eq!(
background_highlights,
[
DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(9), 0),
]
)
})
.unwrap();
}
// #[gpui::test]
// async fn test_grouped_output(executor: BackgroundExecutor, cx: &mut TestAppContext) {
// init_test(cx);

View File

@@ -308,7 +308,6 @@ async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppConte
let debug_scenario = adapter
.config_from_zed_format(adapter_specific_config)
.await
.unwrap_or_else(|_| {
panic!(
"Adapter {} should successfully convert from Zed format",
@@ -324,7 +323,6 @@ async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppConte
let request_type = adapter
.request_kind(&debug_scenario.config)
.await
.unwrap_or_else(|_| {
panic!(
"Adapter {} should validate the config successfully",

View File

@@ -58,8 +58,8 @@ pub enum Model {
name: String,
/// The name displayed in the UI, such as in the assistant panel model dropdown menu.
display_name: Option<String>,
max_tokens: u64,
max_output_tokens: Option<u64>,
max_tokens: usize,
max_output_tokens: Option<u32>,
},
}
@@ -94,14 +94,14 @@ impl Model {
}
}
pub fn max_token_count(&self) -> u64 {
pub fn max_token_count(&self) -> usize {
match self {
Self::Chat | Self::Reasoner => 64_000,
Self::Custom { max_tokens, .. } => *max_tokens,
}
}
pub fn max_output_tokens(&self) -> Option<u64> {
pub fn max_output_tokens(&self) -> Option<u32> {
match self {
Self::Chat => Some(8_192),
Self::Reasoner => Some(8_192),
@@ -118,7 +118,7 @@ pub struct Request {
pub messages: Vec<RequestMessage>,
pub stream: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u64>,
pub max_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]

View File

@@ -1,7 +1,7 @@
use super::*;
use collections::{HashMap, HashSet};
use editor::{
DisplayPoint, EditorSettings,
DisplayPoint, EditorSettings, InlayId,
actions::{GoToDiagnostic, GoToPreviousDiagnostic, Hover, MoveToBeginning},
display_map::{DisplayRow, Inlay},
test::{
@@ -870,11 +870,11 @@ async fn test_random_diagnostics_with_inlays(cx: &mut TestAppContext, mut rng: S
editor.splice_inlays(
&[],
vec![Inlay::inline_completion(
post_inc(&mut next_inlay_id),
snapshot.buffer_snapshot.anchor_before(position),
format!("Test inlay {next_inlay_id}"),
)],
vec![Inlay {
id: InlayId::InlineCompletion(post_inc(&mut next_inlay_id)),
position: snapshot.buffer_snapshot.anchor_before(position),
text: Rope::from(format!("Test inlay {next_inlay_id}")),
}],
cx,
);
}

View File

@@ -40,6 +40,7 @@ dap.workspace = true
db.workspace = true
buffer_diff.workspace = true
emojis.workspace = true
feature_flags.workspace = true
file_icons.workspace = true
futures.workspace = true
fuzzy.workspace = true

View File

@@ -1,5 +1,5 @@
use crate::{code_context_menus::CompletionsMenu, editor_settings::SnippetSortOrder};
use fuzzy::{StringMatch, StringMatchCandidate};
use fuzzy::StringMatchCandidate;
use gpui::TestAppContext;
use language::CodeLabel;
use lsp::{CompletionItem, CompletionItemKind, LanguageServerId};
@@ -9,262 +9,440 @@ use std::sync::atomic::AtomicBool;
use text::Anchor;
#[gpui::test]
async fn test_sort_kind(cx: &mut TestAppContext) {
async fn test_sort_matches_local_variable_over_global_variable(cx: &mut TestAppContext) {
// Case 1: "foo"
let completions = vec![
CompletionBuilder::function("floorf128", None, "80000000"),
CompletionBuilder::constant("foo_bar_baz", None, "80000000"),
CompletionBuilder::variable("foo_bar_qux", None, "80000000"),
CompletionBuilder::constant("foo_bar_baz", "7fffffff"),
CompletionBuilder::variable("foo_bar_qux", "7ffffffe"),
CompletionBuilder::constant("floorf64", "80000000"),
CompletionBuilder::constant("floorf32", "80000000"),
CompletionBuilder::constant("floorf16", "80000000"),
CompletionBuilder::constant("floorf128", "80000000"),
];
let matches =
filter_and_sort_matches("foo", &completions, SnippetSortOrder::default(), cx).await;
let matches = sort_matches("foo", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "foo_bar_qux");
assert_eq!(matches[1], "foo_bar_baz");
assert_eq!(matches[2], "floorf16");
assert_eq!(matches[3], "floorf32");
// variable takes precedence over constant
// constant take precedence over function
assert_eq!(
matches
.iter()
.map(|m| m.string.as_str())
.collect::<Vec<_>>(),
vec!["foo_bar_qux", "foo_bar_baz", "floorf128"]
);
// fuzzy score should match for first two items as query is common prefix
assert_eq!(matches[0].score, matches[1].score);
// Case 2: "foobar"
let completions = vec![
CompletionBuilder::constant("foo_bar_baz", "7fffffff"),
CompletionBuilder::variable("foo_bar_qux", "7ffffffe"),
];
let matches = sort_matches("foobar", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "foo_bar_qux");
assert_eq!(matches[1], "foo_bar_baz");
}
#[gpui::test]
async fn test_fuzzy_score(cx: &mut TestAppContext) {
// first character sensitive over sort_text and sort_kind
{
let completions = vec![
CompletionBuilder::variable("element_type", None, "7ffffffe"),
CompletionBuilder::constant("ElementType", None, "7fffffff"),
];
let matches =
filter_and_sort_matches("Elem", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(
matches
.iter()
.map(|m| m.string.as_str())
.collect::<Vec<_>>(),
vec!["ElementType", "element_type"]
);
assert!(matches[0].score > matches[1].score);
}
async fn test_sort_matches_local_variable_over_global_enum(cx: &mut TestAppContext) {
// Case 1: "ele"
let completions = vec![
CompletionBuilder::constant("ElementType", "7fffffff"),
CompletionBuilder::variable("element_type", "7ffffffe"),
CompletionBuilder::constant("simd_select", "80000000"),
CompletionBuilder::keyword("while let", "7fffffff"),
];
let matches = sort_matches("ele", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "element_type");
assert_eq!(matches[1], "ElementType");
// fuzzy takes over sort_text and sort_kind
{
let completions = vec![
CompletionBuilder::function("onAbort?", None, "12"),
CompletionBuilder::function("onAuxClick?", None, "12"),
CompletionBuilder::variable("onPlay?", None, "12"),
CompletionBuilder::variable("onLoad?", None, "12"),
CompletionBuilder::variable("onDrag?", None, "12"),
CompletionBuilder::function("onPause?", None, "10"),
CompletionBuilder::function("onPaste?", None, "10"),
CompletionBuilder::function("onAnimationEnd?", None, "12"),
CompletionBuilder::function("onAbortCapture?", None, "12"),
CompletionBuilder::constant("onChange?", None, "12"),
CompletionBuilder::constant("onWaiting?", None, "12"),
CompletionBuilder::function("onCanPlay?", None, "12"),
];
let matches =
filter_and_sort_matches("ona", &completions, SnippetSortOrder::default(), cx).await;
for i in 0..4 {
assert!(matches[i].string.to_lowercase().starts_with("ona"));
}
}
// plain fuzzy prefix match
{
let completions = vec![
CompletionBuilder::function("set_text", None, "7fffffff"),
CompletionBuilder::function("set_placeholder_text", None, "7fffffff"),
CompletionBuilder::function("set_text_style_refinement", None, "7fffffff"),
CompletionBuilder::function("set_context_menu_options", None, "7fffffff"),
CompletionBuilder::function("select_to_next_word_end", None, "7fffffff"),
CompletionBuilder::function("select_to_next_subword_end", None, "7fffffff"),
CompletionBuilder::function("set_custom_context_menu", None, "7fffffff"),
CompletionBuilder::function("select_to_end_of_excerpt", None, "7fffffff"),
CompletionBuilder::function("select_to_start_of_excerpt", None, "7fffffff"),
CompletionBuilder::function("select_to_start_of_next_excerpt", None, "7fffffff"),
CompletionBuilder::function("select_to_end_of_previous_excerpt", None, "7fffffff"),
];
let matches =
filter_and_sort_matches("set_text", &completions, SnippetSortOrder::Top, cx).await;
assert_eq!(matches[0].string, "set_text");
assert_eq!(matches[1].string, "set_text_style_refinement");
assert_eq!(matches[2].string, "set_context_menu_options");
}
// fuzzy filter text over label, sort_text and sort_kind
{
// Case 1: "awa"
let completions = vec![
CompletionBuilder::method("await", Some("await"), "7fffffff"),
CompletionBuilder::method("await.ne", Some("ne"), "80000010"),
CompletionBuilder::method("await.eq", Some("eq"), "80000010"),
CompletionBuilder::method("await.or", Some("or"), "7ffffff8"),
CompletionBuilder::method("await.zip", Some("zip"), "80000006"),
CompletionBuilder::method("await.xor", Some("xor"), "7ffffff8"),
CompletionBuilder::method("await.and", Some("and"), "80000006"),
CompletionBuilder::method("await.map", Some("map"), "80000006"),
];
test_for_each_prefix("await", &completions, cx, |matches| {
// for each prefix, first item should always be one with lower sort_text
assert_eq!(matches[0].string, "await");
})
.await;
}
// Case 2: "eleme"
let completions = vec![
CompletionBuilder::constant("ElementType", "7fffffff"),
CompletionBuilder::variable("element_type", "7ffffffe"),
CompletionBuilder::constant("REPLACEMENT_CHARACTER", "80000000"),
];
let matches = sort_matches("eleme", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "element_type");
assert_eq!(matches[1], "ElementType");
}
#[gpui::test]
async fn test_sort_text(cx: &mut TestAppContext) {
// sort text takes precedance over sort_kind, when fuzzy is same
{
let completions = vec![
CompletionBuilder::variable("unreachable", None, "80000000"),
CompletionBuilder::function("unreachable!(…)", None, "7fffffff"),
CompletionBuilder::function("unchecked_rem", None, "80000010"),
CompletionBuilder::function("unreachable_unchecked", None, "80000020"),
];
test_for_each_prefix("unreachabl", &completions, cx, |matches| {
// for each prefix, first item should always be one with lower sort_text
assert_eq!(matches[0].string, "unreachable!(…)");
assert_eq!(matches[1].string, "unreachable");
// fuzzy score should match for first two items as query is common prefix
assert_eq!(matches[0].score, matches[1].score);
})
.await;
let matches =
filter_and_sort_matches("unreachable", &completions, SnippetSortOrder::Top, cx).await;
// exact match comes first
assert_eq!(matches[0].string, "unreachable");
assert_eq!(matches[1].string, "unreachable!(…)");
// fuzzy score should match for first two items as query is common prefix
assert_eq!(matches[0].score, matches[1].score);
}
async fn test_sort_matches_capitalization(cx: &mut TestAppContext) {
// Case 1: "Elem"
let completions = vec![
CompletionBuilder::constant("ElementType", "7fffffff"),
CompletionBuilder::variable("element_type", "7ffffffe"),
];
let matches = sort_matches("Elem", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "ElementType");
assert_eq!(matches[1], "element_type");
}
#[gpui::test]
async fn test_sort_snippet(cx: &mut TestAppContext) {
async fn test_sort_matches_for_unreachable(cx: &mut TestAppContext) {
// Case 1: "unre"
let completions = vec![
CompletionBuilder::constant("println", None, "7fffffff"),
CompletionBuilder::snippet("println!(…)", None, "80000000"),
CompletionBuilder::function("unreachable", "80000000"),
CompletionBuilder::function("unreachable!(…)", "7fffffff"),
CompletionBuilder::function("unchecked_rem", "80000000"),
CompletionBuilder::function("unreachable_unchecked", "80000000"),
];
let matches = filter_and_sort_matches("prin", &completions, SnippetSortOrder::Top, cx).await;
let matches = sort_matches("unre", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "unreachable!(…)");
// snippet take precedence over sort_text and sort_kind
assert_eq!(matches[0].string, "println!(…)");
// Case 2: "unrea"
let completions = vec![
CompletionBuilder::function("unreachable", "80000000"),
CompletionBuilder::function("unreachable!(…)", "7fffffff"),
CompletionBuilder::function("unreachable_unchecked", "80000000"),
];
let matches = sort_matches("unrea", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "unreachable!(…)");
// Case 3: "unreach"
let completions = vec![
CompletionBuilder::function("unreachable", "80000000"),
CompletionBuilder::function("unreachable!(…)", "7fffffff"),
CompletionBuilder::function("unreachable_unchecked", "80000000"),
];
let matches = sort_matches("unreach", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "unreachable!(…)");
// Case 4: "unreachabl"
let completions = vec![
CompletionBuilder::function("unreachable", "80000000"),
CompletionBuilder::function("unreachable!(…)", "7fffffff"),
CompletionBuilder::function("unreachable_unchecked", "80000000"),
];
let matches = sort_matches("unreachable", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "unreachable!(…)");
// Case 5: "unreachable"
let completions = vec![
CompletionBuilder::function("unreachable", "80000000"),
CompletionBuilder::function("unreachable!(…)", "7fffffff"),
CompletionBuilder::function("unreachable_unchecked", "80000000"),
];
let matches = sort_matches("unreachable", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "unreachable!(…)");
}
#[gpui::test]
async fn test_sort_exact(cx: &mut TestAppContext) {
// sort_text takes over if no exact match
async fn test_sort_matches_variable_and_constants_over_function(cx: &mut TestAppContext) {
// Case 1: "var" as variable
let completions = vec![
CompletionBuilder::function("into", None, "80000004"),
CompletionBuilder::function("try_into", None, "80000004"),
CompletionBuilder::snippet("println", None, "80000004"),
CompletionBuilder::function("clone_into", None, "80000004"),
CompletionBuilder::function("into_searcher", None, "80000000"),
CompletionBuilder::snippet("eprintln", None, "80000004"),
CompletionBuilder::function("var", "7fffffff"),
CompletionBuilder::variable("var", "7fffffff"),
];
let matches =
filter_and_sort_matches("int", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0].string, "into_searcher");
let matches = sort_matches("var", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "var");
assert_eq!(matches[1], "var");
// exact match takes over sort_text
// Case 2: "var" as constant
let completions = vec![
CompletionBuilder::function("into", None, "80000004"),
CompletionBuilder::function("try_into", None, "80000004"),
CompletionBuilder::function("clone_into", None, "80000004"),
CompletionBuilder::function("into_searcher", None, "80000000"),
CompletionBuilder::function("split_terminator", None, "7fffffff"),
CompletionBuilder::function("rsplit_terminator", None, "7fffffff"),
CompletionBuilder::function("var", "7fffffff"),
CompletionBuilder::constant("var", "7fffffff"),
];
let matches =
filter_and_sort_matches("into", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0].string, "into");
let matches = sort_matches("var", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "var");
assert_eq!(matches[1], "var");
}
#[gpui::test]
async fn test_sort_positions(cx: &mut TestAppContext) {
// positions take precedence over fuzzy score and sort_text
async fn test_sort_matches_for_jsx_event_handler(cx: &mut TestAppContext) {
// Case 1: "on"
let completions = vec![
CompletionBuilder::function("rounded-full", None, "15788"),
CompletionBuilder::variable("rounded-t-full", None, "15846"),
CompletionBuilder::variable("rounded-b-full", None, "15731"),
CompletionBuilder::function("rounded-tr-full", None, "15866"),
CompletionBuilder::function("onCut?", "12"),
CompletionBuilder::function("onPlay?", "12"),
CompletionBuilder::function("color?", "12"),
CompletionBuilder::function("defaultValue?", "12"),
CompletionBuilder::function("style?", "12"),
CompletionBuilder::function("className?", "12"),
];
let matches = sort_matches("on", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "onCut?");
assert_eq!(matches[1], "onPlay?");
let matches = filter_and_sort_matches(
// Case 2: "ona"
let completions = vec![
CompletionBuilder::function("onAbort?", "12"),
CompletionBuilder::function("onAuxClick?", "12"),
CompletionBuilder::function("onPlay?", "12"),
CompletionBuilder::function("onLoad?", "12"),
CompletionBuilder::function("onDrag?", "12"),
CompletionBuilder::function("onPause?", "12"),
CompletionBuilder::function("onPaste?", "12"),
CompletionBuilder::function("onAnimationEnd?", "12"),
CompletionBuilder::function("onAbortCapture?", "12"),
CompletionBuilder::function("onChange?", "12"),
CompletionBuilder::function("onWaiting?", "12"),
CompletionBuilder::function("onCanPlay?", "12"),
];
let matches = sort_matches("ona", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "onAbort?");
assert_eq!(matches[1], "onAuxClick?");
}
#[gpui::test]
async fn test_sort_matches_for_snippets(cx: &mut TestAppContext) {
// Case 1: "prin"
let completions = vec![
CompletionBuilder::constant("println", "80000000"),
CompletionBuilder::snippet("println!(…)", "80000000"),
];
let matches = sort_matches("prin", &completions, SnippetSortOrder::Top, cx).await;
assert_eq!(matches[0], "println!(…)");
}
#[gpui::test]
async fn test_sort_matches_for_exact_match(cx: &mut TestAppContext) {
// Case 1: "set_text"
let completions = vec![
CompletionBuilder::function("set_text", "7fffffff"),
CompletionBuilder::function("set_placeholder_text", "7fffffff"),
CompletionBuilder::function("set_text_style_refinement", "7fffffff"),
CompletionBuilder::function("set_context_menu_options", "7fffffff"),
CompletionBuilder::function("select_to_next_word_end", "7fffffff"),
CompletionBuilder::function("select_to_next_subword_end", "7fffffff"),
CompletionBuilder::function("set_custom_context_menu", "7fffffff"),
CompletionBuilder::function("select_to_end_of_excerpt", "7fffffff"),
CompletionBuilder::function("select_to_start_of_excerpt", "7fffffff"),
CompletionBuilder::function("select_to_start_of_next_excerpt", "7fffffff"),
CompletionBuilder::function("select_to_end_of_previous_excerpt", "7fffffff"),
];
let matches = sort_matches("set_text", &completions, SnippetSortOrder::Top, cx).await;
assert_eq!(matches[0], "set_text");
assert_eq!(matches[1], "set_text_style_refinement");
assert_eq!(matches[2], "set_context_menu_options");
}
#[gpui::test]
async fn test_sort_matches_for_prefix_matches(cx: &mut TestAppContext) {
// Case 1: "set"
let completions = vec![
CompletionBuilder::function("select_to_beginning", "7fffffff"),
CompletionBuilder::function("set_collapse_matches", "7fffffff"),
CompletionBuilder::function("set_autoindent", "7fffffff"),
CompletionBuilder::function("set_all_diagnostics_active", "7fffffff"),
CompletionBuilder::function("select_to_end_of_line", "7fffffff"),
CompletionBuilder::function("select_all", "7fffffff"),
CompletionBuilder::function("select_line", "7fffffff"),
CompletionBuilder::function("select_left", "7fffffff"),
CompletionBuilder::function("select_down", "7fffffff"),
];
let matches = sort_matches("set", &completions, SnippetSortOrder::Top, cx).await;
assert_eq!(matches[0], "set_autoindent");
assert_eq!(matches[1], "set_collapse_matches");
assert_eq!(matches[2], "set_all_diagnostics_active");
}
#[gpui::test]
async fn test_sort_matches_for_await(cx: &mut TestAppContext) {
// Case 1: "awa"
let completions = vec![
CompletionBuilder::keyword("await", "7fffffff"),
CompletionBuilder::function("await.ne", "80000010"),
CompletionBuilder::function("await.eq", "80000010"),
CompletionBuilder::function("await.or", "7ffffff8"),
CompletionBuilder::function("await.zip", "80000006"),
CompletionBuilder::function("await.xor", "7ffffff8"),
CompletionBuilder::function("await.and", "80000006"),
CompletionBuilder::function("await.map", "80000006"),
CompletionBuilder::function("await.take", "7ffffff8"),
];
let matches = sort_matches("awa", &completions, SnippetSortOrder::Top, cx).await;
assert_eq!(matches[0], "await");
// Case 2: "await"
let completions = vec![
CompletionBuilder::keyword("await", "7fffffff"),
CompletionBuilder::function("await.ne", "80000010"),
CompletionBuilder::function("await.eq", "80000010"),
CompletionBuilder::function("await.or", "7ffffff8"),
CompletionBuilder::function("await.zip", "80000006"),
CompletionBuilder::function("await.xor", "7ffffff8"),
CompletionBuilder::function("await.and", "80000006"),
CompletionBuilder::function("await.map", "80000006"),
CompletionBuilder::function("await.take", "7ffffff8"),
];
let matches = sort_matches("await", &completions, SnippetSortOrder::Top, cx).await;
assert_eq!(matches[0], "await");
}
#[gpui::test]
async fn test_sort_matches_for_python_init(cx: &mut TestAppContext) {
// Case 1: "__in"
let completions = vec![
CompletionBuilder::function("__init__", "05.0003.__init__"),
CompletionBuilder::function("__init__", "05.0003"),
CompletionBuilder::function("__instancecheck__", "05.0005.__instancecheck__"),
CompletionBuilder::function("__init_subclass__", "05.0004.__init_subclass__"),
CompletionBuilder::function("__instancecheck__", "05.0005"),
CompletionBuilder::function("__init_subclass__", "05.0004"),
];
let matches = sort_matches("__in", &completions, SnippetSortOrder::Top, cx).await;
assert_eq!(matches[0], "__init__");
assert_eq!(matches[1], "__init__");
// Case 2: "__ini"
let completions = vec![
CompletionBuilder::function("__init__", "05.0004.__init__"),
CompletionBuilder::function("__init__", "05.0004"),
CompletionBuilder::function("__init_subclass__", "05.0003.__init_subclass__"),
CompletionBuilder::function("__init_subclass__", "05.0003"),
];
let matches = sort_matches("__ini", &completions, SnippetSortOrder::Top, cx).await;
assert_eq!(matches[0], "__init__");
assert_eq!(matches[1], "__init__");
// Case 3: "__init"
let completions = vec![
CompletionBuilder::function("__init__", "05.0000.__init__"),
CompletionBuilder::function("__init__", "05.0000"),
CompletionBuilder::function("__init_subclass__", "05.0001.__init_subclass__"),
CompletionBuilder::function("__init_subclass__", "05.0001"),
];
let matches = sort_matches("__init", &completions, SnippetSortOrder::Top, cx).await;
assert_eq!(matches[0], "__init__");
assert_eq!(matches[1], "__init__");
// Case 4: "__init_"
let completions = vec![
CompletionBuilder::function("__init__", "11.9999.__init__"),
CompletionBuilder::function("__init__", "11.9999"),
CompletionBuilder::function("__init_subclass__", "05.0000.__init_subclass__"),
CompletionBuilder::function("__init_subclass__", "05.0000"),
];
let matches = sort_matches("__init_", &completions, SnippetSortOrder::Top, cx).await;
assert_eq!(matches[0], "__init__");
assert_eq!(matches[1], "__init__");
}
#[gpui::test]
async fn test_sort_matches_for_rust_into(cx: &mut TestAppContext) {
// Case 1: "int"
let completions = vec![
CompletionBuilder::function("into", "80000004"),
CompletionBuilder::function("try_into", "80000004"),
CompletionBuilder::snippet("println", "80000004"),
CompletionBuilder::function("clone_into", "80000004"),
CompletionBuilder::function("into_searcher", "80000000"),
CompletionBuilder::snippet("eprintln", "80000004"),
];
let matches = sort_matches("int", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "into");
// Case 2: "into"
let completions = vec![
CompletionBuilder::function("into", "80000004"),
CompletionBuilder::function("try_into", "80000004"),
CompletionBuilder::function("clone_into", "80000004"),
CompletionBuilder::function("into_searcher", "80000000"),
CompletionBuilder::function("split_terminator", "7fffffff"),
CompletionBuilder::function("rsplit_terminator", "7fffffff"),
];
let matches = sort_matches("into", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "into");
}
#[gpui::test]
async fn test_sort_matches_for_variable_over_function(cx: &mut TestAppContext) {
// Case 1: "serial"
let completions = vec![
CompletionBuilder::function("serialize", "80000000"),
CompletionBuilder::function("serialize", "80000000"),
CompletionBuilder::variable("serialization_key", "7ffffffe"),
CompletionBuilder::function("serialize_version", "80000000"),
CompletionBuilder::function("deserialize", "80000000"),
];
let matches = sort_matches("serial", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "serialization_key");
assert_eq!(matches[1], "serialize");
assert_eq!(matches[2], "serialize");
assert_eq!(matches[3], "serialize_version");
assert_eq!(matches[4], "deserialize");
}
#[gpui::test]
async fn test_sort_matches_for_local_methods_over_library(cx: &mut TestAppContext) {
// Case 1: "setis"
let completions = vec![
CompletionBuilder::variable("setISODay", "16"),
CompletionBuilder::variable("setISOWeek", "16"),
CompletionBuilder::variable("setISOWeekYear", "16"),
CompletionBuilder::function("setISOWeekYear", "16"),
CompletionBuilder::variable("setIsRefreshing", "11"),
CompletionBuilder::function("setFips", "16"),
];
let matches = sort_matches("setis", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "setIsRefreshing");
assert_eq!(matches[1], "setISODay");
assert_eq!(matches[2], "setISOWeek");
}
#[gpui::test]
async fn test_sort_matches_for_prioritize_not_exact_match(cx: &mut TestAppContext) {
// Case 1: "item"
let completions = vec![
CompletionBuilder::function("Item", "16"),
CompletionBuilder::variable("Item", "16"),
CompletionBuilder::variable("items", "11"),
CompletionBuilder::function("ItemText", "16"),
];
let matches = sort_matches("item", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "items");
assert_eq!(matches[1], "Item");
assert_eq!(matches[2], "Item");
assert_eq!(matches[3], "ItemText");
}
#[gpui::test]
async fn test_sort_matches_for_tailwind_classes(cx: &mut TestAppContext) {
let completions = vec![
CompletionBuilder::function("rounded-full", "15788"),
CompletionBuilder::variable("rounded-t-full", "15846"),
CompletionBuilder::variable("rounded-b-full", "15731"),
CompletionBuilder::function("rounded-tr-full", "15866"),
];
// Case 1: "rounded-full"
let matches = sort_matches(
"rounded-full",
&completions,
SnippetSortOrder::default(),
cx,
)
.await;
assert_eq!(matches[0].string, "rounded-full");
let matches =
filter_and_sort_matches("roundedfull", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0].string, "rounded-full");
}
async fn test_for_each_prefix<F>(
target: &str,
completions: &Vec<Completion>,
cx: &mut TestAppContext,
mut test_fn: F,
) where
F: FnMut(Vec<StringMatch>),
{
for i in 1..=target.len() {
let prefix = &target[..i];
let matches =
filter_and_sort_matches(prefix, completions, SnippetSortOrder::default(), cx).await;
test_fn(matches);
}
assert_eq!(matches[0], "rounded-full");
// Case 2: "roundedfull"
let matches = sort_matches("roundedfull", &completions, SnippetSortOrder::default(), cx).await;
assert_eq!(matches[0], "rounded-full");
}
struct CompletionBuilder;
impl CompletionBuilder {
fn constant(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
Self::new(label, filter_text, sort_text, CompletionItemKind::CONSTANT)
fn constant(label: &str, sort_text: &str) -> Completion {
Self::new(label, sort_text, CompletionItemKind::CONSTANT)
}
fn function(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
Self::new(label, filter_text, sort_text, CompletionItemKind::FUNCTION)
fn function(label: &str, sort_text: &str) -> Completion {
Self::new(label, sort_text, CompletionItemKind::FUNCTION)
}
fn method(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
Self::new(label, filter_text, sort_text, CompletionItemKind::METHOD)
fn variable(label: &str, sort_text: &str) -> Completion {
Self::new(label, sort_text, CompletionItemKind::VARIABLE)
}
fn variable(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
Self::new(label, filter_text, sort_text, CompletionItemKind::VARIABLE)
fn keyword(label: &str, sort_text: &str) -> Completion {
Self::new(label, sort_text, CompletionItemKind::KEYWORD)
}
fn snippet(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
Self::new(label, filter_text, sort_text, CompletionItemKind::SNIPPET)
fn snippet(label: &str, sort_text: &str) -> Completion {
Self::new(label, sort_text, CompletionItemKind::SNIPPET)
}
fn new(
label: &str,
filter_text: Option<&str>,
sort_text: &str,
kind: CompletionItemKind,
) -> Completion {
fn new(label: &str, sort_text: &str, kind: CompletionItemKind) -> Completion {
Completion {
replace_range: Anchor::MIN..Anchor::MAX,
new_text: label.to_string(),
label: CodeLabel::plain(label.to_string(), filter_text),
label: CodeLabel {
text: label.to_string(),
runs: Default::default(),
filter_range: 0..label.len(),
},
documentation: None,
source: CompletionSource::Lsp {
insert_range: None,
@@ -273,7 +451,6 @@ impl CompletionBuilder {
label: label.to_string(),
kind: Some(kind),
sort_text: Some(sort_text.to_string()),
filter_text: filter_text.map(|text| text.to_string()),
..Default::default()
}),
lsp_defaults: None,
@@ -286,16 +463,16 @@ impl CompletionBuilder {
}
}
async fn filter_and_sort_matches(
async fn sort_matches(
query: &str,
completions: &Vec<Completion>,
snippet_sort_order: SnippetSortOrder,
cx: &mut TestAppContext,
) -> Vec<StringMatch> {
) -> Vec<String> {
let candidates: Arc<[StringMatchCandidate]> = completions
.iter()
.enumerate()
.map(|(id, completion)| StringMatchCandidate::new(id, &completion.label.filter_text()))
.map(|(id, completion)| StringMatchCandidate::new(id, &completion.label.text))
.collect();
let cancel_flag = Arc::new(AtomicBool::new(false));
let background_executor = cx.executor();
@@ -303,11 +480,16 @@ async fn filter_and_sort_matches(
&candidates,
query,
query.chars().any(|c| c.is_uppercase()),
false,
100,
&cancel_flag,
background_executor,
)
.await;
CompletionsMenu::sort_string_matches(matches, Some(query), snippet_sort_order, &completions)
let sorted_matches = CompletionsMenu::sort_string_matches(
matches,
Some(query),
snippet_sort_order,
&completions,
);
sorted_matches.into_iter().map(|m| m.string).collect()
}

View File

@@ -260,7 +260,7 @@ impl CompletionsMenu {
let match_candidates = completions
.iter()
.enumerate()
.map(|(id, completion)| StringMatchCandidate::new(id, completion.label.filter_text()))
.map(|(id, completion)| StringMatchCandidate::new(id, &completion.label.filter_text()))
.collect();
let completions_menu = Self {
@@ -979,8 +979,7 @@ impl CompletionsMenu {
&match_candidates,
&query,
query.chars().any(|c| c.is_uppercase()),
false,
1000,
100,
&cancel_filter,
background_executor,
)
@@ -1056,12 +1055,13 @@ impl CompletionsMenu {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum MatchTier<'a> {
WordStartMatch {
sort_exact: Reverse<i32>,
sort_capitalize: Reverse<usize>,
sort_positions: Vec<usize>,
sort_snippet: Reverse<i32>,
sort_score: Reverse<OrderedFloat<f64>>,
sort_text: Option<&'a str>,
sort_kind: usize,
sort_fuzzy_bracket: Reverse<usize>,
sort_text: Option<&'a str>,
sort_score: Reverse<OrderedFloat<f64>>,
sort_label: &'a str,
},
OtherMatch {
@@ -1069,6 +1069,14 @@ impl CompletionsMenu {
},
}
// Our goal here is to intelligently sort completion suggestions. We want to
// balance the raw fuzzy match score with hints from the language server
// In a fuzzy bracket, matches with a score of 1.0 are prioritized.
// The remaining matches are partitioned into two groups at 3/5 of the max_score.
let max_score = matches.iter().map(|mat| mat.score).fold(0.0, f64::max);
let fuzzy_bracket_threshold = max_score * (3.0 / 5.0);
let query_start_lower = query
.as_ref()
.and_then(|q| q.chars().next())
@@ -1109,25 +1117,34 @@ impl CompletionsMenu {
if query_start_doesnt_match_split_words {
MatchTier::OtherMatch { sort_score }
} else {
let sort_fuzzy_bracket = Reverse(if score >= fuzzy_bracket_threshold {
1
} else {
0
});
let sort_snippet = match snippet_sort_order {
SnippetSortOrder::Top => Reverse(if is_snippet { 1 } else { 0 }),
SnippetSortOrder::Bottom => Reverse(if is_snippet { 0 } else { 1 }),
SnippetSortOrder::Inline => Reverse(0),
};
let sort_capitalize = Reverse(
query
.as_ref()
.and_then(|q| q.chars().next())
.zip(string_match.string.chars().next())
.map(|(q_char, s_char)| if q_char == s_char { 1 } else { 0 })
.unwrap_or(0),
);
let sort_positions = string_match.positions.clone();
let sort_exact = Reverse(if Some(completion.label.filter_text()) == query {
1
} else {
0
});
MatchTier::WordStartMatch {
sort_exact,
sort_capitalize,
sort_positions,
sort_snippet,
sort_score,
sort_text,
sort_kind,
sort_fuzzy_bracket,
sort_text,
sort_score,
sort_label,
}
}
@@ -1205,7 +1222,7 @@ impl CodeActionContents {
tasks_len + code_actions_len + self.debug_scenarios.len()
}
pub fn is_empty(&self) -> bool {
fn is_empty(&self) -> bool {
self.len() == 0
}

View File

@@ -37,9 +37,7 @@ pub use block_map::{
use block_map::{BlockRow, BlockSnapshot};
use collections::{HashMap, HashSet};
pub use crease_map::*;
pub use fold_map::{
ChunkRenderer, ChunkRendererContext, ChunkRendererId, Fold, FoldId, FoldPlaceholder, FoldPoint,
};
pub use fold_map::{ChunkRenderer, ChunkRendererContext, Fold, FoldId, FoldPlaceholder, FoldPoint};
use fold_map::{FoldMap, FoldSnapshot};
use gpui::{App, Context, Entity, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle};
pub use inlay_map::Inlay;
@@ -78,17 +76,11 @@ pub enum FoldStatus {
Foldable,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum HighlightKey {
Type(TypeId),
TypePlus(TypeId, usize),
}
pub trait ToDisplayPoint {
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
}
type TextHighlights = TreeMap<HighlightKey, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
type TextHighlights = TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
@@ -481,11 +473,12 @@ impl DisplayMap {
pub fn highlight_text(
&mut self,
key: HighlightKey,
type_id: TypeId,
ranges: Vec<Range<Anchor>>,
style: HighlightStyle,
) {
self.text_highlights.insert(key, Arc::new((style, ranges)));
self.text_highlights
.insert(type_id, Arc::new((style, ranges)));
}
pub(crate) fn highlight_inlays(
@@ -508,22 +501,11 @@ impl DisplayMap {
}
pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
let highlights = self.text_highlights.get(&HighlightKey::Type(type_id))?;
let highlights = self.text_highlights.get(&type_id)?;
Some((highlights.0, &highlights.1))
}
#[cfg(feature = "test-support")]
pub fn all_text_highlights(
&self,
) -> impl Iterator<Item = &Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
self.text_highlights.values()
}
pub fn clear_highlights(&mut self, type_id: TypeId) -> bool {
let mut cleared = self
.text_highlights
.remove(&HighlightKey::Type(type_id))
.is_some();
let mut cleared = self.text_highlights.remove(&type_id).is_some();
cleared |= self.inlay_highlights.remove(&type_id).is_some();
cleared
}
@@ -540,7 +522,7 @@ impl DisplayMap {
pub fn update_fold_widths(
&mut self,
widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
widths: impl IntoIterator<Item = (FoldId, Pixels)>,
cx: &mut Context<Self>,
) -> bool {
let snapshot = self.buffer.read(cx).snapshot(cx);
@@ -968,22 +950,10 @@ impl DisplaySnapshot {
.and_then(|id| id.style(&editor_style.syntax));
if let Some(chunk_highlight) = chunk.highlight_style {
// For color inlays, blend the color with the editor background
let mut processed_highlight = chunk_highlight;
if chunk.is_inlay {
if let Some(inlay_color) = chunk_highlight.color {
// Only blend if the color has transparency (alpha < 1.0)
if inlay_color.a < 1.0 {
let blended_color = editor_style.background.blend(inlay_color);
processed_highlight.color = Some(blended_color);
}
}
}
if let Some(highlight_style) = highlight_style.as_mut() {
highlight_style.highlight(processed_highlight);
highlight_style.highlight(chunk_highlight);
} else {
highlight_style = Some(processed_highlight);
highlight_style = Some(chunk_highlight);
}
}
@@ -1363,9 +1333,7 @@ impl DisplaySnapshot {
&self,
) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
let type_id = TypeId::of::<Tag>();
self.text_highlights
.get(&HighlightKey::Type(type_id))
.cloned()
self.text_highlights.get(&type_id).cloned()
}
#[allow(unused)]
@@ -2039,11 +2007,11 @@ pub mod tests {
map.update(cx, |map, cx| {
map.splice_inlays(
&[],
vec![Inlay::inline_completion(
0,
buffer_snapshot.anchor_after(0),
"\n",
)],
vec![Inlay {
id: InlayId::InlineCompletion(0),
position: buffer_snapshot.anchor_after(0),
text: "\n".into(),
}],
cx,
);
});
@@ -2326,7 +2294,7 @@ pub mod tests {
// Insert a block in the middle of a multi-line diagnostic.
map.update(cx, |map, cx| {
map.highlight_text(
HighlightKey::Type(TypeId::of::<usize>()),
TypeId::of::<usize>(),
vec![
buffer_snapshot.anchor_before(Point::new(3, 9))
..buffer_snapshot.anchor_after(Point::new(3, 14)),
@@ -2648,7 +2616,7 @@ pub mod tests {
map.update(cx, |map, _cx| {
map.highlight_text(
HighlightKey::Type(TypeId::of::<MyType>()),
TypeId::of::<MyType>(),
highlighted_ranges
.into_iter()
.map(|range| {

View File

@@ -1,15 +1,16 @@
use collections::BTreeMap;
use gpui::HighlightStyle;
use language::Chunk;
use multi_buffer::{MultiBufferChunks, MultiBufferSnapshot, ToOffset as _};
use multi_buffer::{Anchor, MultiBufferChunks, MultiBufferSnapshot, ToOffset as _};
use std::{
any::TypeId,
cmp,
iter::{self, Peekable},
ops::Range,
sync::Arc,
vec,
};
use crate::display_map::{HighlightKey, TextHighlights};
use sum_tree::TreeMap;
pub struct CustomHighlightsChunks<'a> {
buffer_chunks: MultiBufferChunks<'a>,
@@ -18,15 +19,15 @@ pub struct CustomHighlightsChunks<'a> {
multibuffer_snapshot: &'a MultiBufferSnapshot,
highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
active_highlights: BTreeMap<HighlightKey, HighlightStyle>,
text_highlights: Option<&'a TextHighlights>,
active_highlights: BTreeMap<TypeId, HighlightStyle>,
text_highlights: Option<&'a TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
struct HighlightEndpoint {
offset: usize,
is_start: bool,
tag: HighlightKey,
tag: TypeId,
style: HighlightStyle,
}
@@ -34,7 +35,7 @@ impl<'a> CustomHighlightsChunks<'a> {
pub fn new(
range: Range<usize>,
language_aware: bool,
text_highlights: Option<&'a TextHighlights>,
text_highlights: Option<&'a TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
multibuffer_snapshot: &'a MultiBufferSnapshot,
) -> Self {
Self {
@@ -65,7 +66,7 @@ impl<'a> CustomHighlightsChunks<'a> {
fn create_highlight_endpoints(
range: &Range<usize>,
text_highlights: Option<&TextHighlights>,
text_highlights: Option<&TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
buffer: &MultiBufferSnapshot,
) -> iter::Peekable<vec::IntoIter<HighlightEndpoint>> {
let mut highlight_endpoints = Vec::new();

View File

@@ -1,5 +1,3 @@
use crate::{InlayId, display_map::inlay_map::InlayChunk};
use super::{
Highlights,
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
@@ -277,16 +275,13 @@ impl FoldMapWriter<'_> {
pub(crate) fn update_fold_widths(
&mut self,
new_widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
new_widths: impl IntoIterator<Item = (FoldId, Pixels)>,
) -> (FoldSnapshot, Vec<FoldEdit>) {
let mut edits = Vec::new();
let inlay_snapshot = self.0.snapshot.inlay_snapshot.clone();
let buffer = &inlay_snapshot.buffer;
for (id, new_width) in new_widths {
let ChunkRendererId::Fold(id) = id else {
continue;
};
if let Some(metadata) = self.0.snapshot.fold_metadata_by_id.get(&id).cloned() {
if Some(new_width) != metadata.width {
let buffer_start = metadata.range.start.to_offset(buffer);
@@ -532,7 +527,7 @@ impl FoldMap {
placeholder: Some(TransformPlaceholder {
text: ELLIPSIS,
renderer: ChunkRenderer {
id: ChunkRendererId::Fold(fold.id),
id: fold.id,
render: Arc::new(move |cx| {
(fold.placeholder.render)(
fold_id,
@@ -1065,7 +1060,7 @@ impl sum_tree::Summary for TransformSummary {
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Ord, PartialOrd, Hash)]
pub struct FoldId(pub(super) usize);
pub struct FoldId(usize);
impl From<FoldId> for ElementId {
fn from(val: FoldId) -> Self {
@@ -1270,17 +1265,11 @@ pub struct Chunk<'a> {
pub renderer: Option<ChunkRenderer>,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ChunkRendererId {
Fold(FoldId),
Inlay(InlayId),
}
/// A recipe for how the chunk should be presented.
#[derive(Clone)]
pub struct ChunkRenderer {
/// The id of the renderer associated with this chunk.
pub id: ChunkRendererId,
/// The id of the fold associated with this chunk.
pub id: FoldId,
/// Creates a custom element to represent this chunk.
pub render: Arc<dyn Send + Sync + Fn(&mut ChunkRendererContext) -> AnyElement>,
/// If true, the element is constrained to the shaped width of the text.
@@ -1322,7 +1311,7 @@ impl DerefMut for ChunkRendererContext<'_, '_> {
pub struct FoldChunks<'a> {
transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>,
inlay_chunks: InlayChunks<'a>,
inlay_chunk: Option<(InlayOffset, InlayChunk<'a>)>,
inlay_chunk: Option<(InlayOffset, language::Chunk<'a>)>,
inlay_offset: InlayOffset,
output_offset: FoldOffset,
max_output_offset: FoldOffset,
@@ -1414,8 +1403,7 @@ impl<'a> Iterator for FoldChunks<'a> {
}
// Otherwise, take a chunk from the buffer's text.
if let Some((buffer_chunk_start, mut inlay_chunk)) = self.inlay_chunk.clone() {
let chunk = &mut inlay_chunk.chunk;
if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk.clone() {
let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
let transform_end = self.transform_cursor.end(&()).1;
let chunk_end = buffer_chunk_end.min(transform_end);
@@ -1440,7 +1428,7 @@ impl<'a> Iterator for FoldChunks<'a> {
is_tab: chunk.is_tab,
is_inlay: chunk.is_inlay,
underline: chunk.underline,
renderer: inlay_chunk.renderer,
renderer: None,
});
}

View File

@@ -1,6 +1,5 @@
use crate::{ChunkRenderer, HighlightStyles, InlayId};
use crate::{HighlightStyles, InlayId};
use collections::BTreeSet;
use gpui::{Hsla, Rgba};
use language::{Chunk, Edit, Point, TextSummary};
use multi_buffer::{
Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, RowInfo, ToOffset,
@@ -8,13 +7,11 @@ use multi_buffer::{
use std::{
cmp,
ops::{Add, AddAssign, Range, Sub, SubAssign},
sync::Arc,
};
use sum_tree::{Bias, Cursor, SumTree};
use text::{Patch, Rope};
use ui::{ActiveTheme, IntoElement as _, ParentElement as _, Styled as _, div};
use super::{Highlights, custom_highlights::CustomHighlightsChunks, fold_map::ChunkRendererId};
use super::{Highlights, custom_highlights::CustomHighlightsChunks};
/// Decides where the [`Inlay`]s should be displayed.
///
@@ -42,7 +39,6 @@ pub struct Inlay {
pub id: InlayId,
pub position: Anchor,
pub text: text::Rope,
color: Option<Hsla>,
}
impl Inlay {
@@ -58,26 +54,6 @@ impl Inlay {
id: InlayId::Hint(id),
position,
text: text.into(),
color: None,
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn mock_hint(id: usize, position: Anchor, text: impl Into<Rope>) -> Self {
Self {
id: InlayId::Hint(id),
position,
text: text.into(),
color: None,
}
}
pub fn color(id: usize, position: Anchor, color: Rgba) -> Self {
Self {
id: InlayId::Color(id),
position,
text: Rope::from(""),
color: Some(Hsla::from(color)),
}
}
@@ -86,23 +62,16 @@ impl Inlay {
id: InlayId::InlineCompletion(id),
position,
text: text.into(),
color: None,
}
}
pub fn debugger<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
pub fn debugger_hint<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
Self {
id: InlayId::DebuggerValue(id),
position,
text: text.into(),
color: None,
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn get_color(&self) -> Option<Hsla> {
self.color
}
}
impl sum_tree::Item for Transform {
@@ -254,13 +223,6 @@ pub struct InlayChunks<'a> {
snapshot: &'a InlaySnapshot,
}
#[derive(Clone)]
pub struct InlayChunk<'a> {
pub chunk: Chunk<'a>,
/// Whether the inlay should be customly rendered.
pub renderer: Option<ChunkRenderer>,
}
impl InlayChunks<'_> {
pub fn seek(&mut self, new_range: Range<InlayOffset>) {
self.transforms.seek(&new_range.start, Bias::Right, &());
@@ -280,7 +242,7 @@ impl InlayChunks<'_> {
}
impl<'a> Iterator for InlayChunks<'a> {
type Item = InlayChunk<'a>;
type Item = Chunk<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.output_offset == self.max_output_offset {
@@ -305,12 +267,9 @@ impl<'a> Iterator for InlayChunks<'a> {
chunk.text = suffix;
self.output_offset.0 += prefix.len();
InlayChunk {
chunk: Chunk {
text: prefix,
..chunk.clone()
},
renderer: None,
Chunk {
text: prefix,
..chunk.clone()
}
}
Transform::Inlay(inlay) => {
@@ -325,7 +284,6 @@ impl<'a> Iterator for InlayChunks<'a> {
}
}
let mut renderer = None;
let mut highlight_style = match inlay.id {
InlayId::InlineCompletion(_) => {
self.highlight_styles.inline_completion.map(|s| {
@@ -338,33 +296,6 @@ impl<'a> Iterator for InlayChunks<'a> {
}
InlayId::Hint(_) => self.highlight_styles.inlay_hint,
InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint,
InlayId::Color(_) => {
if let Some(color) = inlay.color {
renderer = Some(ChunkRenderer {
id: ChunkRendererId::Inlay(inlay.id),
render: Arc::new(move |cx| {
div()
.w_4()
.h_4()
.relative()
.child(
div()
.absolute()
.right_1()
.w_3p5()
.h_3p5()
.border_2()
.border_color(cx.theme().colors().border)
.bg(color),
)
.into_any_element()
}),
constrain_width: false,
measured_width: None,
});
}
self.highlight_styles.inlay_hint
}
};
let next_inlay_highlight_endpoint;
let offset_in_inlay = self.output_offset - self.transforms.start().0;
@@ -402,14 +333,11 @@ impl<'a> Iterator for InlayChunks<'a> {
self.output_offset.0 += chunk.len();
InlayChunk {
chunk: Chunk {
text: chunk,
highlight_style,
is_inlay: true,
..Chunk::default()
},
renderer,
Chunk {
text: chunk,
highlight_style,
is_inlay: true,
..Default::default()
}
}
};
@@ -706,24 +634,24 @@ impl InlayMap {
.take(len)
.collect::<String>();
let next_inlay = if i % 2 == 0 {
Inlay::mock_hint(
post_inc(next_inlay_id),
snapshot.buffer.anchor_at(position, bias),
text.clone(),
)
let inlay_id = if i % 2 == 0 {
InlayId::Hint(post_inc(next_inlay_id))
} else {
Inlay::inline_completion(
post_inc(next_inlay_id),
snapshot.buffer.anchor_at(position, bias),
text.clone(),
)
InlayId::InlineCompletion(post_inc(next_inlay_id))
};
let inlay_id = next_inlay.id;
log::info!(
"creating inlay {inlay_id:?} at buffer offset {position} with bias {bias:?} and text {text:?}"
"creating inlay {:?} at buffer offset {} with bias {:?} and text {:?}",
inlay_id,
position,
bias,
text
);
to_insert.push(next_inlay);
to_insert.push(Inlay {
id: inlay_id,
position: snapshot.buffer.anchor_at(position, bias),
text: text.into(),
});
} else {
to_remove.push(
self.inlays
@@ -1101,7 +1029,7 @@ impl InlaySnapshot {
#[cfg(test)]
pub fn text(&self) -> String {
self.chunks(Default::default()..self.len(), false, Highlights::default())
.map(|chunk| chunk.chunk.text)
.map(|chunk| chunk.text)
.collect()
}
@@ -1150,7 +1078,7 @@ mod tests {
use super::*;
use crate::{
InlayId, MultiBuffer,
display_map::{HighlightKey, InlayHighlights, TextHighlights},
display_map::{InlayHighlights, TextHighlights},
hover_links::InlayHighlight,
};
use gpui::{App, HighlightStyle};
@@ -1255,11 +1183,11 @@ mod tests {
let (inlay_snapshot, _) = inlay_map.splice(
&[],
vec![Inlay::mock_hint(
post_inc(&mut next_inlay_id),
buffer.read(cx).snapshot(cx).anchor_after(3),
"|123|",
)],
vec![Inlay {
id: InlayId::Hint(post_inc(&mut next_inlay_id)),
position: buffer.read(cx).snapshot(cx).anchor_after(3),
text: "|123|".into(),
}],
);
assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
assert_eq!(
@@ -1332,16 +1260,16 @@ mod tests {
let (inlay_snapshot, _) = inlay_map.splice(
&[],
vec![
Inlay::mock_hint(
post_inc(&mut next_inlay_id),
buffer.read(cx).snapshot(cx).anchor_before(3),
"|123|",
),
Inlay::inline_completion(
post_inc(&mut next_inlay_id),
buffer.read(cx).snapshot(cx).anchor_after(3),
"|456|",
),
Inlay {
id: InlayId::Hint(post_inc(&mut next_inlay_id)),
position: buffer.read(cx).snapshot(cx).anchor_before(3),
text: "|123|".into(),
},
Inlay {
id: InlayId::InlineCompletion(post_inc(&mut next_inlay_id)),
position: buffer.read(cx).snapshot(cx).anchor_after(3),
text: "|456|".into(),
},
],
);
assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
@@ -1547,21 +1475,21 @@ mod tests {
let (inlay_snapshot, _) = inlay_map.splice(
&[],
vec![
Inlay::mock_hint(
post_inc(&mut next_inlay_id),
buffer.read(cx).snapshot(cx).anchor_before(0),
"|123|\n",
),
Inlay::mock_hint(
post_inc(&mut next_inlay_id),
buffer.read(cx).snapshot(cx).anchor_before(4),
"|456|",
),
Inlay::inline_completion(
post_inc(&mut next_inlay_id),
buffer.read(cx).snapshot(cx).anchor_before(7),
"\n|567|\n",
),
Inlay {
id: InlayId::Hint(post_inc(&mut next_inlay_id)),
position: buffer.read(cx).snapshot(cx).anchor_before(0),
text: "|123|\n".into(),
},
Inlay {
id: InlayId::Hint(post_inc(&mut next_inlay_id)),
position: buffer.read(cx).snapshot(cx).anchor_before(4),
text: "|456|".into(),
},
Inlay {
id: InlayId::InlineCompletion(post_inc(&mut next_inlay_id)),
position: buffer.read(cx).snapshot(cx).anchor_before(7),
text: "\n|567|\n".into(),
},
],
);
assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
@@ -1664,7 +1592,7 @@ mod tests {
text_highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
log::info!("highlighting text ranges {text_highlight_ranges:?}");
text_highlights.insert(
HighlightKey::Type(TypeId::of::<()>()),
TypeId::of::<()>(),
Arc::new((
HighlightStyle::default(),
text_highlight_ranges
@@ -1739,7 +1667,7 @@ mod tests {
..Highlights::default()
},
)
.map(|chunk| chunk.chunk.text)
.map(|chunk| chunk.text)
.collect::<String>();
assert_eq!(
actual_text,

View File

@@ -29,7 +29,6 @@ mod inlay_hint_cache;
pub mod items;
mod jsx_tag_auto_close;
mod linked_editing_ranges;
mod lsp_colors;
mod lsp_ext;
mod mouse_context_menu;
pub mod movement;
@@ -64,8 +63,8 @@ use dap::TelemetrySpawnLocation;
use display_map::*;
pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
pub use editor_settings::{
CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowScrollbar,
CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
SearchSettings, ShowScrollbar,
};
use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
pub use editor_settings_controls::*;
@@ -73,13 +72,13 @@ use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layo
pub use element::{
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
};
use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
use futures::{
FutureExt, StreamExt as _,
future::{self, Shared, join},
stream::FuturesUnordered,
};
use fuzzy::{StringMatch, StringMatchCandidate};
use lsp_colors::LspColorData;
use ::git::blame::BlameEntry;
use ::git::{Restore, blame::ParsedCommitMessage};
@@ -110,9 +109,10 @@ pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
use language::{
AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
WordsQuery,
language_settings::{
self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
all_language_settings, language_settings,
@@ -125,7 +125,7 @@ use markdown::Markdown;
use mouse_context_menu::MouseContextMenu;
use persistence::DB;
use project::{
BreakpointWithPosition, CompletionResponse, ProjectPath,
BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
debugger::{
breakpoint_store::{
BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
@@ -133,7 +133,6 @@ use project::{
},
session::{Session, SessionEvent},
},
git_store::{GitStoreEvent, RepositoryEvent},
project_settings::DiagnosticSeverity,
};
@@ -197,7 +196,7 @@ pub use sum_tree::Bias;
use sum_tree::TreeMap;
use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
use theme::{
ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
observe_buffer_font_size_adjustment,
};
use ui::{
@@ -275,19 +274,16 @@ impl InlineValueCache {
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum InlayId {
InlineCompletion(usize),
DebuggerValue(usize),
// LSP
Hint(usize),
Color(usize),
DebuggerValue(usize),
}
impl InlayId {
fn id(&self) -> usize {
match self {
Self::InlineCompletion(id) => *id,
Self::DebuggerValue(id) => *id,
Self::Hint(id) => *id,
Self::Color(id) => *id,
Self::DebuggerValue(id) => *id,
}
}
}
@@ -540,7 +536,6 @@ pub enum SoftWrap {
#[derive(Clone)]
pub struct EditorStyle {
pub background: Hsla,
pub border: Hsla,
pub local_player: PlayerColor,
pub text: TextStyle,
pub scrollbar_width: Pixels,
@@ -556,7 +551,6 @@ impl Default for EditorStyle {
fn default() -> Self {
Self {
background: Hsla::default(),
border: Hsla::default(),
local_player: PlayerColor::default(),
text: TextStyle::default(),
scrollbar_width: Pixels::default(),
@@ -710,7 +704,7 @@ impl EditorActionId {
// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
#[derive(Default)]
@@ -1019,7 +1013,7 @@ pub struct Editor {
placeholder_text: Option<Arc<str>>,
highlight_order: usize,
highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
background_highlights: TreeMap<TypeId, BackgroundHighlight>,
gutter_highlights: TreeMap<TypeId, GutterHighlight>,
scrollbar_marker_state: ScrollbarMarkerState,
active_indent_guides_state: ActiveIndentGuidesState,
@@ -1135,8 +1129,6 @@ pub struct Editor {
inline_value_cache: InlineValueCache,
selection_drag_state: SelectionDragState,
drag_and_drop_selection_enabled: bool,
next_color_inlay_id: usize,
colors: Option<LspColorData>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@@ -1804,7 +1796,7 @@ impl Editor {
editor.tasks_update_task =
Some(editor.refresh_runnables(window, cx));
}
editor.update_lsp_data(true, None, window, cx);
editor.pull_diagnostics(None, window, cx);
}
project::Event::SnippetEdit(id, snippet_edits) => {
if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
@@ -1863,31 +1855,6 @@ impl Editor {
_ => {}
},
));
let git_store = project.read(cx).git_store().clone();
let project = project.clone();
project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
match event {
GitStoreEvent::RepositoryUpdated(
_,
RepositoryEvent::Updated {
new_instance: true, ..
},
_,
) => {
this.load_diff_task = Some(
update_uncommitted_diff_for_buffer(
cx.entity(),
&project,
this.buffer.read(cx).all_buffers(),
this.buffer.clone(),
cx,
)
.shared(),
);
}
_ => {}
}
}));
}
}
@@ -2098,8 +2065,6 @@ impl Editor {
],
tasks_update_task: None,
pull_diagnostics_task: Task::ready(()),
colors: None,
next_color_inlay_id: 0,
linked_edit_ranges: Default::default(),
in_project_search: false,
previous_search_ranges: None,
@@ -2241,8 +2206,7 @@ impl Editor {
editor.minimap =
editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
editor.colors = Some(LspColorData::new(cx));
editor.update_lsp_data(false, None, window, cx);
editor.pull_diagnostics(None, window, cx);
}
editor.report_editor_event("Editor Opened", None, cx);
@@ -4930,15 +4894,6 @@ impl Editor {
.collect()
}
#[cfg(any(test, feature = "test-support"))]
pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
self.display_map
.read(cx)
.current_inlays()
.cloned()
.collect()
}
fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
if self.semantics_provider.is_none() || !self.mode.is_full() {
return;
@@ -5039,7 +4994,7 @@ impl Editor {
to_insert,
}) = self.inlay_hint_cache.spawn_hint_refresh(
reason_description,
self.visible_excerpts(required_languages.as_ref(), cx),
self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
invalidate_cache,
ignore_debounce,
cx,
@@ -5057,7 +5012,7 @@ impl Editor {
.collect()
}
pub fn visible_excerpts(
pub fn excerpts_for_inlay_hints_query(
&self,
restrict_to_languages: Option<&HashSet<Arc<Language>>>,
cx: &mut Context<Editor>,
@@ -5268,37 +5223,6 @@ impl Editor {
.as_ref()
.map_or(true, |provider| provider.filter_completions());
let trigger_kind = match trigger {
Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
CompletionTriggerKind::TRIGGER_CHARACTER
}
_ => CompletionTriggerKind::INVOKED,
};
let completion_context = CompletionContext {
trigger_character: trigger.and_then(|trigger| {
if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
Some(String::from(trigger))
} else {
None
}
}),
trigger_kind,
};
// Hide the current completions menu when a trigger char is typed. Without this, cached
// completions from before the trigger char may be reused (#32774). Snippet choices could
// involve trigger chars, so this is skipped in that case.
if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
{
let menu_is_open = matches!(
self.context_menu.borrow().as_ref(),
Some(CodeContextMenu::Completions(_))
);
if menu_is_open {
self.hide_context_menu(window, cx);
}
}
if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
if filter_completions {
menu.filter(query.clone(), provider.clone(), window, cx);
@@ -5329,6 +5253,23 @@ impl Editor {
}
};
let trigger_kind = match trigger {
Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
CompletionTriggerKind::TRIGGER_CHARACTER
}
_ => CompletionTriggerKind::INVOKED,
};
let completion_context = CompletionContext {
trigger_character: trigger.and_then(|trigger| {
if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
Some(String::from(trigger))
} else {
None
}
}),
trigger_kind,
};
let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
buffer_snapshot.surrounding_word(buffer_position)
{
@@ -5943,23 +5884,15 @@ impl Editor {
editor.update_in(cx, |editor, window, cx| {
crate::hover_popover::hide_hover(editor, cx);
let actions = CodeActionContents::new(
resolved_tasks,
code_actions,
debug_scenarios,
task_context.unwrap_or_default(),
);
// Don't show the menu if there are no actions available
if actions.is_empty() {
cx.notify();
return Task::ready(Ok(()));
}
*editor.context_menu.borrow_mut() =
Some(CodeContextMenu::CodeActions(CodeActionsMenu {
buffer,
actions,
actions: CodeActionContents::new(
resolved_tasks,
code_actions,
debug_scenarios,
task_context.unwrap_or_default(),
),
selected_item: Default::default(),
scroll_handle: UniformListScrollHandle::default(),
deployed_from,
@@ -5987,41 +5920,45 @@ impl Editor {
buffer: &Entity<Buffer>,
cx: &mut App,
) -> Task<Vec<task::DebugScenario>> {
maybe!({
let project = self.project.as_ref()?;
let dap_store = project.read(cx).dap_store();
let mut scenarios = vec![];
let resolved_tasks = resolved_tasks.as_ref()?;
let buffer = buffer.read(cx);
let language = buffer.language()?;
let file = buffer.file();
let debug_adapter = language_settings(language.name().into(), file, cx)
.debuggers
.first()
.map(SharedString::from)
.or_else(|| language.config().debuggers.first().map(SharedString::from))?;
if cx.has_flag::<DebuggerFeatureFlag>() {
maybe!({
let project = self.project.as_ref()?;
let dap_store = project.read(cx).dap_store();
let mut scenarios = vec![];
let resolved_tasks = resolved_tasks.as_ref()?;
let buffer = buffer.read(cx);
let language = buffer.language()?;
let file = buffer.file();
let debug_adapter = language_settings(language.name().into(), file, cx)
.debuggers
.first()
.map(SharedString::from)
.or_else(|| language.config().debuggers.first().map(SharedString::from))?;
dap_store.update(cx, |dap_store, cx| {
for (_, task) in &resolved_tasks.templates {
let maybe_scenario = dap_store.debug_scenario_for_build_task(
task.original_task().clone(),
debug_adapter.clone().into(),
task.display_label().to_owned().into(),
cx,
);
scenarios.push(maybe_scenario);
}
});
Some(cx.background_spawn(async move {
let scenarios = futures::future::join_all(scenarios)
.await
.into_iter()
.flatten()
.collect::<Vec<_>>();
scenarios
}))
})
.unwrap_or_else(|| Task::ready(vec![]))
dap_store.update(cx, |dap_store, cx| {
for (_, task) in &resolved_tasks.templates {
let maybe_scenario = dap_store.debug_scenario_for_build_task(
task.original_task().clone(),
debug_adapter.clone().into(),
task.display_label().to_owned().into(),
cx,
);
scenarios.push(maybe_scenario);
}
});
Some(cx.background_spawn(async move {
let scenarios = futures::future::join_all(scenarios)
.await
.into_iter()
.flatten()
.collect::<Vec<_>>();
scenarios
}))
})
.unwrap_or_else(|| Task::ready(vec![]))
} else {
Task::ready(vec![])
}
}
fn code_actions(
@@ -6204,7 +6141,7 @@ impl Editor {
editor.update(cx, |editor, cx| {
editor.highlight_background::<Self>(
&ranges_to_highlight,
|theme| theme.colors().editor_highlighted_line_background,
|theme| theme.editor_highlighted_line_background,
cx,
);
});
@@ -6559,12 +6496,12 @@ impl Editor {
this.highlight_background::<DocumentHighlightRead>(
&read_ranges,
|theme| theme.colors().editor_document_highlight_read_background,
|theme| theme.editor_document_highlight_read_background,
cx,
);
this.highlight_background::<DocumentHighlightWrite>(
&write_ranges,
|theme| theme.colors().editor_document_highlight_write_background,
|theme| theme.editor_document_highlight_write_background,
cx,
);
cx.notify();
@@ -6666,7 +6603,7 @@ impl Editor {
if !match_ranges.is_empty() {
editor.highlight_background::<SelectedTextHighlight>(
&match_ranges,
|theme| theme.colors().editor_document_highlight_bracket_background,
|theme| theme.editor_document_highlight_bracket_background,
cx,
)
}
@@ -10166,6 +10103,9 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) {
if !cx.has_flag::<DebuggerFeatureFlag>() {
return;
}
let source = self
.buffer
.read(cx)
@@ -15182,21 +15122,16 @@ impl Editor {
})
.context("location tasks preparation")?;
let locations: Vec<Location> = future::join_all(location_tasks)
let locations = future::join_all(location_tasks)
.await
.into_iter()
.filter_map(|location| location.transpose())
.collect::<Result<_>>()
.context("location tasks")?;
if locations.is_empty() {
return Ok(Navigated::No);
}
let Some(workspace) = workspace else {
return Ok(Navigated::No);
};
let opened = workspace
.update_in(cx, |workspace, window, cx| {
Self::open_locations_in_multibuffer(
@@ -15360,11 +15295,6 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Workspace>,
) {
if locations.is_empty() {
log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
return;
}
// If there are multiple definitions, open them in a multibuffer
locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
let mut locations = locations.into_iter().peekable();
@@ -15421,7 +15351,7 @@ impl Editor {
}
editor.highlight_background::<Self>(
&ranges,
|theme| theme.colors().editor_highlighted_line_background,
|theme| theme.editor_highlighted_line_background,
cx,
);
}
@@ -16290,14 +16220,8 @@ impl Editor {
let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
buffers
.into_iter()
.filter_map(|buffer| {
project
.update(cx, |project, cx| {
project.lsp_store().update(cx, |lsp_store, cx| {
lsp_store.pull_diagnostics_for_buffer(buffer, cx)
})
})
.ok()
.flat_map(|buffer| {
Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
})
.collect::<FuturesUnordered<_>>()
}) else {
@@ -16974,9 +16898,9 @@ impl Editor {
self.active_indent_guides_state.dirty = true;
}
pub fn update_renderer_widths(
pub fn update_fold_widths(
&mut self,
widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
widths: impl IntoIterator<Item = (FoldId, Pixels)>,
cx: &mut Context<Self>,
) -> bool {
self.display_map
@@ -18354,18 +18278,18 @@ impl Editor {
return;
};
let title = multibuffer.title(cx).to_string();
let locations = self
.selections
.all_anchors(cx)
.into_iter()
.map(|selection| Location {
.disjoint_anchors()
.iter()
.map(|range| Location {
buffer: buffer.clone(),
range: selection.start.text_anchor..selection.end.text_anchor,
range: range.start.text_anchor..range.end.text_anchor,
})
.collect::<Vec<_>>();
let title = multibuffer.title(cx).to_string();
cx.spawn_in(window, async move |_, cx| {
workspace.update_in(cx, |workspace, window, cx| {
Self::open_locations_in_multibuffer(
@@ -18576,7 +18500,7 @@ impl Editor {
pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
self.highlight_background::<SearchWithinRange>(
ranges,
|colors| colors.colors().editor_document_highlight_read_background,
|colors| colors.editor_document_highlight_read_background,
cx,
)
}
@@ -18592,28 +18516,11 @@ impl Editor {
pub fn highlight_background<T: 'static>(
&mut self,
ranges: &[Range<Anchor>],
color_fetcher: fn(&Theme) -> Hsla,
color_fetcher: fn(&ThemeColors) -> Hsla,
cx: &mut Context<Self>,
) {
self.background_highlights.insert(
HighlightKey::Type(TypeId::of::<T>()),
(color_fetcher, Arc::from(ranges)),
);
self.scrollbar_marker_state.dirty = true;
cx.notify();
}
pub fn highlight_background_key<T: 'static>(
&mut self,
key: usize,
ranges: &[Range<Anchor>],
color_fetcher: fn(&Theme) -> Hsla,
cx: &mut Context<Self>,
) {
self.background_highlights.insert(
HighlightKey::TypePlus(TypeId::of::<T>(), key),
(color_fetcher, Arc::from(ranges)),
);
self.background_highlights
.insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
self.scrollbar_marker_state.dirty = true;
cx.notify();
}
@@ -18622,9 +18529,7 @@ impl Editor {
&mut self,
cx: &mut Context<Self>,
) -> Option<BackgroundHighlight> {
let text_highlights = self
.background_highlights
.remove(&HighlightKey::Type(TypeId::of::<T>()))?;
let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
if !text_highlights.1.is_empty() {
self.scrollbar_marker_state.dirty = true;
cx.notify();
@@ -18710,30 +18615,6 @@ impl Editor {
.insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
}
#[cfg(feature = "test-support")]
pub fn all_text_highlights(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
let snapshot = self.snapshot(window, cx);
self.display_map.update(cx, |display_map, _| {
display_map
.all_text_highlights()
.map(|highlight| {
let (style, ranges) = highlight.as_ref();
(
*style,
ranges
.iter()
.map(|range| range.clone().to_display_points(&snapshot))
.collect(),
)
})
.collect()
})
}
#[cfg(feature = "test-support")]
pub fn all_text_background_highlights(
&self,
@@ -18744,7 +18625,8 @@ impl Editor {
let buffer = &snapshot.buffer_snapshot;
let start = buffer.anchor_before(0);
let end = buffer.anchor_after(buffer.len());
self.background_highlights_in_range(start..end, &snapshot, cx.theme())
let theme = cx.theme().colors();
self.background_highlights_in_range(start..end, &snapshot, theme)
}
#[cfg(feature = "test-support")]
@@ -18753,9 +18635,7 @@ impl Editor {
let highlights = self
.background_highlights
.get(&HighlightKey::Type(TypeId::of::<
items::BufferSearchHighlights,
>()));
.get(&TypeId::of::<items::BufferSearchHighlights>());
if let Some((_color, ranges)) = highlights {
ranges
@@ -18774,11 +18654,11 @@ impl Editor {
) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
let read_highlights = self
.background_highlights
.get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
.get(&TypeId::of::<DocumentHighlightRead>())
.map(|h| &h.1);
let write_highlights = self
.background_highlights
.get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
.get(&TypeId::of::<DocumentHighlightWrite>())
.map(|h| &h.1);
let left_position = position.bias_left(buffer);
let right_position = position.bias_right(buffer);
@@ -18805,7 +18685,7 @@ impl Editor {
pub fn has_background_highlights<T: 'static>(&self) -> bool {
self.background_highlights
.get(&HighlightKey::Type(TypeId::of::<T>()))
.get(&TypeId::of::<T>())
.map_or(false, |(_, highlights)| !highlights.is_empty())
}
@@ -18813,7 +18693,7 @@ impl Editor {
&self,
search_range: Range<Anchor>,
display_snapshot: &DisplaySnapshot,
theme: &Theme,
theme: &ThemeColors,
) -> Vec<(Range<DisplayPoint>, Hsla)> {
let mut results = Vec::new();
for (color_fetcher, ranges) in self.background_highlights.values() {
@@ -18854,10 +18734,7 @@ impl Editor {
count: usize,
) -> Vec<RangeInclusive<DisplayPoint>> {
let mut results = Vec::new();
let Some((_, ranges)) = self
.background_highlights
.get(&HighlightKey::Type(TypeId::of::<T>()))
else {
let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
return vec![];
};
@@ -18994,23 +18871,6 @@ impl Editor {
.collect()
}
pub fn highlight_text_key<T: 'static>(
&mut self,
key: usize,
ranges: Vec<Range<Anchor>>,
style: HighlightStyle,
cx: &mut Context<Self>,
) {
self.display_map.update(cx, |map, _| {
map.highlight_text(
HighlightKey::TypePlus(TypeId::of::<T>(), key),
ranges,
style,
);
});
cx.notify();
}
pub fn highlight_text<T: 'static>(
&mut self,
ranges: Vec<Range<Anchor>>,
@@ -19018,7 +18878,7 @@ impl Editor {
cx: &mut Context<Self>,
) {
self.display_map.update(cx, |map, _| {
map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
map.highlight_text(TypeId::of::<T>(), ranges, style)
});
cx.notify();
}
@@ -19147,7 +19007,7 @@ impl Editor {
.into_iter()
.flatten()
.for_each(|hint| {
let inlay = Inlay::debugger(
let inlay = Inlay::debugger_hint(
post_inc(&mut editor.next_inlay_id),
Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
hint.text(),
@@ -19198,15 +19058,17 @@ impl Editor {
.register_buffer_with_language_servers(&edited_buffer, cx)
});
});
if edited_buffer.read(cx).file().is_some() {
self.pull_diagnostics(
Some(edited_buffer.read(cx).remote_id()),
window,
cx,
);
}
}
}
cx.emit(EditorEvent::BufferEdited);
cx.emit(SearchEvent::MatchesInvalidated);
if let Some(buffer) = edited_buffer {
self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
}
if *singleton_buffer_edited {
if let Some(buffer) = edited_buffer {
if buffer.read(cx).file().is_none() {
@@ -19269,7 +19131,6 @@ impl Editor {
.detach();
}
}
self.update_lsp_data(false, Some(buffer_id), window, cx);
cx.emit(EditorEvent::ExcerptsAdded {
buffer: buffer.clone(),
predecessor: *predecessor,
@@ -19289,7 +19150,7 @@ impl Editor {
cx.emit(EditorEvent::ExcerptsRemoved {
ids: ids.clone(),
removed_buffer_ids: removed_buffer_ids.clone(),
});
})
}
multi_buffer::Event::ExcerptsEdited {
excerpt_ids,
@@ -19300,7 +19161,7 @@ impl Editor {
});
cx.emit(EditorEvent::ExcerptsEdited {
ids: excerpt_ids.clone(),
});
})
}
multi_buffer::Event::ExcerptsExpanded { ids } => {
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
@@ -19446,15 +19307,6 @@ impl Editor {
}
}
if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
}) {
if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
}
self.refresh_colors(false, None, window, cx);
}
cx.notify();
}
@@ -20338,17 +20190,6 @@ impl Editor {
self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
}
fn update_lsp_data(
&mut self,
ignore_cache: bool,
for_buffer: Option<BufferId>,
window: &mut Window,
cx: &mut Context<'_, Self>,
) {
self.pull_diagnostics(for_buffer, window, cx);
self.refresh_colors(ignore_cache, for_buffer, window, cx);
}
}
fn vim_enabled(cx: &App) -> bool {
@@ -21035,6 +20876,12 @@ pub trait SemanticsProvider {
new_name: String,
cx: &mut App,
) -> Option<Task<Result<ProjectTransaction>>>;
fn pull_diagnostics_for_buffer(
&self,
buffer: Entity<Buffer>,
cx: &mut App,
) -> Task<anyhow::Result<()>>;
}
pub trait CompletionProvider {
@@ -21230,7 +21077,6 @@ fn snippet_completions(
&candidates,
&last_word,
last_word.chars().any(|c| c.is_uppercase()),
true,
MAX_RESULTS,
&Default::default(),
executor.clone(),
@@ -21553,6 +21399,85 @@ impl SemanticsProvider for Entity<Project> {
project.perform_rename(buffer.clone(), position, new_name, cx)
}))
}
fn pull_diagnostics_for_buffer(
&self,
buffer: Entity<Buffer>,
cx: &mut App,
) -> Task<anyhow::Result<()>> {
let diagnostics = self.update(cx, |project, cx| {
project
.lsp_store()
.update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
});
let project = self.clone();
cx.spawn(async move |cx| {
let diagnostics = diagnostics.await.context("pulling diagnostics")?;
project.update(cx, |project, cx| {
project.lsp_store().update(cx, |lsp_store, cx| {
for diagnostics_set in diagnostics {
let LspPullDiagnostics::Response {
server_id,
uri,
diagnostics,
} = diagnostics_set
else {
continue;
};
let adapter = lsp_store.language_server_adapter_for_id(server_id);
let disk_based_sources = adapter
.as_ref()
.map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
.unwrap_or(&[]);
match diagnostics {
PulledDiagnostics::Unchanged { result_id } => {
lsp_store
.merge_diagnostics(
server_id,
lsp::PublishDiagnosticsParams {
uri: uri.clone(),
diagnostics: Vec::new(),
version: None,
},
Some(result_id),
DiagnosticSourceKind::Pulled,
disk_based_sources,
|_, _| true,
cx,
)
.log_err();
}
PulledDiagnostics::Changed {
diagnostics,
result_id,
} => {
lsp_store
.merge_diagnostics(
server_id,
lsp::PublishDiagnosticsParams {
uri: uri.clone(),
diagnostics,
version: None,
},
result_id,
DiagnosticSourceKind::Pulled,
disk_based_sources,
|old_diagnostic, _| match old_diagnostic.source_kind {
DiagnosticSourceKind::Pulled => false,
DiagnosticSourceKind::Other
| DiagnosticSourceKind::Pushed => true,
},
cx,
)
.log_err();
}
}
}
})
})
})
}
}
fn inlay_hint_settings(
@@ -22033,7 +21958,6 @@ impl Render for Editor {
&cx.entity(),
EditorStyle {
background,
border: cx.theme().colors().border,
local_player: cx.theme().players().local(),
text: text_style,
scrollbar_width: EditorElement::SCROLLBAR_WIDTH,

View File

@@ -50,22 +50,6 @@ pub struct EditorSettings {
pub diagnostics_max_severity: Option<DiagnosticSeverity>,
pub inline_code_actions: bool,
pub drag_and_drop_selection: bool,
pub lsp_document_colors: DocumentColorsRenderMode,
}
/// How to render LSP `textDocument/documentColor` colors in the editor.
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DocumentColorsRenderMode {
/// Do not query and render document colors.
None,
/// Render document colors as inlay hints near the color text.
#[default]
Inlay,
/// Draw a border around the color text.
Border,
/// Draw a background behind the color text.
Background,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -146,7 +130,6 @@ pub struct Scrollbar {
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct Minimap {
pub show: ShowMinimap,
pub display_in: DisplayIn,
pub thumb: MinimapThumb,
pub thumb_border: MinimapThumbBorder,
pub current_line_highlight: Option<CurrentLineHighlight>,
@@ -157,11 +140,6 @@ impl Minimap {
self.show != ShowMinimap::Never
}
#[inline]
pub fn on_active_editor(&self) -> bool {
self.display_in == DisplayIn::ActiveEditor
}
pub fn with_show_override(self) -> Self {
Self {
show: ShowMinimap::Always,
@@ -211,19 +189,6 @@ pub enum ShowMinimap {
Never,
}
/// Where to show the minimap in the editor.
///
/// Default: all_editors
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DisplayIn {
/// Show on all open editors.
AllEditors,
/// Show the minimap on the active editor only.
#[default]
ActiveEditor,
}
/// When to show the minimap thumb.
///
/// Default: always
@@ -537,11 +502,6 @@ pub struct EditorSettingsContent {
///
/// Default: true
pub drag_and_drop_selection: Option<bool>,
/// How to render LSP `textDocument/documentColor` colors in the editor.
///
/// Default: [`DocumentColorsRenderMode::Inlay`]
pub lsp_document_colors: Option<DocumentColorsRenderMode>,
}
// Toolbar related settings
@@ -613,11 +573,6 @@ pub struct MinimapContent {
/// Default: never
pub show: Option<ShowMinimap>,
/// Where to show the minimap in the editor.
///
/// Default: [`DisplayIn::ActiveEditor`]
pub display_in: Option<DisplayIn>,
/// When to show the minimap thumb.
///
/// Default: always

Some files were not shown because too many files have changed in this diff Show More