Compare commits
162 Commits
debug-shel
...
fix-unicod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
211f20f41f | ||
|
|
6107e7c604 | ||
|
|
53ce77a0f7 | ||
|
|
b69a09892b | ||
|
|
7e152e0439 | ||
|
|
45fd87e63a | ||
|
|
6e19923c27 | ||
|
|
92fb7656c4 | ||
|
|
2de99369f4 | ||
|
|
64c413b9b6 | ||
|
|
5f70a9cf59 | ||
|
|
105acacff9 | ||
|
|
aa60647fe8 | ||
|
|
4cc06bfb4e | ||
|
|
11cb9ddeb9 | ||
|
|
169620632a | ||
|
|
f9bd54b4b1 | ||
|
|
4fdda8d5a1 | ||
|
|
9dc3ac9657 | ||
|
|
6d09f3fa40 | ||
|
|
132bba8d8b | ||
|
|
903212b7f5 | ||
|
|
c15d02454e | ||
|
|
f000dfebd2 | ||
|
|
123a25c58c | ||
|
|
79f3cb1225 | ||
|
|
faca128304 | ||
|
|
3f0f316f4d | ||
|
|
9d6b2e8a32 | ||
|
|
1d74fdc59f | ||
|
|
b7bfdd3383 | ||
|
|
0e2e5b8b0d | ||
|
|
6b06685723 | ||
|
|
8d894dd1df | ||
|
|
0eee768e7b | ||
|
|
f1f19a32fb | ||
|
|
2ff155d5a2 | ||
|
|
eb74df632b | ||
|
|
0068de0386 | ||
|
|
a11647d07f | ||
|
|
274f2e90da | ||
|
|
31b7786be7 | ||
|
|
351ba5023b | ||
|
|
3041de0cdf | ||
|
|
52c42125a7 | ||
|
|
62e8f45304 | ||
|
|
0fe73a99e5 | ||
|
|
6e9c6c5684 | ||
|
|
42f788185a | ||
|
|
a5b2428897 | ||
|
|
0629804390 | ||
|
|
3151b5efc1 | ||
|
|
782fbfad90 | ||
|
|
2caa19214b | ||
|
|
bff5d85ff4 | ||
|
|
abe5d523e1 | ||
|
|
8fb3199a84 | ||
|
|
0d809c21ba | ||
|
|
93b1e95a5d | ||
|
|
49bc2e61da | ||
|
|
9a4bcd11a2 | ||
|
|
2ee5bedfa9 | ||
|
|
d497f52e17 | ||
|
|
f022a13091 | ||
|
|
c74ecb4654 | ||
|
|
7609ca7a8d | ||
|
|
32906bfa7c | ||
|
|
5fafab6e52 | ||
|
|
a2e786e0f9 | ||
|
|
b0086b472f | ||
|
|
d10cc13924 | ||
|
|
2680a78f9c | ||
|
|
197828980c | ||
|
|
7c4da37322 | ||
|
|
ce164f5e65 | ||
|
|
42c59014a9 | ||
|
|
3db452eec7 | ||
|
|
6e77e8405b | ||
|
|
465f64da7e | ||
|
|
e5a8cc7aab | ||
|
|
bdf29bf76f | ||
|
|
402c61c00d | ||
|
|
59e88ce82b | ||
|
|
22ab4c53d1 | ||
|
|
f106ea7641 | ||
|
|
e37ef2a991 | ||
|
|
1c05062482 | ||
|
|
8c04f12499 | ||
|
|
aa7ccecc49 | ||
|
|
f4aeeda2d9 | ||
|
|
ca0bd53bed | ||
|
|
ae6237178c | ||
|
|
ac3328adb6 | ||
|
|
d63909c598 | ||
|
|
c3d0230f89 | ||
|
|
bc5927d5af | ||
|
|
d2cf995e27 | ||
|
|
86161aa427 | ||
|
|
a602b4b305 | ||
|
|
047d515abf | ||
|
|
e5bcd720e1 | ||
|
|
41583fb066 | ||
|
|
521a223681 | ||
|
|
c8c6468f9c | ||
|
|
3f4098e87b | ||
|
|
1d684c8890 | ||
|
|
97c5c5a6e7 | ||
|
|
ba4fc1bcfc | ||
|
|
bbf16bda75 | ||
|
|
c56b8904cc | ||
|
|
695118d110 | ||
|
|
a675ca7a1e | ||
|
|
6e762d9c05 | ||
|
|
28380d714d | ||
|
|
f338c46bf7 | ||
|
|
5fbb7b0d40 | ||
|
|
f12b0dddf4 | ||
|
|
14bb10d783 | ||
|
|
c9ce4aec91 | ||
|
|
01dfb6fa82 | ||
|
|
f9987a1141 | ||
|
|
7432e947bc | ||
|
|
157199b65b | ||
|
|
d74f3f4ea6 | ||
|
|
9e2023bffc | ||
|
|
3ab4ad6de8 | ||
|
|
e3ce0618a3 | ||
|
|
865dd4c5fc | ||
|
|
2178f66af6 | ||
|
|
338a7395a7 | ||
|
|
4c2415b338 | ||
|
|
e6bc1308af | ||
|
|
6c46e1129d | ||
|
|
fbb5628ec6 | ||
|
|
8c9116daa5 | ||
|
|
20a3e613b8 | ||
|
|
ba1c05abf2 | ||
|
|
2823771c06 | ||
|
|
343f155ab9 | ||
|
|
2dece13d83 | ||
|
|
985dcf7523 | ||
|
|
b079871428 | ||
|
|
4983b01c89 | ||
|
|
35863c4302 | ||
|
|
a0bd25f218 | ||
|
|
8a1e795746 | ||
|
|
f4818b648e | ||
|
|
7031ed8b87 | ||
|
|
6073d2c93c | ||
|
|
00499aadd4 | ||
|
|
d1eb69c6cd | ||
|
|
40cbfb7eb2 | ||
|
|
5d0f02d356 | ||
|
|
ca8e213151 | ||
|
|
90c893747c | ||
|
|
d09c7eb317 | ||
|
|
1753432406 | ||
|
|
d9218b10ea | ||
|
|
dfdeb1bf51 | ||
|
|
b9f81c7ce7 | ||
|
|
b1450b6d71 | ||
|
|
1af9f98c1d |
16
.github/workflows/ci.yml
vendored
@@ -30,6 +30,7 @@ jobs:
|
||||
run_tests: ${{ steps.filter.outputs.run_tests }}
|
||||
run_license: ${{ steps.filter.outputs.run_license }}
|
||||
run_docs: ${{ steps.filter.outputs.run_docs }}
|
||||
run_nix: ${{ steps.filter.outputs.run_nix }}
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
@@ -69,6 +70,12 @@ jobs:
|
||||
else
|
||||
echo "run_license=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
NIX_REGEX='^(nix/|flake\.|Cargo\.|rust-toolchain.toml|\.cargo/config.toml)'
|
||||
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep "$NIX_REGEX") ]]; then
|
||||
echo "run_nix=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "run_nix=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
migration_checks:
|
||||
name: Check Postgres and Protobuf migrations, mergability
|
||||
@@ -460,8 +467,10 @@ jobs:
|
||||
RET_CODE=0
|
||||
# Always check style
|
||||
[[ "${{ needs.style.result }}" != 'success' ]] && { RET_CODE=1; echo "style tests failed"; }
|
||||
[[ "${{ needs.check_docs.result }}" != 'success' ]] && { RET_CODE=1; echo "docs checks failed"; }
|
||||
|
||||
if [[ "${{ needs.job_spec.outputs.run_docs }}" == "true" ]]; then
|
||||
[[ "${{ needs.check_docs.result }}" != 'success' ]] && { RET_CODE=1; echo "docs checks failed"; }
|
||||
fi
|
||||
# Only check test jobs if they were supposed to run
|
||||
if [[ "${{ needs.job_spec.outputs.run_tests }}" == "true" ]]; then
|
||||
[[ "${{ needs.workspace_hack.result }}" != 'success' ]] && { RET_CODE=1; echo "Workspace Hack failed"; }
|
||||
@@ -744,7 +753,10 @@ jobs:
|
||||
nix-build:
|
||||
name: Build with Nix
|
||||
uses: ./.github/workflows/nix.yml
|
||||
if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix')
|
||||
needs: [job_spec]
|
||||
if: github.repository_owner == 'zed-industries' &&
|
||||
(contains(github.event.pull_request.labels.*.name, 'run-nix') ||
|
||||
needs.job_spec.outputs.run_nix == 'true')
|
||||
secrets: inherit
|
||||
with:
|
||||
flake-output: debug
|
||||
|
||||
12
.github/workflows/eval.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
types: [synchronize, reopened, labeled]
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -25,16 +25,6 @@ env:
|
||||
ZED_EVAL_TELEMETRY: 1
|
||||
|
||||
jobs:
|
||||
# This is a no-op job that we run to prevent GitHub from marking the workflow
|
||||
# as failed for PRs that don't have the `run-eval` label.
|
||||
noop:
|
||||
name: No-op
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
steps:
|
||||
- name: No-op
|
||||
run: echo "Nothing to do"
|
||||
|
||||
run_eval:
|
||||
timeout-minutes: 60
|
||||
name: Run Agent Eval
|
||||
|
||||
89
Cargo.lock
generated
@@ -78,6 +78,7 @@ dependencies = [
|
||||
"language",
|
||||
"language_model",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
@@ -1910,7 +1911,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"strum 0.27.1",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
@@ -2076,7 +2076,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",
|
||||
@@ -2109,7 +2109,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",
|
||||
@@ -2119,7 +2119,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",
|
||||
@@ -4132,7 +4132,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dap-types"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/zed-industries/dap-types?rev=b40956a7f4d1939da67429d941389ee306a3a308#b40956a7f4d1939da67429d941389ee306a3a308"
|
||||
source = "git+https://github.com/zed-industries/dap-types?rev=7f39295b441614ca9dbf44293e53c32f666897f9#7f39295b441614ca9dbf44293e53c32f666897f9"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -4147,6 +4147,8 @@ dependencies = [
|
||||
"async-trait",
|
||||
"collections",
|
||||
"dap",
|
||||
"dotenvy",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"json_dotpath",
|
||||
@@ -4155,6 +4157,7 @@ dependencies = [
|
||||
"paths",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shlex",
|
||||
"task",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
@@ -4308,6 +4311,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
"bitflags 2.9.0",
|
||||
"client",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
@@ -4673,12 +4677,6 @@ dependencies = [
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
@@ -4811,6 +4809,7 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"release_channel",
|
||||
"rpc",
|
||||
"schemars",
|
||||
@@ -5111,7 +5110,7 @@ dependencies = [
|
||||
"collections",
|
||||
"debug_adapter_extension",
|
||||
"dirs 4.0.0",
|
||||
"dotenv",
|
||||
"dotenvy",
|
||||
"env_logger 0.11.8",
|
||||
"extension",
|
||||
"fs",
|
||||
@@ -8844,6 +8843,7 @@ dependencies = [
|
||||
"http_client",
|
||||
"imara-diff",
|
||||
"indoc",
|
||||
"inventory",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"lsp",
|
||||
@@ -8942,8 +8942,10 @@ dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws_http_client",
|
||||
"bedrock",
|
||||
"chrono",
|
||||
"client",
|
||||
"collections",
|
||||
"component",
|
||||
"copilot",
|
||||
"credentials_provider",
|
||||
"deepseek",
|
||||
@@ -9014,6 +9016,7 @@ dependencies = [
|
||||
"collections",
|
||||
"copilot",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
@@ -9237,7 +9240,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libwebrtc"
|
||||
version = "0.3.10"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"jni",
|
||||
@@ -9317,7 +9320,7 @@ checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
|
||||
[[package]]
|
||||
name = "livekit"
|
||||
version = "0.7.8"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"futures-util",
|
||||
@@ -9340,7 +9343,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "livekit-api"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
@@ -9364,7 +9367,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "livekit-protocol"
|
||||
version = "0.3.9"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"livekit-runtime",
|
||||
@@ -9381,7 +9384,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "livekit-runtime"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -12255,6 +12258,7 @@ dependencies = [
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
"markdown",
|
||||
"node_runtime",
|
||||
"parking_lot",
|
||||
"pathdiff",
|
||||
@@ -14049,12 +14053,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
checksum = "fe8c9d1c68d67dd9f97ecbc6f932b60eb289c5dbddd8aa1405484a8fd2fcd984"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"indexmap",
|
||||
"ref-cast",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -14062,9 +14067,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.22"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
||||
checksum = "6ca9fcb757952f8e8629b9ab066fc62da523c46c2b247b1708a3be06dd82530b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -14551,28 +14556,40 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"smallvec",
|
||||
"streaming-iterator",
|
||||
"tree-sitter",
|
||||
"tree-sitter-json",
|
||||
"unindent",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
"zlog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "settings_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"command_palette",
|
||||
"command_palette_hooks",
|
||||
"component",
|
||||
"db",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
"paths",
|
||||
"project",
|
||||
"schemars",
|
||||
"search",
|
||||
"serde",
|
||||
"settings",
|
||||
"theme",
|
||||
"tree-sitter-json",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
@@ -15523,6 +15540,18 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
|
||||
|
||||
[[package]]
|
||||
name = "svg_preview"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"editor",
|
||||
"file_icons",
|
||||
"gpui",
|
||||
"ui",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "svgtypes"
|
||||
version = "0.15.3"
|
||||
@@ -15994,6 +16023,7 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"indexmap",
|
||||
"inventory",
|
||||
"log",
|
||||
"palette",
|
||||
"parking_lot",
|
||||
@@ -16034,7 +16064,6 @@ dependencies = [
|
||||
"indexmap",
|
||||
"log",
|
||||
"palette",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
@@ -16865,8 +16894,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tree-sitter-python"
|
||||
version = "0.23.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d065aaa27f3aaceaf60c1f0e0ac09e1cb9eb8ed28e7bcdaa52129cffc7f4b04"
|
||||
source = "git+https://github.com/zed-industries/tree-sitter-python?rev=218fcbf3fda3d029225f3dec005cb497d111b35e#218fcbf3fda3d029225f3dec005cb497d111b35e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
@@ -18282,7 +18310,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "webrtc-sys"
|
||||
version = "0.3.7"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxx",
|
||||
@@ -18295,7 +18323,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "webrtc-sys-build"
|
||||
version = "0.3.6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"fs2",
|
||||
"regex",
|
||||
@@ -19915,7 +19943,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.194.0"
|
||||
version = "0.195.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"agent",
|
||||
@@ -20020,6 +20048,7 @@ dependencies = [
|
||||
"snippet_provider",
|
||||
"snippets_ui",
|
||||
"supermaven",
|
||||
"svg_preview",
|
||||
"sysinfo",
|
||||
"tab_switcher",
|
||||
"task",
|
||||
@@ -20112,9 +20141,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_llm_client"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de7d9523255f4e00ee3d0918e5407bd252d798a4a8e71f6d37f23317a1588203"
|
||||
checksum = "c740e29260b8797ad252c202ea09a255b3cbc13f30faaf92fb6b2490336106e0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
|
||||
25
Cargo.toml
@@ -95,6 +95,7 @@ members = [
|
||||
"crates/markdown_preview",
|
||||
"crates/media",
|
||||
"crates/menu",
|
||||
"crates/svg_preview",
|
||||
"crates/migrator",
|
||||
"crates/mistral",
|
||||
"crates/multi_buffer",
|
||||
@@ -275,6 +276,7 @@ go_to_line = { path = "crates/go_to_line" }
|
||||
google_ai = { path = "crates/google_ai" }
|
||||
gpui = { path = "crates/gpui", default-features = false, features = [
|
||||
"http_client",
|
||||
"screen-capture",
|
||||
] }
|
||||
gpui_macros = { path = "crates/gpui_macros" }
|
||||
gpui_tokio = { path = "crates/gpui_tokio" }
|
||||
@@ -304,6 +306,7 @@ lmstudio = { path = "crates/lmstudio" }
|
||||
lsp = { path = "crates/lsp" }
|
||||
markdown = { path = "crates/markdown" }
|
||||
markdown_preview = { path = "crates/markdown_preview" }
|
||||
svg_preview = { path = "crates/svg_preview" }
|
||||
media = { path = "crates/media" }
|
||||
menu = { path = "crates/menu" }
|
||||
migrator = { path = "crates/migrator" }
|
||||
@@ -423,9 +426,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"
|
||||
@@ -442,12 +445,12 @@ core-video = { version = "0.4.3", features = ["metal"] }
|
||||
cpal = "0.16"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
ctor = "0.4.0"
|
||||
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "b40956a7f4d1939da67429d941389ee306a3a308" }
|
||||
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "7f39295b441614ca9dbf44293e53c32f666897f9" }
|
||||
dashmap = "6.0"
|
||||
derive_more = "0.99.17"
|
||||
dirs = "4.0"
|
||||
documented = "0.9.1"
|
||||
dotenv = "0.15.0"
|
||||
dotenvy = "0.15.0"
|
||||
ec4rs = "1.1"
|
||||
emojis = "0.6.1"
|
||||
env_logger = "0.11"
|
||||
@@ -478,7 +481,7 @@ json_dotpath = "1.1"
|
||||
jsonschema = "0.30.0"
|
||||
jsonwebtoken = "9.3"
|
||||
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||
libc = "0.2"
|
||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||
linkify = "0.10.0"
|
||||
@@ -489,7 +492,7 @@ metal = "0.29"
|
||||
moka = { version = "0.12.10", features = ["sync"] }
|
||||
naga = { version = "25.0", features = ["wgsl-in"] }
|
||||
nanoid = "0.4"
|
||||
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
objc = "0.2"
|
||||
@@ -529,7 +532,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c77
|
||||
"stream",
|
||||
] }
|
||||
rsa = "0.9.6"
|
||||
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
|
||||
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
@@ -538,7 +541,7 @@ rustc-hash = "2.1.0"
|
||||
rustls = { version = "0.23.26" }
|
||||
rustls-platform-verifier = "0.5.0"
|
||||
scap = { git = "https://github.com/zed-industries/scap", rev = "08f0a01417505cc0990b9931a37e5120db92e0d0", default-features = false }
|
||||
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
|
||||
schemars = { version = "1.0", features = ["indexmap2"] }
|
||||
semver = "1.0"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
@@ -595,7 +598,7 @@ tree-sitter-html = "0.23"
|
||||
tree-sitter-jsdoc = "0.23"
|
||||
tree-sitter-json = "0.24"
|
||||
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
|
||||
tree-sitter-python = "0.23"
|
||||
tree-sitter-python = { git = "https://github.com/zed-industries/tree-sitter-python", rev = "218fcbf3fda3d029225f3dec005cb497d111b35e" }
|
||||
tree-sitter-regex = "0.24"
|
||||
tree-sitter-ruby = "0.23"
|
||||
tree-sitter-rust = "0.24"
|
||||
@@ -623,7 +626,7 @@ wasmtime = { version = "29", default-features = false, features = [
|
||||
wasmtime-wasi = "29"
|
||||
which = "6.0.0"
|
||||
workspace-hack = "0.1.0"
|
||||
zed_llm_client = "0.8.4"
|
||||
zed_llm_client = "= 0.8.5"
|
||||
zstd = "0.11"
|
||||
|
||||
[workspace.dependencies.async-stripe]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.87-bookworm as builder
|
||||
FROM rust:1.88-bookworm as builder
|
||||
WORKDIR app
|
||||
COPY . .
|
||||
|
||||
|
||||
1
assets/icons/arrow_down10.svg
Normal file
@@ -0,0 +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-arrow-down10-icon lucide-arrow-down-1-0"><path d="m3 16 4 4 4-4"/><path d="M7 20V4"/><path d="M17 10V4h-2"/><path d="M15 10h4"/><rect x="15" y="14" width="4" height="6" ry="2"/></svg>
|
||||
|
After Width: | Height: | Size: 386 B |
3
assets/icons/bolt_filled_alt.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.75776 5.50003H8.49988C8.70769 5.50003 8.89518 5.62971 8.95455 5.82346C9.04049 6.01876 8.9858 6.23906 8.82956 6.37656L4.82971 9.87643C4.65315 10.0295 4.39488 10.042 4.20614 9.90455C4.01724 9.76705 3.94849 9.51706 4.04052 9.30301L5.24219 6.49999H3.48601C3.2918 6.49999 3.10524 6.37031 3.03197 6.17657C2.9587 5.98126 3.014 5.76096 3.1708 5.62346L7.17018 2.12375C7.34674 1.97001 7.60454 1.95829 7.7936 2.09547C7.98265 2.23275 8.0514 2.48218 7.95922 2.69695L6.75776 5.50003Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 601 B |
12
assets/icons/lsp_debug.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 3L7 4" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 4L10 3" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.002 6V5.51658C5.98992 5.32067 6.03266 5.12502 6.12762 4.94143C6.22259 4.75784 6.36781 4.59012 6.55453 4.44839C6.74125 4.30666 6.9656 4.19386 7.21403 4.1168C7.46246 4.03973 7.72983 4 8 4C8.27017 4 8.53754 4.03973 8.78597 4.1168C9.0344 4.19386 9.25875 4.30666 9.44547 4.44839C9.63219 4.59012 9.77741 4.75784 9.87238 4.94143C9.96734 5.12502 10.0101 5.32067 9.998 5.51658V6" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 13C6.35 13 5 11.5462 5 9.76923V8.15385C5 7.58261 5.21071 7.03477 5.58579 6.63085C5.96086 6.22692 6.46957 6 7 6H9C9.53043 6 10.0391 6.22692 10.4142 6.63085C10.7893 7.03477 11 7.58261 11 8.15385V9.76923C11 11.5462 9.65 13 8 13Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 6.16663C3.90652 6.06663 3 5.21663 3 4.16663" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 9H3" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 13C3 11.95 3.89474 11.05 5 11" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 4C13 5.05 12.0857 5.9 11 6" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 9H11" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 11C12.1053 11.05 13 11.95 13 13" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
4
assets/icons/lsp_restart.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.84265 10.7778C4.39206 11.6001 5.17295 12.241 6.08658 12.6194C7.00021 12.9978 8.00555 13.0969 8.97545 12.9039C9.94535 12.711 10.8363 12.2348 11.5355 11.5355C12.2348 10.8363 12.711 9.94535 12.9039 8.97545C13.0969 8.00555 12.9978 7.00021 12.6194 6.08658C12.241 5.17295 11.6001 4.39206 10.7778 3.84265C9.9556 3.29324 8.9889 3 8 3C6.60219 3.00526 5.26054 3.55068 4.25556 4.52222L3 5.77778" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 3V6H6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 685 B |
4
assets/icons/lsp_stop.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 5L11 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 409 B |
1
assets/icons/scroll_text.svg
Normal file
@@ -0,0 +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-scroll-text-icon lucide-scroll-text"><path d="M15 12h-5"/><path d="M15 8h-5"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"/></svg>
|
||||
|
After Width: | Height: | Size: 441 B |
1
assets/icons/split_alt.svg
Normal file
@@ -0,0 +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-split-icon lucide-split"><path d="M16 3h5v5"/><path d="M8 3H3v5"/><path d="M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3"/><path d="m15 9 6-6"/></svg>
|
||||
|
After Width: | Height: | Size: 345 B |
@@ -34,7 +34,7 @@
|
||||
"ctrl-q": "zed::Quit",
|
||||
"f4": "debugger::Start",
|
||||
"shift-f5": "debugger::Stop",
|
||||
"ctrl-shift-f5": "debugger::Restart",
|
||||
"ctrl-shift-f5": "debugger::RerunSession",
|
||||
"f6": "debugger::Pause",
|
||||
"f7": "debugger::StepOver",
|
||||
"ctrl-f11": "debugger::StepInto",
|
||||
@@ -244,6 +244,7 @@
|
||||
"ctrl-alt-e": "agent::RemoveAllContext",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-enter": "agent::ContinueThread",
|
||||
"super-ctrl-b": "agent::ToggleBurnMode",
|
||||
"alt-enter": "agent::ContinueWithBurnMode"
|
||||
}
|
||||
},
|
||||
@@ -490,13 +491,27 @@
|
||||
"ctrl-k r": "editor::RevealInFileManager",
|
||||
"ctrl-k p": "editor::CopyPath",
|
||||
"ctrl-\\": "pane::SplitRight",
|
||||
"ctrl-k v": "markdown::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "markdown::OpenPreview",
|
||||
"ctrl-alt-shift-c": "editor::DisplayCursorNames",
|
||||
"alt-.": "editor::GoToHunk",
|
||||
"alt-,": "editor::GoToPreviousHunk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == md",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-k v": "markdown::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "markdown::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == svg",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-k v": "svg::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "svg::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
@@ -542,6 +557,13 @@
|
||||
"ctrl-b": "workspace::ToggleLeftDock",
|
||||
"ctrl-j": "workspace::ToggleBottomDock",
|
||||
"ctrl-alt-y": "workspace::CloseAllDocks",
|
||||
"ctrl-alt-0": "workspace::ResetActiveDockSize",
|
||||
// For 0px parameter, uses UI font size value.
|
||||
"ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
|
||||
"ctrl-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }],
|
||||
"ctrl-alt-)": "workspace::ResetOpenDocksSize",
|
||||
"ctrl-alt-_": ["workspace::DecreaseOpenDocksSize", { "px": 0 }],
|
||||
"ctrl-alt-+": ["workspace::IncreaseOpenDocksSize", { "px": 0 }],
|
||||
"shift-find": "pane::DeploySearch",
|
||||
"ctrl-shift-f": "pane::DeploySearch",
|
||||
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
|
||||
@@ -583,7 +605,7 @@
|
||||
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
||||
// or by tag:
|
||||
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
||||
"f5": "debugger::RerunLastSession"
|
||||
"f5": "debugger::Rerun"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -686,6 +708,13 @@
|
||||
"pagedown": "editor::ContextMenuLast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"up": "editor::SignatureHelpPrevious",
|
||||
"down": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
// Custom bindings
|
||||
{
|
||||
"bindings": {
|
||||
@@ -904,7 +933,9 @@
|
||||
"context": "BreakpointList",
|
||||
"bindings": {
|
||||
"space": "debugger::ToggleEnableBreakpoint",
|
||||
"backspace": "debugger::UnsetBreakpoint"
|
||||
"backspace": "debugger::UnsetBreakpoint",
|
||||
"left": "debugger::PreviousBreakpointProperty",
|
||||
"right": "debugger::NextBreakpointProperty"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1050,5 +1081,19 @@
|
||||
"ctrl-tab": "pane::ActivateNextItem",
|
||||
"ctrl-shift-tab": "pane::ActivatePreviousItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "MarkdownPreview",
|
||||
"bindings": {
|
||||
"pageup": "markdown::MovePageUp",
|
||||
"pagedown": "markdown::MovePageDown"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "KeymapEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-f": "search::FocusSearch"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"bindings": {
|
||||
"f4": "debugger::Start",
|
||||
"shift-f5": "debugger::Stop",
|
||||
"shift-cmd-f5": "debugger::Restart",
|
||||
"shift-cmd-f5": "debugger::RerunSession",
|
||||
"f6": "debugger::Pause",
|
||||
"f7": "debugger::StepOver",
|
||||
"f11": "debugger::StepInto",
|
||||
@@ -283,6 +283,7 @@
|
||||
"cmd->": "assistant::QuoteSelection",
|
||||
"cmd-alt-e": "agent::RemoveAllContext",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-ctrl-b": "agent::ToggleBurnMode",
|
||||
"cmd-shift-enter": "agent::ContinueThread",
|
||||
"alt-enter": "agent::ContinueWithBurnMode"
|
||||
}
|
||||
@@ -544,11 +545,25 @@
|
||||
"cmd-k r": "editor::RevealInFileManager",
|
||||
"cmd-k p": "editor::CopyPath",
|
||||
"cmd-\\": "pane::SplitRight",
|
||||
"cmd-k v": "markdown::OpenPreviewToTheSide",
|
||||
"cmd-shift-v": "markdown::OpenPreview",
|
||||
"ctrl-cmd-c": "editor::DisplayCursorNames"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == md",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-k v": "markdown::OpenPreviewToTheSide",
|
||||
"cmd-shift-v": "markdown::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == svg",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-k v": "svg::OpenPreviewToTheSide",
|
||||
"cmd-shift-v": "svg::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"use_key_equivalents": true,
|
||||
@@ -587,6 +602,7 @@
|
||||
"alt-cmd-o": ["projects::OpenRecent", { "create_new_window": false }],
|
||||
"ctrl-cmd-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }],
|
||||
"ctrl-cmd-shift-o": ["projects::OpenRemote", { "from_existing_connection": true, "create_new_window": false }],
|
||||
"cmd-ctrl-b": "branches::OpenRecent",
|
||||
"ctrl-~": "workspace::NewTerminal",
|
||||
"cmd-s": "workspace::Save",
|
||||
"cmd-k s": "workspace::SaveWithoutFormat",
|
||||
@@ -604,9 +620,17 @@
|
||||
"cmd-8": ["workspace::ActivatePane", 7],
|
||||
"cmd-9": ["workspace::ActivatePane", 8],
|
||||
"cmd-b": "workspace::ToggleLeftDock",
|
||||
"cmd-alt-b": "workspace::ToggleRightDock",
|
||||
"cmd-r": "workspace::ToggleRightDock",
|
||||
"cmd-j": "workspace::ToggleBottomDock",
|
||||
"alt-cmd-y": "workspace::CloseAllDocks",
|
||||
// For 0px parameter, uses UI font size value.
|
||||
"ctrl-alt-0": "workspace::ResetActiveDockSize",
|
||||
"ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
|
||||
"ctrl-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }],
|
||||
"ctrl-alt-)": "workspace::ResetOpenDocksSize",
|
||||
"ctrl-alt-_": ["workspace::DecreaseOpenDocksSize", { "px": 0 }],
|
||||
"ctrl-alt-+": ["workspace::IncreaseOpenDocksSize", { "px": 0 }],
|
||||
"cmd-shift-f": "pane::DeploySearch",
|
||||
"cmd-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
|
||||
"cmd-shift-t": "pane::ReopenClosedItem",
|
||||
@@ -635,7 +659,7 @@
|
||||
"cmd-k shift-up": "workspace::SwapPaneUp",
|
||||
"cmd-k shift-down": "workspace::SwapPaneDown",
|
||||
"cmd-shift-x": "zed::Extensions",
|
||||
"f5": "debugger::RerunLastSession"
|
||||
"f5": "debugger::Rerun"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -749,6 +773,13 @@
|
||||
"pagedown": "editor::ContextMenuLast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"up": "editor::SignatureHelpPrevious",
|
||||
"down": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
// Custom bindings
|
||||
{
|
||||
"use_key_equivalents": true,
|
||||
@@ -963,7 +994,9 @@
|
||||
"context": "BreakpointList",
|
||||
"bindings": {
|
||||
"space": "debugger::ToggleEnableBreakpoint",
|
||||
"backspace": "debugger::UnsetBreakpoint"
|
||||
"backspace": "debugger::UnsetBreakpoint",
|
||||
"left": "debugger::PreviousBreakpointProperty",
|
||||
"right": "debugger::NextBreakpointProperty"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1148,5 +1181,19 @@
|
||||
"ctrl-tab": "pane::ActivateNextItem",
|
||||
"ctrl-shift-tab": "pane::ActivatePreviousItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "MarkdownPreview",
|
||||
"bindings": {
|
||||
"pageup": "markdown::MovePageUp",
|
||||
"pagedown": "markdown::MovePageDown"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "KeymapEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-f": "search::FocusSearch"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
"alt->": "editor::MoveToEnd", // end-of-buffer
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
|
||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||
"alt-^": "editor::JoinLines" // join-line
|
||||
"alt-^": "editor::JoinLines", // join-line
|
||||
"alt-q": "editor::Rewrap" // fill-paragraph
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -97,6 +98,13 @@
|
||||
"ctrl-n": "editor::ContextMenuNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
"alt->": "editor::MoveToEnd", // end-of-buffer
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
|
||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||
"alt-^": "editor::JoinLines" // join-line
|
||||
"alt-^": "editor::JoinLines", // join-line
|
||||
"alt-q": "editor::Rewrap" // fill-paragraph
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -97,6 +98,13 @@
|
||||
"ctrl-n": "editor::ContextMenuNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
||||
@@ -210,7 +210,8 @@
|
||||
"ctrl-w space": "editor::OpenExcerptsSplit",
|
||||
"ctrl-w g space": "editor::OpenExcerptsSplit",
|
||||
"ctrl-6": "pane::AlternateFile",
|
||||
"ctrl-^": "pane::AlternateFile"
|
||||
"ctrl-^": "pane::AlternateFile",
|
||||
".": "vim::Repeat"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -219,7 +220,6 @@
|
||||
"ctrl-[": "editor::Cancel",
|
||||
"escape": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
".": "vim::Repeat",
|
||||
"c": "vim::PushChange",
|
||||
"shift-c": "vim::ChangeToEndOfLine",
|
||||
"d": "vim::PushDelete",
|
||||
@@ -477,6 +477,13 @@
|
||||
"ctrl-n": "editor::ShowWordCompletions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == replace",
|
||||
"bindings": {
|
||||
@@ -849,6 +856,25 @@
|
||||
"shift-u": "git::UnstageAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == auto_height && VimControl",
|
||||
"bindings": {
|
||||
// TODO: Implement search
|
||||
"/": null,
|
||||
"?": null,
|
||||
"#": null,
|
||||
"*": null,
|
||||
"n": null,
|
||||
"shift-n": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitCommit > Editor && VimControl && vim_mode == normal",
|
||||
"bindings": {
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"escape": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && edit_prediction",
|
||||
"bindings": {
|
||||
@@ -860,14 +886,7 @@
|
||||
{
|
||||
"context": "MessageEditor > Editor && VimControl",
|
||||
"bindings": {
|
||||
"enter": "agent::Chat",
|
||||
// TODO: Implement search
|
||||
"/": null,
|
||||
"?": null,
|
||||
"#": null,
|
||||
"*": null,
|
||||
"n": null,
|
||||
"shift-n": null
|
||||
"enter": "agent::Chat"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1784,7 +1784,8 @@
|
||||
// `socks5h`. `http` will be used when no scheme is specified.
|
||||
//
|
||||
// By default no proxy will be used, or Zed will try get proxy settings from
|
||||
// environment variables.
|
||||
// environment variables. If certain hosts should not be proxied,
|
||||
// set the `no_proxy` environment variable and provide a comma-separated list.
|
||||
//
|
||||
// Examples:
|
||||
// - "proxy": "socks5h://localhost:10808"
|
||||
|
||||
@@ -72,6 +72,7 @@ gpui = { workspace = true, "features" = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, "features" = ["test-support"] }
|
||||
language_model = { workspace = true, "features" = ["test-support"] }
|
||||
parking_lot.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -96,16 +96,11 @@ impl AgentProfile {
|
||||
fn is_enabled(settings: &AgentProfileSettings, source: ToolSource, name: String) -> bool {
|
||||
match source {
|
||||
ToolSource::Native => *settings.tools.get(name.as_str()).unwrap_or(&false),
|
||||
ToolSource::ContextServer { id } => {
|
||||
if settings.enable_all_context_servers {
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(preset) = settings.context_servers.get(id.as_ref()) else {
|
||||
return false;
|
||||
};
|
||||
*preset.tools.get(name.as_str()).unwrap_or(&false)
|
||||
}
|
||||
ToolSource::ContextServer { id } => settings
|
||||
.context_servers
|
||||
.get(id.as_ref())
|
||||
.and_then(|preset| preset.tools.get(name.as_str()).copied())
|
||||
.unwrap_or(settings.enable_all_context_servers),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ use anyhow::{Result, bail};
|
||||
use collections::IndexMap;
|
||||
use gpui::{App, Pixels, SharedString};
|
||||
use language_model::LanguageModel;
|
||||
use schemars::{JsonSchema, schema::Schema};
|
||||
use schemars::{JsonSchema, json_schema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub use crate::agent_profile::*;
|
||||
|
||||
@@ -49,7 +50,7 @@ pub struct AgentSettings {
|
||||
pub dock: AgentDockPosition,
|
||||
pub default_width: Pixels,
|
||||
pub default_height: Pixels,
|
||||
pub default_model: LanguageModelSelection,
|
||||
pub default_model: Option<LanguageModelSelection>,
|
||||
pub inline_assistant_model: Option<LanguageModelSelection>,
|
||||
pub commit_message_model: Option<LanguageModelSelection>,
|
||||
pub thread_summary_model: Option<LanguageModelSelection>,
|
||||
@@ -211,7 +212,6 @@ impl AgentSettingsContent {
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct AgentSettingsContent {
|
||||
/// Whether the Agent is enabled.
|
||||
///
|
||||
@@ -321,29 +321,27 @@ pub struct LanguageModelSelection {
|
||||
pub struct LanguageModelProviderSetting(pub String);
|
||||
|
||||
impl JsonSchema for LanguageModelProviderSetting {
|
||||
fn schema_name() -> String {
|
||||
fn schema_name() -> Cow<'static, str> {
|
||||
"LanguageModelProviderSetting".into()
|
||||
}
|
||||
|
||||
fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> Schema {
|
||||
schemars::schema::SchemaObject {
|
||||
enum_values: Some(vec![
|
||||
"anthropic".into(),
|
||||
"amazon-bedrock".into(),
|
||||
"google".into(),
|
||||
"lmstudio".into(),
|
||||
"ollama".into(),
|
||||
"openai".into(),
|
||||
"zed.dev".into(),
|
||||
"copilot_chat".into(),
|
||||
"deepseek".into(),
|
||||
"openrouter".into(),
|
||||
"mistral".into(),
|
||||
"vercel".into(),
|
||||
]),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
json_schema!({
|
||||
"enum": [
|
||||
"anthropic",
|
||||
"amazon-bedrock",
|
||||
"google",
|
||||
"lmstudio",
|
||||
"ollama",
|
||||
"openai",
|
||||
"zed.dev",
|
||||
"copilot_chat",
|
||||
"deepseek",
|
||||
"openrouter",
|
||||
"mistral",
|
||||
"vercel"
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,15 +357,6 @@ impl From<&str> for LanguageModelProviderSetting {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LanguageModelSelection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
provider: LanguageModelProviderSetting("openai".to_string()),
|
||||
model: "gpt-4".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct AgentProfileContent {
|
||||
pub name: Arc<str>,
|
||||
@@ -411,7 +400,10 @@ impl Settings for AgentSettings {
|
||||
&mut settings.default_height,
|
||||
value.default_height.map(Into::into),
|
||||
);
|
||||
merge(&mut settings.default_model, value.default_model.clone());
|
||||
settings.default_model = value
|
||||
.default_model
|
||||
.clone()
|
||||
.or(settings.default_model.take());
|
||||
settings.inline_assistant_model = value
|
||||
.inline_assistant_model
|
||||
.clone()
|
||||
|
||||
@@ -19,7 +19,7 @@ use audio::{Audio, Sound};
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::actions::{MoveUp, Paste};
|
||||
use editor::scroll::Autoscroll;
|
||||
use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer};
|
||||
use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, SelectionEffects};
|
||||
use gpui::{
|
||||
AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry,
|
||||
ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla,
|
||||
@@ -47,8 +47,8 @@ use std::time::Duration;
|
||||
use text::ToPoint;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize, Tooltip,
|
||||
prelude::*,
|
||||
Banner, Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize,
|
||||
Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use util::markdown::MarkdownCodeBlock;
|
||||
@@ -58,6 +58,7 @@ use zed_llm_client::CompletionIntent;
|
||||
|
||||
const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";
|
||||
const EDIT_PREVIOUS_MESSAGE_MIN_LINES: usize = 1;
|
||||
const RESPONSE_PADDING_X: Pixels = px(19.);
|
||||
|
||||
pub struct ActiveThread {
|
||||
context_store: Entity<ContextStore>,
|
||||
@@ -204,7 +205,7 @@ pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
selection_background_color: cx.theme().colors().element_selection_background,
|
||||
code_block_overflow_x_scroll: true,
|
||||
table_overflow_x_scroll: true,
|
||||
heading_level_styles: Some(HeadingLevelStyles {
|
||||
@@ -301,7 +302,7 @@ fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
selection_background_color: cx.theme().colors().element_selection_background,
|
||||
code_block_overflow_x_scroll: false,
|
||||
code_block: StyleRefinement {
|
||||
margin: EdgesRefinement::default(),
|
||||
@@ -689,9 +690,12 @@ fn open_markdown_link(
|
||||
})
|
||||
.context("Could not find matching symbol")?;
|
||||
|
||||
editor.change_selections(Some(Autoscroll::center()), window, cx, |s| {
|
||||
s.select_anchor_ranges([symbol_range.start..symbol_range.start])
|
||||
});
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::center()),
|
||||
window,
|
||||
cx,
|
||||
|s| s.select_anchor_ranges([symbol_range.start..symbol_range.start]),
|
||||
);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
})
|
||||
@@ -708,10 +712,15 @@ fn open_markdown_link(
|
||||
.downcast::<Editor>()
|
||||
.context("Item is not an editor")?;
|
||||
active_editor.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(Some(Autoscroll::center()), window, cx, |s| {
|
||||
s.select_ranges([Point::new(line_range.start as u32, 0)
|
||||
..Point::new(line_range.start as u32, 0)])
|
||||
});
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::center()),
|
||||
window,
|
||||
cx,
|
||||
|s| {
|
||||
s.select_ranges([Point::new(line_range.start as u32, 0)
|
||||
..Point::new(line_range.start as u32, 0)])
|
||||
},
|
||||
);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
})
|
||||
@@ -1140,6 +1149,9 @@ impl ActiveThread {
|
||||
self.save_thread(cx);
|
||||
cx.notify();
|
||||
}
|
||||
ThreadEvent::RetriesFailed { message } => {
|
||||
self.show_notification(message, ui::IconName::Warning, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1835,9 +1847,10 @@ impl ActiveThread {
|
||||
.filter(|(id, _)| *id == message_id)
|
||||
.map(|(_, state)| state);
|
||||
|
||||
let colors = cx.theme().colors();
|
||||
let editor_bg_color = colors.editor_background;
|
||||
let panel_bg = colors.panel_background;
|
||||
let (editor_bg_color, panel_bg) = {
|
||||
let colors = cx.theme().colors();
|
||||
(colors.editor_background, colors.panel_background)
|
||||
};
|
||||
|
||||
let open_as_markdown = IconButton::new(("open-as-markdown", ix), IconName::DocumentText)
|
||||
.icon_size(IconSize::XSmall)
|
||||
@@ -1862,9 +1875,6 @@ impl ActiveThread {
|
||||
this.scroll_to_top(cx);
|
||||
}));
|
||||
|
||||
// For all items that should be aligned with the LLM's response.
|
||||
const RESPONSE_PADDING_X: Pixels = px(19.);
|
||||
|
||||
let show_feedback = thread.is_turn_end(ix);
|
||||
let feedback_container = h_flex()
|
||||
.group("feedback_container")
|
||||
@@ -2025,152 +2035,162 @@ impl ActiveThread {
|
||||
}
|
||||
});
|
||||
|
||||
let styled_message = match message.role {
|
||||
Role::User => v_flex()
|
||||
.id(("message-container", ix))
|
||||
.pt_2()
|
||||
.pl_2()
|
||||
.pr_2p5()
|
||||
.pb_4()
|
||||
.child(
|
||||
let styled_message = if message.ui_only {
|
||||
self.render_ui_notification(message_content, ix, cx)
|
||||
} else {
|
||||
match message.role {
|
||||
Role::User => {
|
||||
let colors = cx.theme().colors();
|
||||
v_flex()
|
||||
.id(("user-message", ix))
|
||||
.bg(editor_bg_color)
|
||||
.rounded_lg()
|
||||
.shadow_md()
|
||||
.border_1()
|
||||
.border_color(colors.border)
|
||||
.hover(|hover| hover.border_color(colors.text_accent.opacity(0.5)))
|
||||
.id(("message-container", ix))
|
||||
.pt_2()
|
||||
.pl_2()
|
||||
.pr_2p5()
|
||||
.pb_4()
|
||||
.child(
|
||||
v_flex()
|
||||
.p_2p5()
|
||||
.gap_1()
|
||||
.children(message_content)
|
||||
.when_some(editing_message_state, |this, state| {
|
||||
let focus_handle = state.editor.focus_handle(cx).clone();
|
||||
.id(("user-message", ix))
|
||||
.bg(editor_bg_color)
|
||||
.rounded_lg()
|
||||
.shadow_md()
|
||||
.border_1()
|
||||
.border_color(colors.border)
|
||||
.hover(|hover| hover.border_color(colors.text_accent.opacity(0.5)))
|
||||
.child(
|
||||
v_flex()
|
||||
.p_2p5()
|
||||
.gap_1()
|
||||
.children(message_content)
|
||||
.when_some(editing_message_state, |this, state| {
|
||||
let focus_handle = state.editor.focus_handle(cx).clone();
|
||||
|
||||
this.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.flex_wrap()
|
||||
.child(
|
||||
this.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.flex_wrap()
|
||||
.child(
|
||||
div()
|
||||
.opacity(0.8)
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::Indicator)
|
||||
.color(Color::Warning)
|
||||
div()
|
||||
.opacity(0.8)
|
||||
.child(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::Indicator)
|
||||
.color(Color::Warning)
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Label::new("Editing will restart the thread from this point.")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Label::new("Editing will restart the thread from this point.")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
IconButton::new(
|
||||
"cancel-edit-message",
|
||||
IconName::Close,
|
||||
)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Error)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Cancel Edit",
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
IconButton::new(
|
||||
"cancel-edit-message",
|
||||
IconName::Close,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(Self::handle_cancel_click)),
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Error)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Cancel Edit",
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(Self::handle_cancel_click)),
|
||||
)
|
||||
.child(
|
||||
IconButton::new(
|
||||
"confirm-edit-message",
|
||||
IconName::Return,
|
||||
)
|
||||
.disabled(state.editor.read(cx).is_empty(cx))
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Regenerate",
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(Self::handle_regenerate_click),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
IconButton::new(
|
||||
"confirm-edit-message",
|
||||
IconName::Return,
|
||||
)
|
||||
.disabled(state.editor.read(cx).is_empty(cx))
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Regenerate",
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(Self::handle_regenerate_click),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.on_click(cx.listener({
|
||||
let message_creases = message.creases.clone();
|
||||
move |this, _, window, cx| {
|
||||
if let Some(message_text) =
|
||||
this.thread.read(cx).message(message_id).and_then(|message| {
|
||||
message.segments.first().and_then(|segment| {
|
||||
match segment {
|
||||
MessageSegment::Text(message_text) => {
|
||||
Some(Into::<Arc<str>>::into(message_text.as_str()))
|
||||
}
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
{
|
||||
this.start_editing_message(
|
||||
message_id,
|
||||
message_text,
|
||||
&message_creases,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
})),
|
||||
)
|
||||
.on_click(cx.listener({
|
||||
let message_creases = message.creases.clone();
|
||||
move |this, _, window, cx| {
|
||||
if let Some(message_text) =
|
||||
this.thread.read(cx).message(message_id).and_then(|message| {
|
||||
message.segments.first().and_then(|segment| {
|
||||
match segment {
|
||||
MessageSegment::Text(message_text) => {
|
||||
Some(Into::<Arc<str>>::into(message_text.as_str()))
|
||||
}
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
{
|
||||
this.start_editing_message(
|
||||
message_id,
|
||||
message_text,
|
||||
&message_creases,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
})),
|
||||
),
|
||||
Role::Assistant => v_flex()
|
||||
.id(("message-container", ix))
|
||||
.px(RESPONSE_PADDING_X)
|
||||
.gap_2()
|
||||
.children(message_content)
|
||||
.when(has_tool_uses, |parent| {
|
||||
parent.children(tool_uses.into_iter().map(|tool_use| {
|
||||
self.render_tool_use(tool_use, window, workspace.clone(), cx)
|
||||
}))
|
||||
}),
|
||||
Role::System => div().id(("message-container", ix)).py_1().px_2().child(
|
||||
v_flex()
|
||||
.bg(colors.editor_background)
|
||||
.rounded_sm()
|
||||
.child(div().p_4().children(message_content)),
|
||||
),
|
||||
}
|
||||
Role::Assistant => v_flex()
|
||||
.id(("message-container", ix))
|
||||
.px(RESPONSE_PADDING_X)
|
||||
.gap_2()
|
||||
.children(message_content)
|
||||
.when(has_tool_uses, |parent| {
|
||||
parent.children(tool_uses.into_iter().map(|tool_use| {
|
||||
self.render_tool_use(tool_use, window, workspace.clone(), cx)
|
||||
}))
|
||||
}),
|
||||
Role::System => {
|
||||
let colors = cx.theme().colors();
|
||||
div().id(("message-container", ix)).py_1().px_2().child(
|
||||
v_flex()
|
||||
.bg(colors.editor_background)
|
||||
.rounded_sm()
|
||||
.child(div().p_4().children(message_content)),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let after_editing_message = self
|
||||
@@ -2509,6 +2529,26 @@ impl ActiveThread {
|
||||
.blend(cx.theme().colors().editor_foreground.opacity(0.025))
|
||||
}
|
||||
|
||||
fn render_ui_notification(
|
||||
&self,
|
||||
message_content: impl IntoIterator<Item = impl IntoElement>,
|
||||
ix: usize,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Stateful<Div> {
|
||||
let message = div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.text_size(TextSize::XSmall.rems(cx))
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.children(message_content);
|
||||
|
||||
div()
|
||||
.id(("message-container", ix))
|
||||
.py_1()
|
||||
.px_2p5()
|
||||
.child(Banner::new().severity(ui::Severity::Warning).child(message))
|
||||
}
|
||||
|
||||
fn render_message_thinking_segment(
|
||||
&self,
|
||||
message_id: MessageId,
|
||||
@@ -3763,9 +3803,9 @@ mod tests {
|
||||
|
||||
// Stream response to user message
|
||||
thread.update(cx, |thread, cx| {
|
||||
let request =
|
||||
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx);
|
||||
thread.stream_completion(request, model, cx.active_window(), cx)
|
||||
let intent = CompletionIntent::UserPrompt;
|
||||
let request = thread.to_completion_request(model.clone(), intent, cx);
|
||||
thread.stream_completion(request, model, intent, cx.active_window(), cx)
|
||||
});
|
||||
// Follow the agent
|
||||
cx.update(|window, cx| {
|
||||
|
||||
@@ -16,7 +16,9 @@ use gpui::{
|
||||
Focusable, ScrollHandle, Subscription, Task, Transformation, WeakEntity, percentage,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||
use language_model::{
|
||||
LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,
|
||||
};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use project::{
|
||||
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
|
||||
@@ -86,6 +88,14 @@ impl AgentConfiguration {
|
||||
let scroll_handle = ScrollHandle::new();
|
||||
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
|
||||
|
||||
let mut expanded_provider_configurations = HashMap::default();
|
||||
if LanguageModelRegistry::read_global(cx)
|
||||
.provider(&ZED_CLOUD_PROVIDER_ID)
|
||||
.map_or(false, |cloud_provider| cloud_provider.must_accept_terms(cx))
|
||||
{
|
||||
expanded_provider_configurations.insert(ZED_CLOUD_PROVIDER_ID, true);
|
||||
}
|
||||
|
||||
let mut this = Self {
|
||||
fs,
|
||||
language_registry,
|
||||
@@ -94,7 +104,7 @@ impl AgentConfiguration {
|
||||
configuration_views_by_provider: HashMap::default(),
|
||||
context_server_store,
|
||||
expanded_context_server_tools: HashMap::default(),
|
||||
expanded_provider_configurations: HashMap::default(),
|
||||
expanded_provider_configurations,
|
||||
tools,
|
||||
_registry_subscription: registry_subscription,
|
||||
scroll_handle,
|
||||
|
||||
@@ -180,7 +180,7 @@ impl ConfigurationSource {
|
||||
}
|
||||
|
||||
fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)>) -> String {
|
||||
let (name, path, args, env) = match existing {
|
||||
let (name, command, args, env) = match existing {
|
||||
Some((id, cmd)) => {
|
||||
let args = serde_json::to_string(&cmd.args).unwrap();
|
||||
let env = serde_json::to_string(&cmd.env.unwrap_or_default()).unwrap();
|
||||
@@ -198,14 +198,12 @@ fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)
|
||||
r#"{{
|
||||
/// The name of your MCP server
|
||||
"{name}": {{
|
||||
"command": {{
|
||||
/// The path to the executable
|
||||
"path": "{path}",
|
||||
/// The arguments to pass to the executable
|
||||
"args": {args},
|
||||
/// The environment variables to set for the executable
|
||||
"env": {env}
|
||||
}}
|
||||
/// The command which runs the MCP server
|
||||
"command": "{command}",
|
||||
/// The arguments to pass to the MCP server
|
||||
"args": {args},
|
||||
/// The environment variables to set
|
||||
"env": {env}
|
||||
}}
|
||||
}}"#
|
||||
)
|
||||
@@ -439,8 +437,7 @@ fn parse_input(text: &str) -> Result<(ContextServerId, ContextServerCommand)> {
|
||||
let object = value.as_object().context("Expected object")?;
|
||||
anyhow::ensure!(object.len() == 1, "Expected exactly one key-value pair");
|
||||
let (context_server_name, value) = object.into_iter().next().unwrap();
|
||||
let command = value.get("command").context("Expected command")?;
|
||||
let command: ContextServerCommand = serde_json::from_value(command.clone())?;
|
||||
let command: ContextServerCommand = serde_json::from_value(value.clone())?;
|
||||
Ok((ContextServerId(context_server_name.clone().into()), command))
|
||||
}
|
||||
|
||||
@@ -748,7 +745,7 @@ pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
selection_background_color: colors.element_selection_background,
|
||||
link: TextStyleRefinement {
|
||||
background_color: Some(colors.editor_foreground.opacity(0.025)),
|
||||
underline: Some(UnderlineStyle {
|
||||
|
||||
@@ -5,7 +5,8 @@ use anyhow::Result;
|
||||
use buffer_diff::DiffHunkStatus;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot, ToPoint,
|
||||
Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot,
|
||||
SelectionEffects, ToPoint,
|
||||
actions::{GoToHunk, GoToPreviousHunk},
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
@@ -171,15 +172,9 @@ impl AgentDiffPane {
|
||||
|
||||
if let Some(first_hunk) = first_hunk {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
editor.change_selections(
|
||||
Some(Autoscroll::fit()),
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
selections
|
||||
.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
},
|
||||
)
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +237,7 @@ impl AgentDiffPane {
|
||||
|
||||
if let Some(first_hunk) = first_hunk {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
})
|
||||
}
|
||||
@@ -416,7 +411,7 @@ fn update_editor_selection(
|
||||
};
|
||||
|
||||
if let Some(target_hunk) = target_hunk {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
let next_hunk_start = target_hunk.multi_buffer_range().start;
|
||||
selections.select_anchor_ranges([next_hunk_start..next_hunk_start]);
|
||||
})
|
||||
@@ -1380,6 +1375,7 @@ impl AgentDiff {
|
||||
| ThreadEvent::ToolConfirmationNeeded
|
||||
| ThreadEvent::ToolUseLimitReached
|
||||
| ThreadEvent::CancelEditing
|
||||
| ThreadEvent::RetriesFailed { .. }
|
||||
| ThreadEvent::ProfileChanged => {}
|
||||
}
|
||||
}
|
||||
@@ -1543,7 +1539,7 @@ impl AgentDiff {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
|
||||
editor.change_selections(
|
||||
Some(Autoscroll::center()),
|
||||
SelectionEffects::scroll(Autoscroll::center()),
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
@@ -1867,7 +1863,7 @@ mod tests {
|
||||
|
||||
// Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end.
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
|
||||
});
|
||||
});
|
||||
@@ -2123,7 +2119,7 @@ mod tests {
|
||||
|
||||
// Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end.
|
||||
editor1.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ use language_model::{ConfiguredModel, LanguageModelRegistry};
|
||||
use picker::popover_menu::PickerPopoverMenu;
|
||||
use settings::update_settings_file;
|
||||
use std::sync::Arc;
|
||||
use ui::{PopoverMenuHandle, Tooltip, prelude::*};
|
||||
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
|
||||
|
||||
pub struct AgentModelSelector {
|
||||
selector: Entity<LanguageModelSelector>,
|
||||
@@ -94,20 +94,35 @@ impl Render for AgentModelSelector {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let model = self.selector.read(cx).delegate.active_model(cx);
|
||||
let model_name = model
|
||||
.as_ref()
|
||||
.map(|model| model.model.name().0)
|
||||
.unwrap_or_else(|| SharedString::from("No model selected"));
|
||||
let provider_icon = model
|
||||
.as_ref()
|
||||
.map(|model| model.provider.icon())
|
||||
.unwrap_or_else(|| IconName::Ai);
|
||||
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
PickerPopoverMenu::new(
|
||||
self.selector.clone(),
|
||||
Button::new("active-model", model_name)
|
||||
.label_size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.icon(IconName::ChevronDown)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon_color(Color::Muted),
|
||||
ButtonLike::new("active-model")
|
||||
.child(
|
||||
Icon::new(provider_icon)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.ml_0p5(),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
|
||||
@@ -4,6 +4,7 @@ mod agent_diff;
|
||||
mod agent_model_selector;
|
||||
mod agent_panel;
|
||||
mod buffer_codegen;
|
||||
mod burn_mode_tooltip;
|
||||
mod context_picker;
|
||||
mod context_server_configuration;
|
||||
mod context_strip;
|
||||
@@ -11,7 +12,6 @@ mod debug;
|
||||
mod inline_assistant;
|
||||
mod inline_prompt_editor;
|
||||
mod language_model_selector;
|
||||
mod max_mode_tooltip;
|
||||
mod message_editor;
|
||||
mod profile_selector;
|
||||
mod slash_command;
|
||||
@@ -48,7 +48,7 @@ pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
use crate::slash_command_settings::SlashCommandSettings;
|
||||
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
||||
pub use text_thread_editor::AgentPanelDelegate;
|
||||
pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
|
||||
pub use ui::preview::{all_agent_previews, get_agent_preview};
|
||||
|
||||
actions!(
|
||||
@@ -92,6 +92,7 @@ actions!(
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = agent)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NewThread {
|
||||
#[serde(default)]
|
||||
from_thread_id: Option<ThreadId>,
|
||||
@@ -99,6 +100,7 @@ pub struct NewThread {
|
||||
|
||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = agent)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ManageProfiles {
|
||||
#[serde(default)]
|
||||
pub customize_tools: Option<AgentProfileId>,
|
||||
@@ -157,6 +159,7 @@ pub fn init(
|
||||
agent::init(cx);
|
||||
agent_panel::init(cx);
|
||||
context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
|
||||
TextThreadEditor::init(cx);
|
||||
|
||||
register_slash_commands(cx);
|
||||
inline_assistant::init(
|
||||
@@ -208,7 +211,7 @@ fn update_active_language_model_from_settings(cx: &mut App) {
|
||||
}
|
||||
}
|
||||
|
||||
let default = to_selected_model(&settings.default_model);
|
||||
let default = settings.default_model.as_ref().map(to_selected_model);
|
||||
let inline_assistant = settings
|
||||
.inline_assistant_model
|
||||
.as_ref()
|
||||
@@ -228,7 +231,7 @@ fn update_active_language_model_from_settings(cx: &mut App) {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry.select_default_model(Some(&default), cx);
|
||||
registry.select_default_model(default.as_ref(), cx);
|
||||
registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
|
||||
registry.select_commit_message_model(commit_message.as_ref(), cx);
|
||||
registry.select_thread_summary_model(thread_summary.as_ref(), cx);
|
||||
|
||||
@@ -1094,15 +1094,9 @@ mod tests {
|
||||
};
|
||||
use language_model::{LanguageModelRegistry, TokenUsage};
|
||||
use rand::prelude::*;
|
||||
use serde::Serialize;
|
||||
use settings::SettingsStore;
|
||||
use std::{future, sync::Arc};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DummyCompletionRequest {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use gpui::{Context, FontWeight, IntoElement, Render, Window};
|
||||
use ui::{prelude::*, tooltip_container};
|
||||
|
||||
pub struct MaxModeTooltip {
|
||||
pub struct BurnModeTooltip {
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
impl MaxModeTooltip {
|
||||
impl BurnModeTooltip {
|
||||
pub fn new() -> Self {
|
||||
Self { selected: false }
|
||||
}
|
||||
@@ -16,7 +16,7 @@ impl MaxModeTooltip {
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for MaxModeTooltip {
|
||||
impl Render for BurnModeTooltip {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (icon, color) = if self.selected {
|
||||
(IconName::ZedBurnModeOn, Color::Error)
|
||||
@@ -661,7 +661,7 @@ fn recent_context_picker_entries(
|
||||
|
||||
let active_thread_id = workspace
|
||||
.panel::<AgentPanel>(cx)
|
||||
.and_then(|panel| Some(panel.read(cx).active_thread()?.read(cx).id()));
|
||||
.and_then(|panel| Some(panel.read(cx).active_thread(cx)?.read(cx).id()));
|
||||
|
||||
if let Some((thread_store, text_thread_store)) = thread_store
|
||||
.and_then(|store| store.upgrade())
|
||||
@@ -930,8 +930,8 @@ impl MentionLink {
|
||||
format!(
|
||||
"[@{} ({}-{})]({}:{}:{}-{})",
|
||||
file_name,
|
||||
line_range.start,
|
||||
line_range.end,
|
||||
line_range.start + 1,
|
||||
line_range.end + 1,
|
||||
Self::SELECTION,
|
||||
full_path,
|
||||
line_range.start,
|
||||
|
||||
@@ -161,7 +161,7 @@ impl ContextStrip {
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let panel = workspace.read(cx).panel::<AgentPanel>(cx)?.read(cx);
|
||||
|
||||
if let Some(active_thread) = panel.active_thread() {
|
||||
if let Some(active_thread) = panel.active_thread(cx) {
|
||||
let weak_active_thread = active_thread.downgrade();
|
||||
|
||||
let active_thread = active_thread.read(cx);
|
||||
|
||||
@@ -18,6 +18,7 @@ use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
||||
use editor::SelectionEffects;
|
||||
use editor::{
|
||||
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,
|
||||
MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
|
||||
@@ -1159,7 +1160,7 @@ impl InlineAssistant {
|
||||
|
||||
let position = assist.range.start;
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([position..position])
|
||||
});
|
||||
|
||||
|
||||
@@ -399,7 +399,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
let all_models = self.all_models.clone();
|
||||
let current_index = self.selected_index;
|
||||
let active_model = (self.get_active_model)(cx);
|
||||
let bg_executor = cx.background_executor();
|
||||
|
||||
let language_model_registry = LanguageModelRegistry::global(cx);
|
||||
@@ -441,12 +441,9 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.delegate.filtered_entries = filtered_models.entries();
|
||||
// Preserve selection focus
|
||||
let new_index = if current_index >= this.delegate.filtered_entries.len() {
|
||||
0
|
||||
} else {
|
||||
current_index
|
||||
};
|
||||
// Finds the currently selected model in the list
|
||||
let new_index =
|
||||
Self::get_active_model_index(&this.delegate.filtered_entries, active_model);
|
||||
this.set_selected_index(new_index, Some(picker::Direction::Down), true, window, cx);
|
||||
cx.notify();
|
||||
})
|
||||
|
||||
@@ -575,7 +575,7 @@ impl MessageEditor {
|
||||
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let thread = self.thread.read(cx);
|
||||
let model = thread.configured_model();
|
||||
if !model?.model.supports_max_mode() {
|
||||
if !model?.model.supports_burn_mode() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1250,9 +1250,7 @@ impl MessageEditor {
|
||||
self.thread
|
||||
.read(cx)
|
||||
.configured_model()
|
||||
.map_or(false, |model| {
|
||||
model.provider.id().0 == ZED_CLOUD_PROVIDER_ID
|
||||
})
|
||||
.map_or(false, |model| model.provider.id() == ZED_CLOUD_PROVIDER_ID)
|
||||
}
|
||||
|
||||
fn render_usage_callout(&self, line_height: Pixels, cx: &mut Context<Self>) -> Option<Div> {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
burn_mode_tooltip::BurnModeTooltip,
|
||||
language_model_selector::{
|
||||
LanguageModelSelector, ToggleModelSelector, language_model_selector,
|
||||
},
|
||||
max_mode_tooltip::MaxModeTooltip,
|
||||
};
|
||||
use agent_settings::{AgentSettings, CompletionMode};
|
||||
use anyhow::Result;
|
||||
@@ -21,7 +21,6 @@ use editor::{
|
||||
BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId,
|
||||
RenderBlock, ToDisplayPoint,
|
||||
},
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
use editor::{FoldPlaceholder, display_map::CreaseId};
|
||||
use fs::Fs;
|
||||
@@ -69,7 +68,7 @@ use workspace::{
|
||||
searchable::{Direction, SearchableItemHandle},
|
||||
};
|
||||
use workspace::{
|
||||
Save, Toast, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
Save, Toast, Workspace,
|
||||
item::{self, FollowableItem, Item, ItemHandle},
|
||||
notifications::NotificationId,
|
||||
pane,
|
||||
@@ -389,7 +388,7 @@ impl TextThreadEditor {
|
||||
cursor..cursor
|
||||
};
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_ranges([new_selection])
|
||||
});
|
||||
});
|
||||
@@ -449,8 +448,7 @@ impl TextThreadEditor {
|
||||
if let Some(command) = self.slash_commands.command(name, cx) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor
|
||||
.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel());
|
||||
editor.change_selections(Default::default(), window, cx, |s| s.try_cancel());
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let newest_cursor = editor.selections.newest::<Point>(cx).head();
|
||||
if newest_cursor.column > 0
|
||||
@@ -1583,7 +1581,7 @@ impl TextThreadEditor {
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.transact(window, cx, |this, window, cx| {
|
||||
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
s.select(selections);
|
||||
});
|
||||
this.insert("", window, cx);
|
||||
@@ -2075,12 +2073,12 @@ impl TextThreadEditor {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_max_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let context = self.context().read(cx);
|
||||
let active_model = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()
|
||||
.map(|default| default.model)?;
|
||||
if !active_model.supports_max_mode() {
|
||||
if !active_model.supports_burn_mode() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -2107,7 +2105,7 @@ impl TextThreadEditor {
|
||||
});
|
||||
}))
|
||||
.tooltip(move |_window, cx| {
|
||||
cx.new(|_| MaxModeTooltip::new().selected(burn_mode_enabled))
|
||||
cx.new(|_| BurnModeTooltip::new().selected(burn_mode_enabled))
|
||||
.into()
|
||||
})
|
||||
.into_any_element(),
|
||||
@@ -2122,12 +2120,21 @@ impl TextThreadEditor {
|
||||
let active_model = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()
|
||||
.map(|default| default.model);
|
||||
let focus_handle = self.editor().focus_handle(cx).clone();
|
||||
let model_name = match active_model {
|
||||
Some(model) => model.name().0,
|
||||
None => SharedString::from("No model selected"),
|
||||
};
|
||||
|
||||
let active_provider = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()
|
||||
.map(|default| default.provider);
|
||||
let provider_icon = match active_provider {
|
||||
Some(provider) => provider.icon(),
|
||||
None => IconName::Ai,
|
||||
};
|
||||
|
||||
let focus_handle = self.editor().focus_handle(cx).clone();
|
||||
|
||||
PickerPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
@@ -2135,10 +2142,16 @@ impl TextThreadEditor {
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Icon::new(provider_icon)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
.ml_0p5(),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
@@ -2575,7 +2588,7 @@ impl Render for TextThreadEditor {
|
||||
};
|
||||
|
||||
let language_model_selector = self.language_model_selector_menu_handle.clone();
|
||||
let max_mode_toggle = self.render_max_mode_toggle(cx);
|
||||
let burn_mode_toggle = self.render_burn_mode_toggle(cx);
|
||||
|
||||
v_flex()
|
||||
.key_context("ContextEditor")
|
||||
@@ -2630,7 +2643,7 @@ impl Render for TextThreadEditor {
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(self.render_inject_context_menu(cx))
|
||||
.when_some(max_mode_toggle, |this, element| this.child(element)),
|
||||
.when_some(burn_mode_toggle, |this, element| this.child(element)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -2924,13 +2937,6 @@ impl FollowableItem for TextThreadEditor {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContextEditorToolbarItem {
|
||||
active_context_editor: Option<WeakEntity<TextThreadEditor>>,
|
||||
model_summary_editor: Entity<Editor>,
|
||||
}
|
||||
|
||||
impl ContextEditorToolbarItem {}
|
||||
|
||||
pub fn render_remaining_tokens(
|
||||
context_editor: &Entity<TextThreadEditor>,
|
||||
cx: &App,
|
||||
@@ -2983,98 +2989,6 @@ pub fn render_remaining_tokens(
|
||||
)
|
||||
}
|
||||
|
||||
impl Render for ContextEditorToolbarItem {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let left_side = h_flex()
|
||||
.group("chat-title-group")
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.flex_grow()
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.when(self.active_context_editor.is_some(), |left_side| {
|
||||
left_side.child(self.model_summary_editor.clone())
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div().visible_on_hover("chat-title-group").child(
|
||||
IconButton::new("regenerate-context", IconName::RefreshTitle)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(Tooltip::text("Regenerate Title"))
|
||||
.on_click(cx.listener(move |_, _, _window, cx| {
|
||||
cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
let right_side = h_flex()
|
||||
.gap_2()
|
||||
// TODO display this in a nicer way, once we have a design for it.
|
||||
// .children({
|
||||
// let project = self
|
||||
// .workspace
|
||||
// .upgrade()
|
||||
// .map(|workspace| workspace.read(cx).project().downgrade());
|
||||
//
|
||||
// let scan_items_remaining = cx.update_global(|db: &mut SemanticDb, cx| {
|
||||
// project.and_then(|project| db.remaining_summaries(&project, cx))
|
||||
// });
|
||||
// scan_items_remaining
|
||||
// .map(|remaining_items| format!("Files to scan: {}", remaining_items))
|
||||
// })
|
||||
.children(
|
||||
self.active_context_editor
|
||||
.as_ref()
|
||||
.and_then(|editor| editor.upgrade())
|
||||
.and_then(|editor| render_remaining_tokens(&editor, cx)),
|
||||
);
|
||||
|
||||
h_flex()
|
||||
.px_0p5()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(left_side)
|
||||
.child(right_side)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolbarItemView for ContextEditorToolbarItem {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
active_pane_item: Option<&dyn ItemHandle>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> ToolbarItemLocation {
|
||||
self.active_context_editor = active_pane_item
|
||||
.and_then(|item| item.act_as::<TextThreadEditor>(cx))
|
||||
.map(|editor| editor.downgrade());
|
||||
cx.notify();
|
||||
if self.active_context_editor.is_none() {
|
||||
ToolbarItemLocation::Hidden
|
||||
} else {
|
||||
ToolbarItemLocation::PrimaryRight
|
||||
}
|
||||
}
|
||||
|
||||
fn pane_focus_update(
|
||||
&mut self,
|
||||
_pane_focused: bool,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
|
||||
|
||||
pub enum ContextEditorToolbarItemEvent {
|
||||
RegenerateSummary,
|
||||
}
|
||||
impl EventEmitter<ContextEditorToolbarItemEvent> for ContextEditorToolbarItem {}
|
||||
|
||||
enum PendingSlashCommand {}
|
||||
|
||||
fn invoked_slash_command_fold_placeholder(
|
||||
@@ -3240,6 +3154,7 @@ pub fn make_lsp_adapter_delegate(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use editor::SelectionEffects;
|
||||
use fs::FakeFs;
|
||||
use gpui::{App, TestAppContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
@@ -3465,7 +3380,9 @@ mod tests {
|
||||
) {
|
||||
context_editor.update_in(cx, |context_editor, window, cx| {
|
||||
context_editor.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([range]));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([range])
|
||||
});
|
||||
});
|
||||
|
||||
context_editor.copy(&Default::default(), window, cx);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
mod agent_notification;
|
||||
mod animated_label;
|
||||
mod burn_mode_tooltip;
|
||||
mod context_pill;
|
||||
mod max_mode_tooltip;
|
||||
mod onboarding_modal;
|
||||
pub mod preview;
|
||||
mod upsell;
|
||||
|
||||
pub use agent_notification::*;
|
||||
pub use animated_label::*;
|
||||
pub use burn_mode_tooltip::*;
|
||||
pub use context_pill::*;
|
||||
pub use max_mode_tooltip::*;
|
||||
pub use onboarding_modal::*;
|
||||
|
||||
@@ -6,7 +6,7 @@ use anyhow::{Context as _, Result, anyhow};
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};
|
||||
use http_client::http::{self, HeaderMap, HeaderValue};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest, StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumIter, EnumString};
|
||||
use thiserror::Error;
|
||||
@@ -356,7 +356,7 @@ pub async fn complete(
|
||||
.send(request)
|
||||
.await
|
||||
.map_err(AnthropicError::HttpSend)?;
|
||||
let status = response.status();
|
||||
let status_code = response.status();
|
||||
let mut body = String::new();
|
||||
response
|
||||
.body_mut()
|
||||
@@ -364,12 +364,12 @@ pub async fn complete(
|
||||
.await
|
||||
.map_err(AnthropicError::ReadResponse)?;
|
||||
|
||||
if status.is_success() {
|
||||
if status_code.is_success() {
|
||||
Ok(serde_json::from_str(&body).map_err(AnthropicError::DeserializeResponse)?)
|
||||
} else {
|
||||
Err(AnthropicError::HttpResponseError {
|
||||
status: status.as_u16(),
|
||||
body,
|
||||
status_code,
|
||||
message: body,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -444,11 +444,7 @@ impl RateLimitInfo {
|
||||
}
|
||||
|
||||
Self {
|
||||
retry_after: headers
|
||||
.get("retry-after")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| v.parse::<u64>().ok())
|
||||
.map(Duration::from_secs),
|
||||
retry_after: parse_retry_after(headers),
|
||||
requests: RateLimit::from_headers("requests", headers).ok(),
|
||||
tokens: RateLimit::from_headers("tokens", headers).ok(),
|
||||
input_tokens: RateLimit::from_headers("input-tokens", headers).ok(),
|
||||
@@ -457,6 +453,17 @@ impl RateLimitInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the Retry-After header value as an integer number of seconds (anthropic always uses
|
||||
/// seconds). Note that other services might specify an HTTP date or some other format for this
|
||||
/// header. Returns `None` if the header is not present or cannot be parsed.
|
||||
pub fn parse_retry_after(headers: &HeaderMap<HeaderValue>) -> Option<Duration> {
|
||||
headers
|
||||
.get("retry-after")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| v.parse::<u64>().ok())
|
||||
.map(Duration::from_secs)
|
||||
}
|
||||
|
||||
fn get_header<'a>(key: &str, headers: &'a HeaderMap) -> anyhow::Result<&'a str> {
|
||||
Ok(headers
|
||||
.get(key)
|
||||
@@ -520,6 +527,10 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
})
|
||||
.boxed();
|
||||
Ok((stream, Some(rate_limits)))
|
||||
} else if response.status().as_u16() == 529 {
|
||||
Err(AnthropicError::ServerOverloaded {
|
||||
retry_after: rate_limits.retry_after,
|
||||
})
|
||||
} else if let Some(retry_after) = rate_limits.retry_after {
|
||||
Err(AnthropicError::RateLimit { retry_after })
|
||||
} else {
|
||||
@@ -532,10 +543,9 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
|
||||
match serde_json::from_str::<Event>(&body) {
|
||||
Ok(Event::Error { error }) => Err(AnthropicError::ApiError(error)),
|
||||
Ok(_) => Err(AnthropicError::UnexpectedResponseFormat(body)),
|
||||
Err(_) => Err(AnthropicError::HttpResponseError {
|
||||
status: response.status().as_u16(),
|
||||
body: body,
|
||||
Ok(_) | Err(_) => Err(AnthropicError::HttpResponseError {
|
||||
status_code: response.status(),
|
||||
message: body,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -801,16 +811,19 @@ pub enum AnthropicError {
|
||||
ReadResponse(io::Error),
|
||||
|
||||
/// HTTP error response from the API
|
||||
HttpResponseError { status: u16, body: String },
|
||||
HttpResponseError {
|
||||
status_code: StatusCode,
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// Rate limit exceeded
|
||||
RateLimit { retry_after: Duration },
|
||||
|
||||
/// Server overloaded
|
||||
ServerOverloaded { retry_after: Option<Duration> },
|
||||
|
||||
/// API returned an error response
|
||||
ApiError(ApiError),
|
||||
|
||||
/// Unexpected response format
|
||||
UnexpectedResponseFormat(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Error)]
|
||||
|
||||
@@ -2140,7 +2140,8 @@ impl AssistantContext {
|
||||
);
|
||||
}
|
||||
LanguageModelCompletionEvent::ToolUse(_) |
|
||||
LanguageModelCompletionEvent::UsageUpdate(_) => {}
|
||||
LanguageModelCompletionEvent::ToolUseJsonParseError { .. } |
|
||||
LanguageModelCompletionEvent::UsageUpdate(_) => {}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2346,13 +2347,13 @@ impl AssistantContext {
|
||||
completion_request.messages.push(request_message);
|
||||
}
|
||||
}
|
||||
let supports_max_mode = if let Some(model) = model {
|
||||
model.supports_max_mode()
|
||||
let supports_burn_mode = if let Some(model) = model {
|
||||
model.supports_burn_mode()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if supports_max_mode {
|
||||
if supports_burn_mode {
|
||||
completion_request.mode = Some(self.completion_mode.into());
|
||||
}
|
||||
completion_request
|
||||
|
||||
@@ -74,7 +74,7 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
.slice(section.range.to_offset(&context_buffer)),
|
||||
);
|
||||
file_command_new_outputs.push(Arc::new(FileSlashCommand).run(
|
||||
&[metadata.path.clone()],
|
||||
std::slice::from_ref(&metadata.path),
|
||||
context_slash_command_output_sections,
|
||||
context_buffer.clone(),
|
||||
workspace.clone(),
|
||||
|
||||
@@ -29,6 +29,7 @@ use std::{
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
sync::mpsc,
|
||||
time::Duration,
|
||||
};
|
||||
use util::path;
|
||||
|
||||
@@ -1658,12 +1659,14 @@ async fn retry_on_rate_limit<R>(mut request: impl AsyncFnMut() -> Result<R>) ->
|
||||
match request().await {
|
||||
Ok(result) => return Ok(result),
|
||||
Err(err) => match err.downcast::<LanguageModelCompletionError>() {
|
||||
Ok(err) => match err {
|
||||
LanguageModelCompletionError::RateLimitExceeded { retry_after } => {
|
||||
Ok(err) => match &err {
|
||||
LanguageModelCompletionError::RateLimitExceeded { retry_after, .. }
|
||||
| LanguageModelCompletionError::ServerOverloaded { retry_after, .. } => {
|
||||
let retry_after = retry_after.unwrap_or(Duration::from_secs(5));
|
||||
// Wait for the duration supplied, with some jitter to avoid all requests being made at the same time.
|
||||
let jitter = retry_after.mul_f64(rand::thread_rng().gen_range(0.0..1.0));
|
||||
eprintln!(
|
||||
"Attempt #{attempt}: Rate limit exceeded. Retry after {retry_after:?} + jitter of {jitter:?}"
|
||||
"Attempt #{attempt}: {err}. Retry after {retry_after:?} + jitter of {jitter:?}"
|
||||
);
|
||||
Timer::after(retry_after + jitter).await;
|
||||
continue;
|
||||
|
||||
@@ -10,7 +10,7 @@ use assistant_tool::{
|
||||
ToolUseStatus,
|
||||
};
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey, scroll::Autoscroll};
|
||||
use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyWindowHandle, App, AppContext, AsyncApp, Entity, Task,
|
||||
@@ -823,7 +823,7 @@ impl ToolCard for EditFileToolCard {
|
||||
let first_hunk_start =
|
||||
first_hunk.multi_buffer_range().start;
|
||||
editor.change_selections(
|
||||
Some(Autoscroll::fit()),
|
||||
Default::default(),
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
@@ -1065,7 +1065,7 @@ fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
selection_background_color: cx.theme().colors().element_selection_background,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
use schemars::{
|
||||
JsonSchema,
|
||||
schema::{RootSchema, Schema, SchemaObject},
|
||||
JsonSchema, Schema,
|
||||
generate::SchemaSettings,
|
||||
transform::{Transform, transform_subschemas},
|
||||
};
|
||||
|
||||
pub fn json_schema_for<T: JsonSchema>(
|
||||
@@ -13,7 +14,7 @@ pub fn json_schema_for<T: JsonSchema>(
|
||||
}
|
||||
|
||||
fn schema_to_json(
|
||||
schema: &RootSchema,
|
||||
schema: &Schema,
|
||||
format: LanguageModelToolSchemaFormat,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut value = serde_json::to_value(schema)?;
|
||||
@@ -21,58 +22,42 @@ fn schema_to_json(
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn root_schema_for<T: JsonSchema>(format: LanguageModelToolSchemaFormat) -> RootSchema {
|
||||
fn root_schema_for<T: JsonSchema>(format: LanguageModelToolSchemaFormat) -> Schema {
|
||||
let mut generator = match format {
|
||||
LanguageModelToolSchemaFormat::JsonSchema => schemars::SchemaGenerator::default(),
|
||||
LanguageModelToolSchemaFormat::JsonSchemaSubset => {
|
||||
schemars::r#gen::SchemaSettings::default()
|
||||
.with(|settings| {
|
||||
settings.meta_schema = None;
|
||||
settings.inline_subschemas = true;
|
||||
settings
|
||||
.visitors
|
||||
.push(Box::new(TransformToJsonSchemaSubsetVisitor));
|
||||
})
|
||||
.into_generator()
|
||||
}
|
||||
LanguageModelToolSchemaFormat::JsonSchema => SchemaSettings::draft07().into_generator(),
|
||||
// TODO: Gemini docs mention using a subset of OpenAPI 3, so this may benefit from using
|
||||
// `SchemaSettings::openapi3()`.
|
||||
LanguageModelToolSchemaFormat::JsonSchemaSubset => SchemaSettings::draft07()
|
||||
.with(|settings| {
|
||||
settings.meta_schema = None;
|
||||
settings.inline_subschemas = true;
|
||||
})
|
||||
.with_transform(ToJsonSchemaSubsetTransform)
|
||||
.into_generator(),
|
||||
};
|
||||
generator.root_schema_for::<T>()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TransformToJsonSchemaSubsetVisitor;
|
||||
struct ToJsonSchemaSubsetTransform;
|
||||
|
||||
impl schemars::visit::Visitor for TransformToJsonSchemaSubsetVisitor {
|
||||
fn visit_root_schema(&mut self, root: &mut RootSchema) {
|
||||
schemars::visit::visit_root_schema(self, root)
|
||||
}
|
||||
|
||||
fn visit_schema(&mut self, schema: &mut Schema) {
|
||||
schemars::visit::visit_schema(self, schema)
|
||||
}
|
||||
|
||||
fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
|
||||
impl Transform for ToJsonSchemaSubsetTransform {
|
||||
fn transform(&mut self, schema: &mut Schema) {
|
||||
// Ensure that the type field is not an array, this happens when we use
|
||||
// Option<T>, the type will be [T, "null"].
|
||||
if let Some(instance_type) = schema.instance_type.take() {
|
||||
schema.instance_type = match instance_type {
|
||||
schemars::schema::SingleOrVec::Single(t) => {
|
||||
Some(schemars::schema::SingleOrVec::Single(t))
|
||||
if let Some(type_field) = schema.get_mut("type") {
|
||||
if let Some(types) = type_field.as_array() {
|
||||
if let Some(first_type) = types.first() {
|
||||
*type_field = first_type.clone();
|
||||
}
|
||||
schemars::schema::SingleOrVec::Vec(items) => items
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(schemars::schema::SingleOrVec::from),
|
||||
};
|
||||
}
|
||||
|
||||
// One of is not supported, use anyOf instead.
|
||||
if let Some(subschema) = schema.subschemas.as_mut() {
|
||||
if let Some(one_of) = subschema.one_of.take() {
|
||||
subschema.any_of = Some(one_of);
|
||||
}
|
||||
}
|
||||
|
||||
schemars::visit::visit_schema_object(self, schema)
|
||||
// oneOf is not supported, use anyOf instead
|
||||
if let Some(one_of) = schema.remove("oneOf") {
|
||||
schema.insert("anyOf".to_string(), one_of);
|
||||
}
|
||||
|
||||
transform_subschemas(self, schema);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,7 +691,7 @@ fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
selection_background_color: cx.theme().colors().element_selection_background,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use auto_update::AutoUpdater;
|
||||
use client::proto::UpdateNotification;
|
||||
use editor::{Editor, MultiBuffer};
|
||||
use gpui::{App, Context, DismissEvent, Entity, SharedString, Window, actions, prelude::*};
|
||||
use gpui::{App, Context, DismissEvent, Entity, Window, actions, prelude::*};
|
||||
use http_client::HttpClient;
|
||||
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
|
||||
use release_channel::{AppVersion, ReleaseChannel};
|
||||
@@ -94,7 +94,6 @@ fn view_release_notes_locally(
|
||||
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
|
||||
let tab_content = Some(SharedString::from(body.title.to_string()));
|
||||
let editor = cx.new(|cx| {
|
||||
Editor::for_multibuffer(buffer, Some(project), window, cx)
|
||||
});
|
||||
@@ -105,7 +104,6 @@ fn view_release_notes_locally(
|
||||
editor,
|
||||
workspace_handle,
|
||||
language_registry,
|
||||
tab_content,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -25,5 +25,4 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
strum.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
|
||||
workspace-hack.workspace = true
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
mod models;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::pin::Pin;
|
||||
|
||||
use anyhow::{Context as _, Error, Result, anyhow};
|
||||
use anyhow::{Context, Error, Result, anyhow};
|
||||
use aws_sdk_bedrockruntime as bedrock;
|
||||
pub use aws_sdk_bedrockruntime as bedrock_client;
|
||||
pub use aws_sdk_bedrockruntime::types::{
|
||||
@@ -24,9 +21,10 @@ pub use bedrock::types::{
|
||||
ToolResultContentBlock as BedrockToolResultContentBlock,
|
||||
ToolResultStatus as BedrockToolResultStatus, ToolUseBlock as BedrockToolUseBlock,
|
||||
};
|
||||
use futures::stream::{self, BoxStream, Stream};
|
||||
use futures::stream::{self, BoxStream};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Number, Value};
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use crate::models::*;
|
||||
@@ -34,70 +32,59 @@ pub use crate::models::*;
|
||||
pub async fn stream_completion(
|
||||
client: bedrock::Client,
|
||||
request: Request,
|
||||
handle: tokio::runtime::Handle,
|
||||
) -> Result<BoxStream<'static, Result<BedrockStreamingResponse, BedrockError>>, Error> {
|
||||
handle
|
||||
.spawn(async move {
|
||||
let mut response = bedrock::Client::converse_stream(&client)
|
||||
.model_id(request.model.clone())
|
||||
.set_messages(request.messages.into());
|
||||
let mut response = bedrock::Client::converse_stream(&client)
|
||||
.model_id(request.model.clone())
|
||||
.set_messages(request.messages.into());
|
||||
|
||||
if let Some(Thinking::Enabled {
|
||||
budget_tokens: Some(budget_tokens),
|
||||
}) = request.thinking
|
||||
{
|
||||
response =
|
||||
response.additional_model_request_fields(Document::Object(HashMap::from([(
|
||||
"thinking".to_string(),
|
||||
Document::from(HashMap::from([
|
||||
("type".to_string(), Document::String("enabled".to_string())),
|
||||
(
|
||||
"budget_tokens".to_string(),
|
||||
Document::Number(AwsNumber::PosInt(budget_tokens)),
|
||||
),
|
||||
])),
|
||||
)])));
|
||||
}
|
||||
if let Some(Thinking::Enabled {
|
||||
budget_tokens: Some(budget_tokens),
|
||||
}) = request.thinking
|
||||
{
|
||||
let thinking_config = HashMap::from([
|
||||
("type".to_string(), Document::String("enabled".to_string())),
|
||||
(
|
||||
"budget_tokens".to_string(),
|
||||
Document::Number(AwsNumber::PosInt(budget_tokens)),
|
||||
),
|
||||
]);
|
||||
response = response.additional_model_request_fields(Document::Object(HashMap::from([(
|
||||
"thinking".to_string(),
|
||||
Document::from(thinking_config),
|
||||
)])));
|
||||
}
|
||||
|
||||
if request.tools.is_some() && !request.tools.as_ref().unwrap().tools.is_empty() {
|
||||
response = response.set_tool_config(request.tools);
|
||||
}
|
||||
if request
|
||||
.tools
|
||||
.as_ref()
|
||||
.map_or(false, |t| !t.tools.is_empty())
|
||||
{
|
||||
response = response.set_tool_config(request.tools);
|
||||
}
|
||||
|
||||
let response = response.send().await;
|
||||
let output = response
|
||||
.send()
|
||||
.await
|
||||
.context("Failed to send API request to Bedrock");
|
||||
|
||||
match response {
|
||||
Ok(output) => {
|
||||
let stream: Pin<
|
||||
Box<
|
||||
dyn Stream<Item = Result<BedrockStreamingResponse, BedrockError>>
|
||||
+ Send,
|
||||
>,
|
||||
> = Box::pin(stream::unfold(output.stream, |mut stream| async move {
|
||||
match stream.recv().await {
|
||||
Ok(Some(output)) => Some(({ Ok(output) }, stream)),
|
||||
Ok(None) => None,
|
||||
Err(err) => {
|
||||
Some((
|
||||
// TODO: Figure out how we can capture Throttling Exceptions
|
||||
Err(BedrockError::ClientError(anyhow!(
|
||||
"{:?}",
|
||||
aws_sdk_bedrockruntime::error::DisplayErrorContext(err)
|
||||
))),
|
||||
stream,
|
||||
))
|
||||
}
|
||||
}
|
||||
}));
|
||||
Ok(stream)
|
||||
}
|
||||
Err(err) => Err(anyhow!(
|
||||
"{:?}",
|
||||
aws_sdk_bedrockruntime::error::DisplayErrorContext(err)
|
||||
let stream = Box::pin(stream::unfold(
|
||||
output?.stream,
|
||||
move |mut stream| async move {
|
||||
match stream.recv().await {
|
||||
Ok(Some(output)) => Some((Ok(output), stream)),
|
||||
Ok(None) => None,
|
||||
Err(err) => Some((
|
||||
Err(BedrockError::ClientError(anyhow!(
|
||||
"{:?}",
|
||||
aws_sdk_bedrockruntime::error::DisplayErrorContext(err)
|
||||
))),
|
||||
stream,
|
||||
)),
|
||||
}
|
||||
})
|
||||
.await
|
||||
.context("spawning a task")?
|
||||
},
|
||||
));
|
||||
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
pub fn aws_document_to_value(document: &Document) -> Value {
|
||||
|
||||
@@ -1867,7 +1867,7 @@ mod tests {
|
||||
let hunk = diff.hunks(&buffer, cx).next().unwrap();
|
||||
|
||||
let new_index_text = diff
|
||||
.stage_or_unstage_hunks(true, &[hunk.clone()], &buffer, true, cx)
|
||||
.stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
assert_eq!(new_index_text, buffer_text);
|
||||
|
||||
@@ -12,7 +12,6 @@ pub struct CallSettings {
|
||||
|
||||
/// Configuration of voice calls in Zed.
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct CallSettingsContent {
|
||||
/// Whether the microphone should be muted when joining a channel or a call.
|
||||
///
|
||||
|
||||
@@ -1404,6 +1404,9 @@ async fn sync_model_request_usage_with_stripe(
|
||||
llm_db: &Arc<LlmDatabase>,
|
||||
stripe_billing: &Arc<StripeBilling>,
|
||||
) -> anyhow::Result<()> {
|
||||
log::info!("Stripe usage sync: Starting");
|
||||
let started_at = Utc::now();
|
||||
|
||||
let staff_users = app.db.get_staff_users().await?;
|
||||
let staff_user_ids = staff_users
|
||||
.iter()
|
||||
@@ -1448,6 +1451,10 @@ async fn sync_model_request_usage_with_stripe(
|
||||
.find_price_by_lookup_key("claude-3-7-sonnet-requests-max")
|
||||
.await?;
|
||||
|
||||
let usage_meter_count = usage_meters.len();
|
||||
|
||||
log::info!("Stripe usage sync: Syncing {usage_meter_count} usage meters");
|
||||
|
||||
for (usage_meter, usage) in usage_meters {
|
||||
maybe!(async {
|
||||
let Some((billing_customer, billing_subscription)) =
|
||||
@@ -1504,5 +1511,10 @@ async fn sync_model_request_usage_with_stripe(
|
||||
.log_err();
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Stripe usage sync: Synced {usage_meter_count} usage meters in {:?}",
|
||||
Utc::now() - started_at
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,20 +4,19 @@ mod tables;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
use crate::{Error, Result, executor::Executor};
|
||||
use crate::{Error, Result};
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use dashmap::DashMap;
|
||||
use futures::StreamExt;
|
||||
use project_repository_statuses::StatusKind;
|
||||
use rand::{Rng, SeedableRng, prelude::StdRng};
|
||||
use rpc::ExtensionProvides;
|
||||
use rpc::{
|
||||
ConnectionId, ExtensionMetadata,
|
||||
proto::{self},
|
||||
};
|
||||
use sea_orm::{
|
||||
ActiveValue, Condition, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbErr,
|
||||
ActiveValue, Condition, ConnectionTrait, DatabaseConnection, DatabaseTransaction,
|
||||
FromQueryResult, IntoActiveModel, IsolationLevel, JoinType, QueryOrder, QuerySelect, Statement,
|
||||
TransactionTrait,
|
||||
entity::prelude::*,
|
||||
@@ -33,7 +32,6 @@ use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use time::PrimitiveDateTime;
|
||||
use tokio::sync::{Mutex, OwnedMutexGuard};
|
||||
@@ -58,6 +56,7 @@ pub use tables::*;
|
||||
|
||||
#[cfg(test)]
|
||||
pub struct DatabaseTestOptions {
|
||||
pub executor: gpui::BackgroundExecutor,
|
||||
pub runtime: tokio::runtime::Runtime,
|
||||
pub query_failure_probability: parking_lot::Mutex<f64>,
|
||||
}
|
||||
@@ -69,8 +68,6 @@ pub struct Database {
|
||||
pool: DatabaseConnection,
|
||||
rooms: DashMap<RoomId, Arc<Mutex<()>>>,
|
||||
projects: DashMap<ProjectId, Arc<Mutex<()>>>,
|
||||
rng: Mutex<StdRng>,
|
||||
executor: Executor,
|
||||
notification_kinds_by_id: HashMap<NotificationKindId, &'static str>,
|
||||
notification_kinds_by_name: HashMap<String, NotificationKindId>,
|
||||
#[cfg(test)]
|
||||
@@ -81,17 +78,15 @@ pub struct Database {
|
||||
// separate files in the `queries` folder.
|
||||
impl Database {
|
||||
/// Connects to the database with the given options
|
||||
pub async fn new(options: ConnectOptions, executor: Executor) -> Result<Self> {
|
||||
pub async fn new(options: ConnectOptions) -> Result<Self> {
|
||||
sqlx::any::install_default_drivers();
|
||||
Ok(Self {
|
||||
options: options.clone(),
|
||||
pool: sea_orm::Database::connect(options).await?,
|
||||
rooms: DashMap::with_capacity(16384),
|
||||
projects: DashMap::with_capacity(16384),
|
||||
rng: Mutex::new(StdRng::seed_from_u64(0)),
|
||||
notification_kinds_by_id: HashMap::default(),
|
||||
notification_kinds_by_name: HashMap::default(),
|
||||
executor,
|
||||
#[cfg(test)]
|
||||
test_options: None,
|
||||
})
|
||||
@@ -107,48 +102,13 @@ impl Database {
|
||||
self.projects.clear();
|
||||
}
|
||||
|
||||
/// Transaction runs things in a transaction. If you want to call other methods
|
||||
/// and pass the transaction around you need to reborrow the transaction at each
|
||||
/// call site with: `&*tx`.
|
||||
pub async fn transaction<F, Fut, T>(&self, f: F) -> Result<T>
|
||||
where
|
||||
F: Send + Fn(TransactionHandle) -> Fut,
|
||||
Fut: Send + Future<Output = Result<T>>,
|
||||
{
|
||||
let body = async {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let (tx, result) = self.with_transaction(&f).await?;
|
||||
match result {
|
||||
Ok(result) => match tx.commit().await.map_err(Into::into) {
|
||||
Ok(()) => return Ok(result),
|
||||
Err(error) => {
|
||||
if !self.retry_on_serialization_error(&error, i).await {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tx.rollback().await?;
|
||||
if !self.retry_on_serialization_error(&error, i).await {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
};
|
||||
|
||||
self.run(body).await
|
||||
}
|
||||
|
||||
pub async fn weak_transaction<F, Fut, T>(&self, f: F) -> Result<T>
|
||||
where
|
||||
F: Send + Fn(TransactionHandle) -> Fut,
|
||||
Fut: Send + Future<Output = Result<T>>,
|
||||
{
|
||||
let body = async {
|
||||
let (tx, result) = self.with_weak_transaction(&f).await?;
|
||||
let (tx, result) = self.with_transaction(&f).await?;
|
||||
match result {
|
||||
Ok(result) => match tx.commit().await.map_err(Into::into) {
|
||||
Ok(()) => Ok(result),
|
||||
@@ -174,44 +134,28 @@ impl Database {
|
||||
Fut: Send + Future<Output = Result<Option<(RoomId, T)>>>,
|
||||
{
|
||||
let body = async {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let (tx, result) = self.with_transaction(&f).await?;
|
||||
match result {
|
||||
Ok(Some((room_id, data))) => {
|
||||
let lock = self.rooms.entry(room_id).or_default().clone();
|
||||
let _guard = lock.lock_owned().await;
|
||||
match tx.commit().await.map_err(Into::into) {
|
||||
Ok(()) => {
|
||||
return Ok(Some(TransactionGuard {
|
||||
data,
|
||||
_guard,
|
||||
_not_send: PhantomData,
|
||||
}));
|
||||
}
|
||||
Err(error) => {
|
||||
if !self.retry_on_serialization_error(&error, i).await {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => match tx.commit().await.map_err(Into::into) {
|
||||
Ok(()) => return Ok(None),
|
||||
Err(error) => {
|
||||
if !self.retry_on_serialization_error(&error, i).await {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tx.rollback().await?;
|
||||
if !self.retry_on_serialization_error(&error, i).await {
|
||||
return Err(error);
|
||||
}
|
||||
let (tx, result) = self.with_transaction(&f).await?;
|
||||
match result {
|
||||
Ok(Some((room_id, data))) => {
|
||||
let lock = self.rooms.entry(room_id).or_default().clone();
|
||||
let _guard = lock.lock_owned().await;
|
||||
match tx.commit().await.map_err(Into::into) {
|
||||
Ok(()) => Ok(Some(TransactionGuard {
|
||||
data,
|
||||
_guard,
|
||||
_not_send: PhantomData,
|
||||
})),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
Ok(None) => match tx.commit().await.map_err(Into::into) {
|
||||
Ok(()) => Ok(None),
|
||||
Err(error) => Err(error),
|
||||
},
|
||||
Err(error) => {
|
||||
tx.rollback().await?;
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -229,38 +173,26 @@ impl Database {
|
||||
{
|
||||
let room_id = Database::room_id_for_project(self, project_id).await?;
|
||||
let body = async {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let lock = if let Some(room_id) = room_id {
|
||||
self.rooms.entry(room_id).or_default().clone()
|
||||
} else {
|
||||
self.projects.entry(project_id).or_default().clone()
|
||||
};
|
||||
let _guard = lock.lock_owned().await;
|
||||
let (tx, result) = self.with_transaction(&f).await?;
|
||||
match result {
|
||||
Ok(data) => match tx.commit().await.map_err(Into::into) {
|
||||
Ok(()) => {
|
||||
return Ok(TransactionGuard {
|
||||
data,
|
||||
_guard,
|
||||
_not_send: PhantomData,
|
||||
});
|
||||
}
|
||||
Err(error) => {
|
||||
if !self.retry_on_serialization_error(&error, i).await {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tx.rollback().await?;
|
||||
if !self.retry_on_serialization_error(&error, i).await {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
let lock = if let Some(room_id) = room_id {
|
||||
self.rooms.entry(room_id).or_default().clone()
|
||||
} else {
|
||||
self.projects.entry(project_id).or_default().clone()
|
||||
};
|
||||
let _guard = lock.lock_owned().await;
|
||||
let (tx, result) = self.with_transaction(&f).await?;
|
||||
match result {
|
||||
Ok(data) => match tx.commit().await.map_err(Into::into) {
|
||||
Ok(()) => Ok(TransactionGuard {
|
||||
data,
|
||||
_guard,
|
||||
_not_send: PhantomData,
|
||||
}),
|
||||
Err(error) => Err(error),
|
||||
},
|
||||
Err(error) => {
|
||||
tx.rollback().await?;
|
||||
Err(error)
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -280,34 +212,22 @@ impl Database {
|
||||
Fut: Send + Future<Output = Result<T>>,
|
||||
{
|
||||
let body = async {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let lock = self.rooms.entry(room_id).or_default().clone();
|
||||
let _guard = lock.lock_owned().await;
|
||||
let (tx, result) = self.with_transaction(&f).await?;
|
||||
match result {
|
||||
Ok(data) => match tx.commit().await.map_err(Into::into) {
|
||||
Ok(()) => {
|
||||
return Ok(TransactionGuard {
|
||||
data,
|
||||
_guard,
|
||||
_not_send: PhantomData,
|
||||
});
|
||||
}
|
||||
Err(error) => {
|
||||
if !self.retry_on_serialization_error(&error, i).await {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tx.rollback().await?;
|
||||
if !self.retry_on_serialization_error(&error, i).await {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
let lock = self.rooms.entry(room_id).or_default().clone();
|
||||
let _guard = lock.lock_owned().await;
|
||||
let (tx, result) = self.with_transaction(&f).await?;
|
||||
match result {
|
||||
Ok(data) => match tx.commit().await.map_err(Into::into) {
|
||||
Ok(()) => Ok(TransactionGuard {
|
||||
data,
|
||||
_guard,
|
||||
_not_send: PhantomData,
|
||||
}),
|
||||
Err(error) => Err(error),
|
||||
},
|
||||
Err(error) => {
|
||||
tx.rollback().await?;
|
||||
Err(error)
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -315,28 +235,6 @@ impl Database {
|
||||
}
|
||||
|
||||
async fn with_transaction<F, Fut, T>(&self, f: &F) -> Result<(DatabaseTransaction, Result<T>)>
|
||||
where
|
||||
F: Send + Fn(TransactionHandle) -> Fut,
|
||||
Fut: Send + Future<Output = Result<T>>,
|
||||
{
|
||||
let tx = self
|
||||
.pool
|
||||
.begin_with_config(Some(IsolationLevel::Serializable), None)
|
||||
.await?;
|
||||
|
||||
let mut tx = Arc::new(Some(tx));
|
||||
let result = f(TransactionHandle(tx.clone())).await;
|
||||
let tx = Arc::get_mut(&mut tx)
|
||||
.and_then(|tx| tx.take())
|
||||
.context("couldn't complete transaction because it's still in use")?;
|
||||
|
||||
Ok((tx, result))
|
||||
}
|
||||
|
||||
async fn with_weak_transaction<F, Fut, T>(
|
||||
&self,
|
||||
f: &F,
|
||||
) -> Result<(DatabaseTransaction, Result<T>)>
|
||||
where
|
||||
F: Send + Fn(TransactionHandle) -> Fut,
|
||||
Fut: Send + Future<Output = Result<T>>,
|
||||
@@ -361,13 +259,13 @@ impl Database {
|
||||
{
|
||||
#[cfg(test)]
|
||||
{
|
||||
use rand::prelude::*;
|
||||
|
||||
let test_options = self.test_options.as_ref().unwrap();
|
||||
if let Executor::Deterministic(executor) = &self.executor {
|
||||
executor.simulate_random_delay().await;
|
||||
let fail_probability = *test_options.query_failure_probability.lock();
|
||||
if executor.rng().gen_bool(fail_probability) {
|
||||
return Err(anyhow!("simulated query failure"))?;
|
||||
}
|
||||
test_options.executor.simulate_random_delay().await;
|
||||
let fail_probability = *test_options.query_failure_probability.lock();
|
||||
if test_options.executor.rng().gen_bool(fail_probability) {
|
||||
return Err(anyhow!("simulated query failure"))?;
|
||||
}
|
||||
|
||||
test_options.runtime.block_on(future)
|
||||
@@ -378,46 +276,6 @@ impl Database {
|
||||
future.await
|
||||
}
|
||||
}
|
||||
|
||||
async fn retry_on_serialization_error(&self, error: &Error, prev_attempt_count: usize) -> bool {
|
||||
// If the error is due to a failure to serialize concurrent transactions, then retry
|
||||
// this transaction after a delay. With each subsequent retry, double the delay duration.
|
||||
// Also vary the delay randomly in order to ensure different database connections retry
|
||||
// at different times.
|
||||
const SLEEPS: [f32; 10] = [10., 20., 40., 80., 160., 320., 640., 1280., 2560., 5120.];
|
||||
if is_serialization_error(error) && prev_attempt_count < SLEEPS.len() {
|
||||
let base_delay = SLEEPS[prev_attempt_count];
|
||||
let randomized_delay = base_delay * self.rng.lock().await.gen_range(0.5..=2.0);
|
||||
log::warn!(
|
||||
"retrying transaction after serialization error. delay: {} ms.",
|
||||
randomized_delay
|
||||
);
|
||||
self.executor
|
||||
.sleep(Duration::from_millis(randomized_delay as u64))
|
||||
.await;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_serialization_error(error: &Error) -> bool {
|
||||
const SERIALIZATION_FAILURE_CODE: &str = "40001";
|
||||
match error {
|
||||
Error::Database(
|
||||
DbErr::Exec(sea_orm::RuntimeErr::SqlxError(error))
|
||||
| DbErr::Query(sea_orm::RuntimeErr::SqlxError(error)),
|
||||
) if error
|
||||
.as_database_error()
|
||||
.and_then(|error| error.code())
|
||||
.as_deref()
|
||||
== Some(SERIALIZATION_FAILURE_CODE) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a [`DatabaseTransaction`].
|
||||
|
||||
@@ -20,7 +20,7 @@ impl Database {
|
||||
&self,
|
||||
params: &CreateBillingCustomerParams,
|
||||
) -> Result<billing_customer::Model> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let customer = billing_customer::Entity::insert(billing_customer::ActiveModel {
|
||||
user_id: ActiveValue::set(params.user_id),
|
||||
stripe_customer_id: ActiveValue::set(params.stripe_customer_id.clone()),
|
||||
@@ -40,7 +40,7 @@ impl Database {
|
||||
id: BillingCustomerId,
|
||||
params: &UpdateBillingCustomerParams,
|
||||
) -> Result<()> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
billing_customer::Entity::update(billing_customer::ActiveModel {
|
||||
id: ActiveValue::set(id),
|
||||
user_id: params.user_id.clone(),
|
||||
@@ -61,7 +61,7 @@ impl Database {
|
||||
&self,
|
||||
id: BillingCustomerId,
|
||||
) -> Result<Option<billing_customer::Model>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(billing_customer::Entity::find()
|
||||
.filter(billing_customer::Column::Id.eq(id))
|
||||
.one(&*tx)
|
||||
@@ -75,7 +75,7 @@ impl Database {
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<Option<billing_customer::Model>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(billing_customer::Entity::find()
|
||||
.filter(billing_customer::Column::UserId.eq(user_id))
|
||||
.one(&*tx)
|
||||
@@ -89,7 +89,7 @@ impl Database {
|
||||
&self,
|
||||
stripe_customer_id: &str,
|
||||
) -> Result<Option<billing_customer::Model>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(billing_customer::Entity::find()
|
||||
.filter(billing_customer::Column::StripeCustomerId.eq(stripe_customer_id))
|
||||
.one(&*tx)
|
||||
|
||||
@@ -22,7 +22,7 @@ impl Database {
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<Option<billing_preference::Model>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(billing_preference::Entity::find()
|
||||
.filter(billing_preference::Column::UserId.eq(user_id))
|
||||
.one(&*tx)
|
||||
@@ -37,7 +37,7 @@ impl Database {
|
||||
user_id: UserId,
|
||||
params: &CreateBillingPreferencesParams,
|
||||
) -> Result<billing_preference::Model> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let preferences = billing_preference::Entity::insert(billing_preference::ActiveModel {
|
||||
user_id: ActiveValue::set(user_id),
|
||||
max_monthly_llm_usage_spending_in_cents: ActiveValue::set(
|
||||
@@ -65,7 +65,7 @@ impl Database {
|
||||
user_id: UserId,
|
||||
params: &UpdateBillingPreferencesParams,
|
||||
) -> Result<billing_preference::Model> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let preferences = billing_preference::Entity::update_many()
|
||||
.set(billing_preference::ActiveModel {
|
||||
max_monthly_llm_usage_spending_in_cents: params
|
||||
|
||||
@@ -35,7 +35,7 @@ impl Database {
|
||||
&self,
|
||||
params: &CreateBillingSubscriptionParams,
|
||||
) -> Result<billing_subscription::Model> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let id = billing_subscription::Entity::insert(billing_subscription::ActiveModel {
|
||||
billing_customer_id: ActiveValue::set(params.billing_customer_id),
|
||||
kind: ActiveValue::set(params.kind),
|
||||
@@ -64,7 +64,7 @@ impl Database {
|
||||
id: BillingSubscriptionId,
|
||||
params: &UpdateBillingSubscriptionParams,
|
||||
) -> Result<()> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
billing_subscription::Entity::update(billing_subscription::ActiveModel {
|
||||
id: ActiveValue::set(id),
|
||||
billing_customer_id: params.billing_customer_id.clone(),
|
||||
@@ -90,7 +90,7 @@ impl Database {
|
||||
&self,
|
||||
id: BillingSubscriptionId,
|
||||
) -> Result<Option<billing_subscription::Model>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(billing_subscription::Entity::find_by_id(id)
|
||||
.one(&*tx)
|
||||
.await?)
|
||||
@@ -103,7 +103,7 @@ impl Database {
|
||||
&self,
|
||||
stripe_subscription_id: &str,
|
||||
) -> Result<Option<billing_subscription::Model>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(billing_subscription::Entity::find()
|
||||
.filter(
|
||||
billing_subscription::Column::StripeSubscriptionId.eq(stripe_subscription_id),
|
||||
@@ -118,7 +118,7 @@ impl Database {
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<Option<billing_subscription::Model>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(billing_subscription::Entity::find()
|
||||
.inner_join(billing_customer::Entity)
|
||||
.filter(billing_customer::Column::UserId.eq(user_id))
|
||||
@@ -152,7 +152,7 @@ impl Database {
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<Vec<billing_subscription::Model>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let subscriptions = billing_subscription::Entity::find()
|
||||
.inner_join(billing_customer::Entity)
|
||||
.filter(billing_customer::Column::UserId.eq(user_id))
|
||||
@@ -169,7 +169,7 @@ impl Database {
|
||||
&self,
|
||||
user_ids: HashSet<UserId>,
|
||||
) -> Result<HashMap<UserId, (billing_customer::Model, billing_subscription::Model)>> {
|
||||
self.weak_transaction(|tx| {
|
||||
self.transaction(|tx| {
|
||||
let user_ids = user_ids.clone();
|
||||
async move {
|
||||
let mut rows = billing_subscription::Entity::find()
|
||||
@@ -201,7 +201,7 @@ impl Database {
|
||||
&self,
|
||||
user_ids: HashSet<UserId>,
|
||||
) -> Result<HashMap<UserId, (billing_customer::Model, billing_subscription::Model)>> {
|
||||
self.weak_transaction(|tx| {
|
||||
self.transaction(|tx| {
|
||||
let user_ids = user_ids.clone();
|
||||
async move {
|
||||
let mut rows = billing_subscription::Entity::find()
|
||||
@@ -236,7 +236,7 @@ impl Database {
|
||||
|
||||
/// Returns the count of the active billing subscriptions for the user with the specified ID.
|
||||
pub async fn count_active_billing_subscriptions(&self, user_id: UserId) -> Result<usize> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let count = billing_subscription::Entity::find()
|
||||
.inner_join(billing_customer::Entity)
|
||||
.filter(
|
||||
|
||||
@@ -501,10 +501,8 @@ impl Database {
|
||||
|
||||
/// Returns all channels for the user with the given ID.
|
||||
pub async fn get_channels_for_user(&self, user_id: UserId) -> Result<ChannelsForUser> {
|
||||
self.weak_transaction(
|
||||
|tx| async move { self.get_user_channels(user_id, None, true, &tx).await },
|
||||
)
|
||||
.await
|
||||
self.transaction(|tx| async move { self.get_user_channels(user_id, None, true, &tx).await })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns all channels for the user with the given ID that are descendants
|
||||
@@ -734,8 +732,8 @@ impl Database {
|
||||
users.push(proto::User {
|
||||
id: user.id.to_proto(),
|
||||
avatar_url: format!(
|
||||
"https://github.com/{}.png?size=128",
|
||||
user.github_login
|
||||
"https://avatars.githubusercontent.com/u/{}?s=128&v=4",
|
||||
user.github_user_id
|
||||
),
|
||||
github_login: user.github_login,
|
||||
name: user.name,
|
||||
|
||||
@@ -15,7 +15,7 @@ impl Database {
|
||||
user_b_busy: bool,
|
||||
}
|
||||
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let user_a_participant = Alias::new("user_a_participant");
|
||||
let user_b_participant = Alias::new("user_b_participant");
|
||||
let mut db_contacts = contact::Entity::find()
|
||||
@@ -91,7 +91,7 @@ impl Database {
|
||||
|
||||
/// Returns whether the given user is a busy (on a call).
|
||||
pub async fn is_user_busy(&self, user_id: UserId) -> Result<bool> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let participant = room_participant::Entity::find()
|
||||
.filter(room_participant::Column::UserId.eq(user_id))
|
||||
.one(&*tx)
|
||||
|
||||
@@ -9,7 +9,7 @@ pub enum ContributorSelector {
|
||||
impl Database {
|
||||
/// Retrieves the GitHub logins of all users who have signed the CLA.
|
||||
pub async fn get_contributors(&self) -> Result<Vec<String>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
enum QueryGithubLogin {
|
||||
GithubLogin,
|
||||
@@ -32,7 +32,7 @@ impl Database {
|
||||
&self,
|
||||
selector: &ContributorSelector,
|
||||
) -> Result<Option<DateTime>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let condition = match selector {
|
||||
ContributorSelector::GitHubUserId { github_user_id } => {
|
||||
user::Column::GithubUserId.eq(*github_user_id)
|
||||
@@ -69,7 +69,7 @@ impl Database {
|
||||
github_user_created_at: DateTimeUtc,
|
||||
initial_channel_id: Option<ChannelId>,
|
||||
) -> Result<()> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let user = self
|
||||
.update_or_create_user_by_github_account_tx(
|
||||
github_login,
|
||||
|
||||
@@ -8,7 +8,7 @@ impl Database {
|
||||
model: &str,
|
||||
digests: &[Vec<u8>],
|
||||
) -> Result<HashMap<Vec<u8>, Vec<f32>>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let embeddings = {
|
||||
let mut db_embeddings = embedding::Entity::find()
|
||||
.filter(
|
||||
@@ -52,7 +52,7 @@ impl Database {
|
||||
model: &str,
|
||||
embeddings: &HashMap<Vec<u8>, Vec<f32>>,
|
||||
) -> Result<()> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
embedding::Entity::insert_many(embeddings.iter().map(|(digest, dimensions)| {
|
||||
let now_offset_datetime = OffsetDateTime::now_utc();
|
||||
let retrieved_at =
|
||||
@@ -78,7 +78,7 @@ impl Database {
|
||||
}
|
||||
|
||||
pub async fn purge_old_embeddings(&self) -> Result<()> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
embedding::Entity::delete_many()
|
||||
.filter(
|
||||
embedding::Column::RetrievedAt
|
||||
|
||||
@@ -15,7 +15,7 @@ impl Database {
|
||||
max_schema_version: i32,
|
||||
limit: usize,
|
||||
) -> Result<Vec<ExtensionMetadata>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let mut condition = Condition::all()
|
||||
.add(
|
||||
extension::Column::LatestVersion
|
||||
@@ -43,7 +43,7 @@ impl Database {
|
||||
ids: &[&str],
|
||||
constraints: Option<&ExtensionVersionConstraints>,
|
||||
) -> Result<Vec<ExtensionMetadata>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let extensions = extension::Entity::find()
|
||||
.filter(extension::Column::ExternalId.is_in(ids.iter().copied()))
|
||||
.all(&*tx)
|
||||
@@ -123,7 +123,7 @@ impl Database {
|
||||
&self,
|
||||
extension_id: &str,
|
||||
) -> Result<Vec<ExtensionMetadata>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let condition = extension::Column::ExternalId
|
||||
.eq(extension_id)
|
||||
.into_condition();
|
||||
@@ -162,7 +162,7 @@ impl Database {
|
||||
extension_id: &str,
|
||||
constraints: Option<&ExtensionVersionConstraints>,
|
||||
) -> Result<Option<ExtensionMetadata>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let extension = extension::Entity::find()
|
||||
.filter(extension::Column::ExternalId.eq(extension_id))
|
||||
.one(&*tx)
|
||||
@@ -187,7 +187,7 @@ impl Database {
|
||||
extension_id: &str,
|
||||
version: &str,
|
||||
) -> Result<Option<ExtensionMetadata>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let extension = extension::Entity::find()
|
||||
.filter(extension::Column::ExternalId.eq(extension_id))
|
||||
.filter(extension_version::Column::Version.eq(version))
|
||||
@@ -204,7 +204,7 @@ impl Database {
|
||||
}
|
||||
|
||||
pub async fn get_known_extension_versions(&self) -> Result<HashMap<String, Vec<String>>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let mut extension_external_ids_by_id = HashMap::default();
|
||||
|
||||
let mut rows = extension::Entity::find().stream(&*tx).await?;
|
||||
@@ -242,7 +242,7 @@ impl Database {
|
||||
&self,
|
||||
versions_by_extension_id: &HashMap<&str, Vec<NewExtensionVersion>>,
|
||||
) -> Result<()> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
for (external_id, versions) in versions_by_extension_id {
|
||||
if versions.is_empty() {
|
||||
continue;
|
||||
@@ -349,7 +349,7 @@ impl Database {
|
||||
}
|
||||
|
||||
pub async fn record_extension_download(&self, extension: &str, version: &str) -> Result<bool> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
enum QueryId {
|
||||
Id,
|
||||
|
||||
@@ -13,7 +13,7 @@ impl Database {
|
||||
&self,
|
||||
params: &CreateProcessedStripeEventParams,
|
||||
) -> Result<()> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
processed_stripe_event::Entity::insert(processed_stripe_event::ActiveModel {
|
||||
stripe_event_id: ActiveValue::set(params.stripe_event_id.clone()),
|
||||
stripe_event_type: ActiveValue::set(params.stripe_event_type.clone()),
|
||||
@@ -35,7 +35,7 @@ impl Database {
|
||||
&self,
|
||||
event_id: &str,
|
||||
) -> Result<Option<processed_stripe_event::Model>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(processed_stripe_event::Entity::find_by_id(event_id)
|
||||
.one(&*tx)
|
||||
.await?)
|
||||
@@ -48,7 +48,7 @@ impl Database {
|
||||
&self,
|
||||
event_ids: &[&str],
|
||||
) -> Result<Vec<processed_stripe_event::Model>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(processed_stripe_event::Entity::find()
|
||||
.filter(
|
||||
processed_stripe_event::Column::StripeEventId.is_in(event_ids.iter().copied()),
|
||||
|
||||
@@ -112,7 +112,7 @@ impl Database {
|
||||
}
|
||||
|
||||
pub async fn delete_project(&self, project_id: ProjectId) -> Result<()> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
project::Entity::delete_by_id(project_id).exec(&*tx).await?;
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -80,7 +80,7 @@ impl Database {
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<Option<proto::IncomingCall>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let pending_participant = room_participant::Entity::find()
|
||||
.filter(
|
||||
room_participant::Column::UserId
|
||||
|
||||
@@ -382,7 +382,7 @@ impl Database {
|
||||
|
||||
/// Returns the active flags for the user.
|
||||
pub async fn get_user_flags(&self, user: UserId) -> Result<Vec<String>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
enum QueryAs {
|
||||
Flag,
|
||||
|
||||
@@ -17,11 +17,15 @@ use crate::migrations::run_database_migrations;
|
||||
use super::*;
|
||||
use gpui::BackgroundExecutor;
|
||||
use parking_lot::Mutex;
|
||||
use rand::prelude::*;
|
||||
use sea_orm::ConnectionTrait;
|
||||
use sqlx::migrate::MigrateDatabase;
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicI32, AtomicU32, Ordering::SeqCst},
|
||||
use std::{
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicI32, AtomicU32, Ordering::SeqCst},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub struct TestDb {
|
||||
@@ -41,9 +45,7 @@ impl TestDb {
|
||||
let mut db = runtime.block_on(async {
|
||||
let mut options = ConnectOptions::new(url);
|
||||
options.max_connections(5);
|
||||
let mut db = Database::new(options, Executor::Deterministic(executor.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let mut db = Database::new(options).await.unwrap();
|
||||
let sql = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/migrations.sqlite/20221109000000_test_schema.sql"
|
||||
@@ -60,6 +62,7 @@ impl TestDb {
|
||||
});
|
||||
|
||||
db.test_options = Some(DatabaseTestOptions {
|
||||
executor,
|
||||
runtime,
|
||||
query_failure_probability: parking_lot::Mutex::new(0.0),
|
||||
});
|
||||
@@ -93,9 +96,7 @@ impl TestDb {
|
||||
options
|
||||
.max_connections(5)
|
||||
.idle_timeout(Duration::from_secs(0));
|
||||
let mut db = Database::new(options, Executor::Deterministic(executor.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let mut db = Database::new(options).await.unwrap();
|
||||
let migrations_path = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations");
|
||||
run_database_migrations(db.options(), migrations_path)
|
||||
.await
|
||||
@@ -105,6 +106,7 @@ impl TestDb {
|
||||
});
|
||||
|
||||
db.test_options = Some(DatabaseTestOptions {
|
||||
executor,
|
||||
runtime,
|
||||
query_failure_probability: parking_lot::Mutex::new(0.0),
|
||||
});
|
||||
|
||||
@@ -49,7 +49,7 @@ async fn test_purge_old_embeddings(cx: &mut gpui::TestAppContext) {
|
||||
db.save_embeddings(model, &embeddings).await.unwrap();
|
||||
|
||||
// Reach into the DB and change the retrieved at to be > 60 days
|
||||
db.weak_transaction(|tx| {
|
||||
db.transaction(|tx| {
|
||||
let digest = digest.clone();
|
||||
async move {
|
||||
let sixty_days_ago = OffsetDateTime::now_utc().sub(Duration::days(61));
|
||||
@@ -76,7 +76,10 @@ async fn test_purge_old_embeddings(cx: &mut gpui::TestAppContext) {
|
||||
db.purge_old_embeddings().await.unwrap();
|
||||
|
||||
// Try to retrieve the purged embeddings
|
||||
let retrieved_embeddings = db.get_embeddings(model, &[digest.clone()]).await.unwrap();
|
||||
let retrieved_embeddings = db
|
||||
.get_embeddings(model, std::slice::from_ref(&digest))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
retrieved_embeddings.is_empty(),
|
||||
"Old embeddings should have been purged"
|
||||
|
||||
@@ -285,7 +285,7 @@ impl AppState {
|
||||
pub async fn new(config: Config, executor: Executor) -> Result<Arc<Self>> {
|
||||
let mut db_options = db::ConnectOptions::new(config.database_url.clone());
|
||||
db_options.max_connections(config.database_max_connections);
|
||||
let mut db = Database::new(db_options, Executor::Production).await?;
|
||||
let mut db = Database::new(db_options).await?;
|
||||
db.initialize_notification_kinds().await?;
|
||||
|
||||
let llm_db = if let Some((llm_database_url, llm_database_max_connections)) = config
|
||||
|
||||
@@ -59,7 +59,7 @@ async fn main() -> Result<()> {
|
||||
let config = envy::from_env::<Config>().expect("error loading config");
|
||||
let db_options = db::ConnectOptions::new(config.database_url.clone());
|
||||
|
||||
let mut db = Database::new(db_options, Executor::Production).await?;
|
||||
let mut db = Database::new(db_options).await?;
|
||||
db.initialize_notification_kinds().await?;
|
||||
|
||||
collab::seed::seed(&config, &db, false).await?;
|
||||
@@ -253,7 +253,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
async fn setup_app_database(config: &Config) -> Result<()> {
|
||||
let db_options = db::ConnectOptions::new(config.database_url.clone());
|
||||
let mut db = Database::new(db_options, Executor::Production).await?;
|
||||
let mut db = Database::new(db_options).await?;
|
||||
|
||||
let migrations_path = config.migrations_path.as_deref().unwrap_or_else(|| {
|
||||
#[cfg(feature = "sqlite")]
|
||||
|
||||
@@ -179,7 +179,7 @@ struct Session {
|
||||
}
|
||||
|
||||
impl Session {
|
||||
async fn db(&self) -> tokio::sync::MutexGuard<DbHandle> {
|
||||
async fn db(&self) -> tokio::sync::MutexGuard<'_, DbHandle> {
|
||||
#[cfg(test)]
|
||||
tokio::task::yield_now().await;
|
||||
let guard = self.db.lock().await;
|
||||
@@ -1037,7 +1037,7 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn snapshot(self: &Arc<Self>) -> ServerSnapshot {
|
||||
pub async fn snapshot(self: &Arc<Self>) -> ServerSnapshot<'_> {
|
||||
ServerSnapshot {
|
||||
connection_pool: ConnectionPoolGuard {
|
||||
guard: self.connection_pool.lock(),
|
||||
|
||||
@@ -178,7 +178,7 @@ async fn test_channel_notes_participant_indices(
|
||||
channel_view_a.update_in(cx_a, |notes, window, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.insert("a", window, cx);
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_ranges(vec![0..1]);
|
||||
});
|
||||
});
|
||||
@@ -188,7 +188,7 @@ async fn test_channel_notes_participant_indices(
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.move_down(&Default::default(), window, cx);
|
||||
editor.insert("b", window, cx);
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_ranges(vec![1..2]);
|
||||
});
|
||||
});
|
||||
@@ -198,7 +198,7 @@ async fn test_channel_notes_participant_indices(
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.move_down(&Default::default(), window, cx);
|
||||
editor.insert("c", window, cx);
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_ranges(vec![2..3]);
|
||||
});
|
||||
});
|
||||
@@ -273,12 +273,12 @@ async fn test_channel_notes_participant_indices(
|
||||
.unwrap();
|
||||
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_ranges(vec![0..1]);
|
||||
});
|
||||
});
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_ranges(vec![2..3]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use call::ActiveCall;
|
||||
use editor::{
|
||||
DocumentColorsRenderMode, Editor, EditorSettings, RowInfo,
|
||||
DocumentColorsRenderMode, Editor, EditorSettings, RowInfo, SelectionEffects,
|
||||
actions::{
|
||||
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
|
||||
ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
|
||||
@@ -348,7 +348,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
|
||||
// Type a completion trigger character as the guest.
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.handle_input(".", window, cx);
|
||||
});
|
||||
cx_b.focus(&editor_b);
|
||||
@@ -461,7 +463,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
// Now we do a second completion, this time to ensure that documentation/snippets are
|
||||
// resolved
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([46..46]));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([46..46])
|
||||
});
|
||||
editor.handle_input("; a", window, cx);
|
||||
editor.handle_input(".", window, cx);
|
||||
});
|
||||
@@ -613,7 +617,7 @@ async fn test_collaborating_with_code_actions(
|
||||
|
||||
// Move cursor to a location that contains code actions.
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
|
||||
});
|
||||
});
|
||||
@@ -817,7 +821,9 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
|
||||
// Move cursor to a location that can be renamed.
|
||||
let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([7..7]));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([7..7])
|
||||
});
|
||||
editor.rename(&Rename, window, cx).unwrap()
|
||||
});
|
||||
|
||||
@@ -863,7 +869,9 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
editor.cancel(&editor::actions::Cancel, window, cx);
|
||||
});
|
||||
let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([7..8]));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([7..8])
|
||||
});
|
||||
editor.rename(&Rename, window, cx).unwrap()
|
||||
});
|
||||
|
||||
@@ -1364,7 +1372,9 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
// Type a on type formatting trigger character as the guest.
|
||||
cx_a.focus(&editor_a);
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.handle_input(">", window, cx);
|
||||
});
|
||||
|
||||
@@ -1460,7 +1470,9 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
// Type a on type formatting trigger character as the guest.
|
||||
cx_b.focus(&editor_b);
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.handle_input(":", window, cx);
|
||||
});
|
||||
|
||||
@@ -1697,7 +1709,9 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
|
||||
let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13].clone())
|
||||
});
|
||||
editor.handle_input(":", window, cx);
|
||||
});
|
||||
cx_b.focus(&editor_b);
|
||||
@@ -1718,7 +1732,9 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
|
||||
let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.handle_input("a change to increment both buffers' versions", window, cx);
|
||||
});
|
||||
cx_a.focus(&editor_a);
|
||||
@@ -2121,7 +2137,9 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
|
||||
});
|
||||
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13].clone())
|
||||
});
|
||||
editor.handle_input(":", window, cx);
|
||||
});
|
||||
color_request_handle.next().await.unwrap();
|
||||
|
||||
@@ -6,7 +6,7 @@ use collab_ui::{
|
||||
channel_view::ChannelView,
|
||||
notifications::project_shared_notification::ProjectSharedNotification,
|
||||
};
|
||||
use editor::{Editor, MultiBuffer, PathKey};
|
||||
use editor::{Editor, MultiBuffer, PathKey, SelectionEffects};
|
||||
use gpui::{
|
||||
AppContext as _, BackgroundExecutor, BorrowAppContext, Entity, SharedString, TestAppContext,
|
||||
VisualContext, VisualTestContext, point,
|
||||
@@ -376,7 +376,9 @@ async fn test_basic_following(
|
||||
|
||||
// Changes to client A's editor are reflected on client B.
|
||||
editor_a1.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2]));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([1..1, 2..2])
|
||||
});
|
||||
});
|
||||
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
executor.run_until_parked();
|
||||
@@ -393,7 +395,9 @@ async fn test_basic_following(
|
||||
editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
|
||||
|
||||
editor_a1.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([3..3])
|
||||
});
|
||||
editor.set_scroll_position(point(0., 100.), window, cx);
|
||||
});
|
||||
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
@@ -1647,7 +1651,9 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
|
||||
// b should follow a to position 1
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]))
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([1..1])
|
||||
})
|
||||
});
|
||||
cx_a.executor()
|
||||
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
@@ -1667,7 +1673,9 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
|
||||
// b should not follow a to position 2
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]))
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([2..2])
|
||||
})
|
||||
});
|
||||
cx_a.executor()
|
||||
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
@@ -1968,7 +1976,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||
assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.insert("Hello from A.", window, cx);
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.select_ranges(vec![3..4]);
|
||||
});
|
||||
});
|
||||
@@ -2109,7 +2117,7 @@ async fn test_following_after_replacement(cx_a: &mut TestAppContext, cx_b: &mut
|
||||
workspace.add_item_to_center(Box::new(editor.clone()) as _, window, cx)
|
||||
});
|
||||
editor.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([Point::row_range(4..4)]);
|
||||
})
|
||||
});
|
||||
|
||||
@@ -4591,14 +4591,13 @@ async fn test_formatting_buffer(
|
||||
cx_a.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
|
||||
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
|
||||
vec![Formatter::External {
|
||||
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
|
||||
Formatter::External {
|
||||
command: "awk".into(),
|
||||
arguments: Some(
|
||||
vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
|
||||
),
|
||||
}]
|
||||
.into(),
|
||||
},
|
||||
)));
|
||||
});
|
||||
});
|
||||
@@ -4699,8 +4698,8 @@ async fn test_prettier_formatting_buffer(
|
||||
cx_b.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
|
||||
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
|
||||
vec![Formatter::LanguageServer { name: None }].into(),
|
||||
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
|
||||
Formatter::LanguageServer { name: None },
|
||||
)));
|
||||
file.defaults.prettier = Some(PrettierSettings {
|
||||
allowed: true,
|
||||
@@ -4822,7 +4821,7 @@ async fn test_definition(
|
||||
);
|
||||
|
||||
let definitions_1 = project_b
|
||||
.update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
|
||||
.update(cx_b, |p, cx| p.definitions(&buffer_b, 23, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx_b.read(|cx| {
|
||||
@@ -4853,7 +4852,7 @@ async fn test_definition(
|
||||
);
|
||||
|
||||
let definitions_2 = project_b
|
||||
.update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
|
||||
.update(cx_b, |p, cx| p.definitions(&buffer_b, 33, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx_b.read(|cx| {
|
||||
@@ -4890,7 +4889,7 @@ async fn test_definition(
|
||||
);
|
||||
|
||||
let type_definitions = project_b
|
||||
.update(cx_b, |p, cx| p.type_definition(&buffer_b, 7, cx))
|
||||
.update(cx_b, |p, cx| p.type_definitions(&buffer_b, 7, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx_b.read(|cx| {
|
||||
@@ -5058,7 +5057,7 @@ async fn test_references(
|
||||
lsp_response_tx
|
||||
.unbounded_send(Err(anyhow!("can't find references")))
|
||||
.unwrap();
|
||||
references.await.unwrap_err();
|
||||
assert_eq!(references.await.unwrap(), []);
|
||||
|
||||
// User is informed that the request is no longer pending.
|
||||
executor.run_until_parked();
|
||||
@@ -5642,7 +5641,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
||||
let definitions;
|
||||
let buffer_b2;
|
||||
if rng.r#gen() {
|
||||
definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
|
||||
definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx));
|
||||
(buffer_b2, _) = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
|
||||
@@ -5656,7 +5655,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
|
||||
definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx));
|
||||
}
|
||||
|
||||
let definitions = definitions.await.unwrap();
|
||||
|
||||
@@ -838,7 +838,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
.map(|_| Ok(()))
|
||||
.boxed(),
|
||||
LspRequestKind::Definition => project
|
||||
.definition(&buffer, offset, cx)
|
||||
.definitions(&buffer, offset, cx)
|
||||
.map_ok(|_| ())
|
||||
.boxed(),
|
||||
LspRequestKind::Highlights => project
|
||||
|
||||
@@ -505,8 +505,8 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
||||
cx_b.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
|
||||
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
|
||||
vec![Formatter::LanguageServer { name: None }].into(),
|
||||
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
|
||||
Formatter::LanguageServer { name: None },
|
||||
)));
|
||||
file.defaults.prettier = Some(PrettierSettings {
|
||||
allowed: true,
|
||||
|
||||
@@ -7,8 +7,8 @@ use client::{
|
||||
};
|
||||
use collections::HashMap;
|
||||
use editor::{
|
||||
CollaborationHub, DisplayPoint, Editor, EditorEvent, display_map::ToDisplayPoint,
|
||||
scroll::Autoscroll,
|
||||
CollaborationHub, DisplayPoint, Editor, EditorEvent, SelectionEffects,
|
||||
display_map::ToDisplayPoint, scroll::Autoscroll,
|
||||
};
|
||||
use gpui::{
|
||||
AnyView, App, ClipboardItem, Context, Entity, EventEmitter, Focusable, Pixels, Point, Render,
|
||||
@@ -260,9 +260,16 @@ impl ChannelView {
|
||||
.find(|item| &Channel::slug(&item.text).to_lowercase() == &position)
|
||||
{
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| {
|
||||
s.replace_cursors_with(|map| vec![item.range.start.to_display_point(map)])
|
||||
})
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::focused()),
|
||||
window,
|
||||
cx,
|
||||
|s| {
|
||||
s.replace_cursors_with(|map| {
|
||||
vec![item.range.start.to_display_point(map)]
|
||||
})
|
||||
},
|
||||
)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ pub struct ChatPanelSettings {
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct ChatPanelSettingsContent {
|
||||
/// When to show the panel button in the status bar.
|
||||
///
|
||||
@@ -52,7 +51,6 @@ pub struct NotificationPanelSettings {
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct PanelSettingsContent {
|
||||
/// Whether to show the panel button in the status bar.
|
||||
///
|
||||
@@ -69,7 +67,6 @@ pub struct PanelSettingsContent {
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct MessageEditorSettings {
|
||||
/// Whether to automatically replace emoji shortcodes with emoji characters.
|
||||
/// For example: typing `:wave:` gets replaced with `👋`.
|
||||
|
||||
@@ -41,7 +41,7 @@ pub struct CommandPalette {
|
||||
/// Removes subsequent whitespace characters and double colons from the query.
|
||||
///
|
||||
/// This improves the likelihood of a match by either humanized name or keymap-style name.
|
||||
fn normalize_query(input: &str) -> String {
|
||||
pub fn normalize_action_query(input: &str) -> String {
|
||||
let mut result = String::with_capacity(input.len());
|
||||
let mut last_char = None;
|
||||
|
||||
@@ -297,7 +297,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
let mut commands = self.all_commands.clone();
|
||||
let hit_counts = self.hit_counts();
|
||||
let executor = cx.background_executor().clone();
|
||||
let query = normalize_query(query.as_str());
|
||||
let query = normalize_action_query(query.as_str());
|
||||
async move {
|
||||
commands.sort_by_key(|action| {
|
||||
(
|
||||
@@ -311,29 +311,17 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
.enumerate()
|
||||
.map(|(ix, command)| StringMatchCandidate::new(ix, &command.name))
|
||||
.collect::<Vec<_>>();
|
||||
let matches = if query.is_empty() {
|
||||
candidates
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, candidate)| StringMatch {
|
||||
candidate_id: index,
|
||||
string: candidate.string,
|
||||
positions: Vec::new(),
|
||||
score: 0.0,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
fuzzy::match_strings(
|
||||
&candidates,
|
||||
&query,
|
||||
true,
|
||||
true,
|
||||
10000,
|
||||
&Default::default(),
|
||||
executor,
|
||||
)
|
||||
.await
|
||||
};
|
||||
|
||||
let matches = fuzzy::match_strings(
|
||||
&candidates,
|
||||
&query,
|
||||
true,
|
||||
true,
|
||||
10000,
|
||||
&Default::default(),
|
||||
executor,
|
||||
)
|
||||
.await;
|
||||
|
||||
tx.send((commands, matches)).await.log_err();
|
||||
}
|
||||
@@ -422,8 +410,8 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let r#match = self.matches.get(ix)?;
|
||||
let command = self.commands.get(r#match.candidate_id)?;
|
||||
let matching_command = self.matches.get(ix)?;
|
||||
let command = self.commands.get(matching_command.candidate_id)?;
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
@@ -436,7 +424,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
.justify_between()
|
||||
.child(HighlightedLabel::new(
|
||||
command.name.clone(),
|
||||
r#match.positions.clone(),
|
||||
matching_command.positions.clone(),
|
||||
))
|
||||
.children(KeyBinding::for_action_in(
|
||||
&*command.action,
|
||||
@@ -512,19 +500,28 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_normalize_query() {
|
||||
assert_eq!(normalize_query("editor: backspace"), "editor: backspace");
|
||||
assert_eq!(normalize_query("editor: backspace"), "editor: backspace");
|
||||
assert_eq!(normalize_query("editor: backspace"), "editor: backspace");
|
||||
assert_eq!(
|
||||
normalize_query("editor::GoToDefinition"),
|
||||
normalize_action_query("editor: backspace"),
|
||||
"editor: backspace"
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_action_query("editor: backspace"),
|
||||
"editor: backspace"
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_action_query("editor: backspace"),
|
||||
"editor: backspace"
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_action_query("editor::GoToDefinition"),
|
||||
"editor:GoToDefinition"
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_query("editor::::GoToDefinition"),
|
||||
normalize_action_query("editor::::GoToDefinition"),
|
||||
"editor:GoToDefinition"
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_query("editor: :GoToDefinition"),
|
||||
normalize_action_query("editor: :GoToDefinition"),
|
||||
"editor: :GoToDefinition"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ impl Display for ContextServerId {
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)]
|
||||
pub struct ContextServerCommand {
|
||||
#[serde(rename = "command")]
|
||||
pub path: String,
|
||||
pub args: Vec<String>,
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
|
||||
@@ -698,16 +698,16 @@ async fn stream_completion(
|
||||
completion_url: Arc<str>,
|
||||
request: Request,
|
||||
) -> Result<BoxStream<'static, Result<ResponseEvent>>> {
|
||||
let is_vision_request = request.messages.last().map_or(false, |message| match message {
|
||||
ChatMessage::User { content }
|
||||
| ChatMessage::Assistant { content, .. }
|
||||
| ChatMessage::Tool { content, .. } => {
|
||||
matches!(content, ChatMessageContent::Multipart(parts) if parts.iter().any(|part| matches!(part, ChatMessagePart::Image { .. })))
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
let is_vision_request = request.messages.iter().any(|message| match message {
|
||||
ChatMessage::User { content }
|
||||
| ChatMessage::Assistant { content, .. }
|
||||
| ChatMessage::Tool { content, .. } => {
|
||||
matches!(content, ChatMessageContent::Multipart(parts) if parts.iter().any(|part| matches!(part, ChatMessagePart::Image { .. })))
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
|
||||
let request_builder = HttpRequest::builder()
|
||||
let mut request_builder = HttpRequest::builder()
|
||||
.method(Method::POST)
|
||||
.uri(completion_url.as_ref())
|
||||
.header(
|
||||
@@ -719,8 +719,12 @@ async fn stream_completion(
|
||||
)
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Copilot-Integration-Id", "vscode-chat")
|
||||
.header("Copilot-Vision-Request", is_vision_request.to_string());
|
||||
.header("Copilot-Integration-Id", "vscode-chat");
|
||||
|
||||
if is_vision_request {
|
||||
request_builder =
|
||||
request_builder.header("Copilot-Vision-Request", is_vision_request.to_string());
|
||||
}
|
||||
|
||||
let is_streaming = request.stream;
|
||||
|
||||
|
||||
@@ -264,7 +264,8 @@ fn common_prefix<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(a: T1, b:
|
||||
mod tests {
|
||||
use super::*;
|
||||
use editor::{
|
||||
Editor, ExcerptRange, MultiBuffer, test::editor_lsp_test_context::EditorLspTestContext,
|
||||
Editor, ExcerptRange, MultiBuffer, SelectionEffects,
|
||||
test::editor_lsp_test_context::EditorLspTestContext,
|
||||
};
|
||||
use fs::FakeFs;
|
||||
use futures::StreamExt;
|
||||
@@ -478,7 +479,7 @@ mod tests {
|
||||
// Reset the editor to verify how suggestions behave when tabbing on leading indentation.
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.set_text("fn foo() {\n \n}", window, cx);
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
|
||||
});
|
||||
});
|
||||
@@ -767,7 +768,7 @@ mod tests {
|
||||
);
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
// Ensure copilot suggestions are shown for the first excerpt.
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
|
||||
});
|
||||
editor.next_edit_prediction(&Default::default(), window, cx);
|
||||
@@ -793,7 +794,7 @@ mod tests {
|
||||
);
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
// Move to another excerpt, ensuring the suggestion gets cleared.
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
|
||||
});
|
||||
assert!(!editor.has_active_inline_completion());
|
||||
@@ -1019,7 +1020,7 @@ mod tests {
|
||||
);
|
||||
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
|
||||
});
|
||||
editor.refresh_inline_completion(true, false, window, cx);
|
||||
@@ -1029,7 +1030,7 @@ mod tests {
|
||||
assert!(copilot_requests.try_next().is_err());
|
||||
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([Point::new(5, 0)..Point::new(5, 0)])
|
||||
});
|
||||
editor.refresh_inline_completion(true, false, window, cx);
|
||||
|
||||
@@ -10,6 +10,7 @@ use gpui::{AsyncApp, SharedString};
|
||||
pub use http_client::{HttpClient, github::latest_github_release};
|
||||
use language::{LanguageName, LanguageToolchainStore};
|
||||
use node_runtime::NodeRuntime;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::WorktreeId;
|
||||
use smol::fs::File;
|
||||
@@ -47,7 +48,10 @@ pub trait DapDelegate: Send + Sync + 'static {
|
||||
async fn shell_env(&self) -> collections::HashMap<String, String>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, JsonSchema,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
pub struct DebugAdapterName(pub SharedString);
|
||||
|
||||
impl Deref for DebugAdapterName {
|
||||
|
||||
@@ -25,7 +25,9 @@ anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
collections.workspace = true
|
||||
dap.workspace = true
|
||||
dotenvy.workspace = true
|
||||
futures.workspace = true
|
||||
fs.workspace = true
|
||||
gpui.workspace = true
|
||||
json_dotpath.workspace = true
|
||||
language.workspace = true
|
||||
@@ -33,6 +35,7 @@ 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
|
||||
|
||||
@@ -22,17 +22,16 @@ impl CodeLldbDebugAdapter {
|
||||
async fn request_args(
|
||||
&self,
|
||||
delegate: &Arc<dyn DapDelegate>,
|
||||
task_definition: &DebugTaskDefinition,
|
||||
mut configuration: Value,
|
||||
label: &str,
|
||||
) -> Result<dap::StartDebuggingRequestArguments> {
|
||||
// CodeLLDB uses `name` for a terminal label.
|
||||
let mut configuration = task_definition.config.clone();
|
||||
|
||||
let obj = configuration
|
||||
.as_object_mut()
|
||||
.context("CodeLLDB is not a valid json object")?;
|
||||
|
||||
// CodeLLDB uses `name` for a terminal label.
|
||||
obj.entry("name")
|
||||
.or_insert(Value::String(String::from(task_definition.label.as_ref())));
|
||||
.or_insert(Value::String(String::from(label)));
|
||||
|
||||
obj.entry("cwd")
|
||||
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
|
||||
@@ -361,17 +360,31 @@ impl DebugAdapter for CodeLldbDebugAdapter {
|
||||
self.path_to_codelldb.set(path.clone()).ok();
|
||||
command = Some(path);
|
||||
};
|
||||
|
||||
let mut json_config = config.config.clone();
|
||||
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(),
|
||||
]
|
||||
if let Some(config) = json_config.as_object_mut()
|
||||
&& let Some(source_languages) = config.get("sourceLanguages").filter(|value| {
|
||||
value
|
||||
.as_array()
|
||||
.map_or(false, |array| array.iter().all(Value::is_string))
|
||||
})
|
||||
{
|
||||
let ret = vec![
|
||||
"--settings".into(),
|
||||
json!({"sourceLanguages": source_languages}).to_string(),
|
||||
];
|
||||
config.remove("sourceLanguages");
|
||||
ret
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}),
|
||||
request_args: self.request_args(delegate, &config).await?,
|
||||
request_args: self
|
||||
.request_args(delegate, json_config, &config.label)
|
||||
.await?,
|
||||
envs: HashMap::default(),
|
||||
connection: None,
|
||||
})
|
||||
|
||||
@@ -4,7 +4,6 @@ mod go;
|
||||
mod javascript;
|
||||
mod php;
|
||||
mod python;
|
||||
mod ruby;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -25,7 +24,6 @@ use gpui::{App, BorrowAppContext};
|
||||
use javascript::JsDebugAdapter;
|
||||
use php::PhpDebugAdapter;
|
||||
use python::PythonDebugAdapter;
|
||||
use ruby::RubyDebugAdapter;
|
||||
use serde_json::json;
|
||||
use task::{DebugScenario, ZedDebugConfig};
|
||||
|
||||
@@ -35,7 +33,6 @@ pub fn init(cx: &mut App) {
|
||||
registry.add_adapter(Arc::from(PythonDebugAdapter::default()));
|
||||
registry.add_adapter(Arc::from(PhpDebugAdapter::default()));
|
||||
registry.add_adapter(Arc::from(JsDebugAdapter::default()));
|
||||
registry.add_adapter(Arc::from(RubyDebugAdapter));
|
||||
registry.add_adapter(Arc::from(GoDebugAdapter::default()));
|
||||
registry.add_adapter(Arc::from(GdbDebugAdapter));
|
||||
|
||||
|
||||
@@ -7,13 +7,22 @@ use dap::{
|
||||
latest_github_release,
|
||||
},
|
||||
};
|
||||
|
||||
use fs::Fs;
|
||||
use gpui::{AsyncApp, SharedString};
|
||||
use language::LanguageName;
|
||||
use std::{env::consts, ffi::OsStr, path::PathBuf, sync::OnceLock};
|
||||
use log::warn;
|
||||
use serde_json::{Map, Value};
|
||||
use task::TcpArgumentsTemplate;
|
||||
use util;
|
||||
|
||||
use std::{
|
||||
env::consts,
|
||||
ffi::OsStr,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
use crate::*;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@@ -437,22 +446,34 @@ impl DebugAdapter for GoDebugAdapter {
|
||||
adapter_path.join("dlv").to_string_lossy().to_string()
|
||||
};
|
||||
|
||||
let cwd = task_definition
|
||||
.config
|
||||
.get("cwd")
|
||||
.and_then(|s| s.as_str())
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| delegate.worktree_root_path().to_path_buf());
|
||||
let cwd = Some(
|
||||
task_definition
|
||||
.config
|
||||
.get("cwd")
|
||||
.and_then(|s| s.as_str())
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| delegate.worktree_root_path().to_path_buf()),
|
||||
);
|
||||
|
||||
let arguments;
|
||||
let command;
|
||||
let connection;
|
||||
|
||||
let mut configuration = task_definition.config.clone();
|
||||
let mut envs = HashMap::default();
|
||||
|
||||
if let Some(configuration) = configuration.as_object_mut() {
|
||||
configuration
|
||||
.entry("cwd")
|
||||
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
|
||||
|
||||
handle_envs(
|
||||
configuration,
|
||||
&mut envs,
|
||||
cwd.as_deref(),
|
||||
delegate.fs().clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
if let Some(connection_options) = &task_definition.tcp_connection {
|
||||
@@ -494,8 +515,8 @@ impl DebugAdapter for GoDebugAdapter {
|
||||
Ok(DebugAdapterBinary {
|
||||
command,
|
||||
arguments,
|
||||
cwd: Some(cwd),
|
||||
envs: HashMap::default(),
|
||||
cwd,
|
||||
envs,
|
||||
connection,
|
||||
request_args: StartDebuggingRequestArguments {
|
||||
configuration,
|
||||
@@ -504,3 +525,44 @@ impl DebugAdapter for GoDebugAdapter {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// delve doesn't do anything with the envFile setting, so we intercept it
|
||||
async fn handle_envs(
|
||||
config: &mut Map<String, Value>,
|
||||
envs: &mut HashMap<String, String>,
|
||||
cwd: Option<&Path>,
|
||||
fs: Arc<dyn Fs>,
|
||||
) -> Option<()> {
|
||||
let env_files = match config.get("envFile")? {
|
||||
Value::Array(arr) => arr.iter().map(|v| v.as_str()).collect::<Vec<_>>(),
|
||||
Value::String(s) => vec![Some(s.as_str())],
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let rebase_path = |path: PathBuf| {
|
||||
if path.is_absolute() {
|
||||
Some(path)
|
||||
} else {
|
||||
cwd.map(|p| p.join(path))
|
||||
}
|
||||
};
|
||||
|
||||
for path in env_files {
|
||||
let Some(path) = path
|
||||
.and_then(|s| PathBuf::from_str(s).ok())
|
||||
.and_then(rebase_path)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Ok(file) = fs.open_sync(&path).await {
|
||||
envs.extend(dotenvy::from_read_iter(file).filter_map(Result::ok))
|
||||
} else {
|
||||
warn!("While starting Go debug session: failed to read env file {path:?}");
|
||||
};
|
||||
}
|
||||
|
||||
// remove envFile now that it's been handled
|
||||
config.remove("entry");
|
||||
Some(())
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
use util::{ResultExt, maybe};
|
||||
|
||||
use crate::*;
|
||||
|
||||
@@ -72,6 +72,24 @@ 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()
|
||||
@@ -96,7 +114,6 @@ impl JsDebugAdapter {
|
||||
.entry("cwd")
|
||||
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
|
||||
|
||||
configuration.entry("type").and_modify(normalize_task_type);
|
||||
configuration
|
||||
.entry("console")
|
||||
.or_insert("externalTerminal".into());
|
||||
@@ -265,6 +282,10 @@ impl DebugAdapter for JsDebugAdapter {
|
||||
"description": "Automatically stop program after launch",
|
||||
"default": false
|
||||
},
|
||||
"attachSimplePort": {
|
||||
"type": "number",
|
||||
"description": "If set, attaches to the process via the given port. This is generally no longer necessary for Node.js programs and loses the ability to debug child processes, but can be useful in more esoteric scenarios such as with Deno and Docker launches. If set to 0, a random port will be chosen and --inspect-brk added to the launch arguments automatically."
|
||||
},
|
||||
"runtimeExecutable": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Runtime to use, an absolute path or the name of a runtime available on PATH",
|
||||
@@ -501,7 +522,11 @@ impl DebugAdapter for JsDebugAdapter {
|
||||
}
|
||||
|
||||
fn label_for_child_session(&self, args: &StartDebuggingRequestArguments) -> Option<String> {
|
||||
let label = args.configuration.get("name")?.as_str()?;
|
||||
let label = args
|
||||
.configuration
|
||||
.get("name")?
|
||||
.as_str()
|
||||
.filter(|name| !name.is_empty())?;
|
||||
Some(label.to_owned())
|
||||
}
|
||||
}
|
||||
@@ -512,7 +537,7 @@ fn normalize_task_type(task_type: &mut Value) {
|
||||
};
|
||||
|
||||
let new_name = match task_type_str {
|
||||
"node" | "pwa-node" => "pwa-node",
|
||||
"node" | "pwa-node" | "node-terminal" => "pwa-node",
|
||||
"chrome" | "pwa-chrome" => "pwa-chrome",
|
||||
"edge" | "msedge" | "pwa-edge" | "pwa-msedge" => "pwa-msedge",
|
||||
_ => task_type_str,
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
use anyhow::{Result, bail};
|
||||
use async_trait::async_trait;
|
||||
use collections::FxHashMap;
|
||||
use dap::{
|
||||
DebugRequest, StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
|
||||
adapters::{
|
||||
DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition,
|
||||
},
|
||||
};
|
||||
use gpui::{AsyncApp, SharedString};
|
||||
use language::LanguageName;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::path::PathBuf;
|
||||
use std::{ffi::OsStr, sync::Arc};
|
||||
use task::{DebugScenario, ZedDebugConfig};
|
||||
use util::command::new_smol_command;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct RubyDebugAdapter;
|
||||
|
||||
impl RubyDebugAdapter {
|
||||
const ADAPTER_NAME: &'static str = "Ruby";
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RubyDebugConfig {
|
||||
script_or_command: Option<String>,
|
||||
script: Option<String>,
|
||||
command: Option<String>,
|
||||
#[serde(default)]
|
||||
args: Vec<String>,
|
||||
#[serde(default)]
|
||||
env: FxHashMap<String, String>,
|
||||
cwd: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl DebugAdapter for RubyDebugAdapter {
|
||||
fn name(&self) -> DebugAdapterName {
|
||||
DebugAdapterName(Self::ADAPTER_NAME.into())
|
||||
}
|
||||
|
||||
fn adapter_language_name(&self) -> Option<LanguageName> {
|
||||
Some(SharedString::new_static("Ruby").into())
|
||||
}
|
||||
|
||||
async fn request_kind(
|
||||
&self,
|
||||
_: &serde_json::Value,
|
||||
) -> Result<StartDebuggingRequestArgumentsRequest> {
|
||||
Ok(StartDebuggingRequestArgumentsRequest::Launch)
|
||||
}
|
||||
|
||||
fn dap_schema(&self) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "Command name (ruby, rake, bin/rails, bundle exec ruby, etc)",
|
||||
},
|
||||
"script": {
|
||||
"type": "string",
|
||||
"description": "Absolute path to a Ruby file."
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string",
|
||||
"description": "Directory to execute the program in",
|
||||
"default": "${ZED_WORKTREE_ROOT}"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"description": "Command line arguments passed to the program",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"description": "Additional environment variables to pass to the debugging (and debugged) process",
|
||||
"default": {}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
|
||||
match zed_scenario.request {
|
||||
DebugRequest::Launch(launch) => {
|
||||
let config = RubyDebugConfig {
|
||||
script_or_command: Some(launch.program),
|
||||
script: None,
|
||||
command: None,
|
||||
args: launch.args,
|
||||
env: launch.env,
|
||||
cwd: launch.cwd.clone(),
|
||||
};
|
||||
|
||||
let config = serde_json::to_value(config)?;
|
||||
|
||||
Ok(DebugScenario {
|
||||
adapter: zed_scenario.adapter,
|
||||
label: zed_scenario.label,
|
||||
config,
|
||||
tcp_connection: None,
|
||||
build: None,
|
||||
})
|
||||
}
|
||||
DebugRequest::Attach(_) => {
|
||||
anyhow::bail!("Attach requests are unsupported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_binary(
|
||||
&self,
|
||||
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());
|
||||
let mut rdbg_path = adapter_path.join("rdbg");
|
||||
if !delegate.fs().is_file(&rdbg_path).await {
|
||||
match delegate.which("rdbg".as_ref()).await {
|
||||
Some(path) => rdbg_path = path,
|
||||
None => {
|
||||
delegate.output_to_console(
|
||||
"rdbg not found on path, trying `gem install debug`".to_string(),
|
||||
);
|
||||
let output = new_smol_command("gem")
|
||||
.arg("install")
|
||||
.arg("--no-document")
|
||||
.arg("--bindir")
|
||||
.arg(adapter_path)
|
||||
.arg("debug")
|
||||
.output()
|
||||
.await?;
|
||||
anyhow::ensure!(
|
||||
output.status.success(),
|
||||
"Failed to install rdbg:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr).to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let tcp_connection = definition.tcp_connection.clone().unwrap_or_default();
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
let ruby_config = serde_json::from_value::<RubyDebugConfig>(definition.config.clone())?;
|
||||
|
||||
let mut arguments = vec![
|
||||
"--open".to_string(),
|
||||
format!("--port={}", port),
|
||||
format!("--host={}", host),
|
||||
];
|
||||
|
||||
if let Some(script) = &ruby_config.script {
|
||||
arguments.push(script.clone());
|
||||
} else if let Some(command) = &ruby_config.command {
|
||||
arguments.push("--command".to_string());
|
||||
arguments.push(command.clone());
|
||||
} else if let Some(command_or_script) = &ruby_config.script_or_command {
|
||||
if delegate
|
||||
.which(OsStr::new(&command_or_script))
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
arguments.push("--command".to_string());
|
||||
}
|
||||
arguments.push(command_or_script.clone());
|
||||
} else {
|
||||
bail!("Ruby debug config must have 'script' or 'command' args");
|
||||
}
|
||||
|
||||
arguments.extend(ruby_config.args);
|
||||
|
||||
let mut configuration = definition.config.clone();
|
||||
if let Some(configuration) = configuration.as_object_mut() {
|
||||
configuration
|
||||
.entry("cwd")
|
||||
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
|
||||
}
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: Some(rdbg_path.to_string_lossy().to_string()),
|
||||
arguments,
|
||||
connection: Some(dap::adapters::TcpArguments {
|
||||
host,
|
||||
port,
|
||||
timeout,
|
||||
}),
|
||||
cwd: Some(
|
||||
ruby_config
|
||||
.cwd
|
||||
.unwrap_or(delegate.worktree_root_path().to_owned()),
|
||||
),
|
||||
envs: ruby_config.env.into_iter().collect(),
|
||||
request_args: StartDebuggingRequestArguments {
|
||||
request: self.request_kind(&definition.config).await?,
|
||||
configuration,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ use project::{
|
||||
use settings::Settings as _;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{HashMap, VecDeque},
|
||||
collections::{BTreeMap, HashMap, VecDeque},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::maybe;
|
||||
@@ -32,13 +32,6 @@ use workspace::{
|
||||
ui::{Button, Clickable, ContextMenu, Label, LabelCommon, PopoverMenu, h_flex},
|
||||
};
|
||||
|
||||
// TODO:
|
||||
// - [x] stop sorting by session ID
|
||||
// - [x] pick the most recent session by default (logs if available, RPC messages otherwise)
|
||||
// - [ ] dump the launch/attach request somewhere (logs?)
|
||||
|
||||
const MAX_SESSIONS: usize = 10;
|
||||
|
||||
struct DapLogView {
|
||||
editor: Entity<Editor>,
|
||||
focus_handle: FocusHandle,
|
||||
@@ -49,14 +42,34 @@ struct DapLogView {
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
struct LogStoreEntryIdentifier<'a> {
|
||||
session_id: SessionId,
|
||||
project: Cow<'a, WeakEntity<Project>>,
|
||||
}
|
||||
impl LogStoreEntryIdentifier<'_> {
|
||||
fn to_owned(&self) -> LogStoreEntryIdentifier<'static> {
|
||||
LogStoreEntryIdentifier {
|
||||
session_id: self.session_id,
|
||||
project: Cow::Owned(self.project.as_ref().clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LogStoreMessage {
|
||||
id: LogStoreEntryIdentifier<'static>,
|
||||
kind: IoKind,
|
||||
command: Option<SharedString>,
|
||||
message: SharedString,
|
||||
}
|
||||
|
||||
pub struct LogStore {
|
||||
projects: HashMap<WeakEntity<Project>, ProjectState>,
|
||||
debug_sessions: VecDeque<DebugAdapterState>,
|
||||
rpc_tx: UnboundedSender<(SessionId, IoKind, Option<SharedString>, SharedString)>,
|
||||
adapter_log_tx: UnboundedSender<(SessionId, IoKind, Option<SharedString>, SharedString)>,
|
||||
rpc_tx: UnboundedSender<LogStoreMessage>,
|
||||
adapter_log_tx: UnboundedSender<LogStoreMessage>,
|
||||
}
|
||||
|
||||
struct ProjectState {
|
||||
debug_sessions: BTreeMap<SessionId, DebugAdapterState>,
|
||||
_subscriptions: [gpui::Subscription; 2],
|
||||
}
|
||||
|
||||
@@ -122,13 +135,12 @@ impl DebugAdapterState {
|
||||
|
||||
impl LogStore {
|
||||
pub fn new(cx: &Context<Self>) -> Self {
|
||||
let (rpc_tx, mut rpc_rx) =
|
||||
unbounded::<(SessionId, IoKind, Option<SharedString>, SharedString)>();
|
||||
let (rpc_tx, mut rpc_rx) = unbounded::<LogStoreMessage>();
|
||||
cx.spawn(async move |this, cx| {
|
||||
while let Some((session_id, io_kind, command, message)) = rpc_rx.next().await {
|
||||
while let Some(message) = rpc_rx.next().await {
|
||||
if let Some(this) = this.upgrade() {
|
||||
this.update(cx, |this, cx| {
|
||||
this.add_debug_adapter_message(session_id, io_kind, command, message, cx);
|
||||
this.add_debug_adapter_message(message, cx);
|
||||
})?;
|
||||
}
|
||||
|
||||
@@ -138,13 +150,12 @@ impl LogStore {
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
let (adapter_log_tx, mut adapter_log_rx) =
|
||||
unbounded::<(SessionId, IoKind, Option<SharedString>, SharedString)>();
|
||||
let (adapter_log_tx, mut adapter_log_rx) = unbounded::<LogStoreMessage>();
|
||||
cx.spawn(async move |this, cx| {
|
||||
while let Some((session_id, io_kind, _, message)) = adapter_log_rx.next().await {
|
||||
while let Some(message) = adapter_log_rx.next().await {
|
||||
if let Some(this) = this.upgrade() {
|
||||
this.update(cx, |this, cx| {
|
||||
this.add_debug_adapter_log(session_id, io_kind, message, cx);
|
||||
this.add_debug_adapter_log(message, cx);
|
||||
})?;
|
||||
}
|
||||
|
||||
@@ -157,57 +168,76 @@ impl LogStore {
|
||||
rpc_tx,
|
||||
adapter_log_tx,
|
||||
projects: HashMap::new(),
|
||||
debug_sessions: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) {
|
||||
let weak_project = project.downgrade();
|
||||
self.projects.insert(
|
||||
project.downgrade(),
|
||||
ProjectState {
|
||||
_subscriptions: [
|
||||
cx.observe_release(project, move |this, _, _| {
|
||||
this.projects.remove(&weak_project);
|
||||
cx.observe_release(project, {
|
||||
let weak_project = project.downgrade();
|
||||
move |this, _, _| {
|
||||
this.projects.remove(&weak_project);
|
||||
}
|
||||
}),
|
||||
cx.subscribe(
|
||||
&project.read(cx).dap_store(),
|
||||
|this, dap_store, event, cx| match event {
|
||||
cx.subscribe(&project.read(cx).dap_store(), {
|
||||
let weak_project = project.downgrade();
|
||||
move |this, dap_store, event, cx| match event {
|
||||
dap_store::DapStoreEvent::DebugClientStarted(session_id) => {
|
||||
let session = dap_store.read(cx).session_by_id(session_id);
|
||||
if let Some(session) = session {
|
||||
this.add_debug_session(*session_id, session, cx);
|
||||
this.add_debug_session(
|
||||
LogStoreEntryIdentifier {
|
||||
project: Cow::Owned(weak_project.clone()),
|
||||
session_id: *session_id,
|
||||
},
|
||||
session,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
dap_store::DapStoreEvent::DebugClientShutdown(session_id) => {
|
||||
this.get_debug_adapter_state(*session_id)
|
||||
.iter_mut()
|
||||
.for_each(|state| state.is_terminated = true);
|
||||
let id = LogStoreEntryIdentifier {
|
||||
project: Cow::Borrowed(&weak_project),
|
||||
session_id: *session_id,
|
||||
};
|
||||
if let Some(state) = this.get_debug_adapter_state(&id) {
|
||||
state.is_terminated = true;
|
||||
}
|
||||
|
||||
this.clean_sessions(cx);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
),
|
||||
}
|
||||
}),
|
||||
],
|
||||
debug_sessions: Default::default(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn get_debug_adapter_state(&mut self, id: SessionId) -> Option<&mut DebugAdapterState> {
|
||||
self.debug_sessions
|
||||
.iter_mut()
|
||||
.find(|adapter_state| adapter_state.id == id)
|
||||
fn get_debug_adapter_state(
|
||||
&mut self,
|
||||
id: &LogStoreEntryIdentifier<'_>,
|
||||
) -> Option<&mut DebugAdapterState> {
|
||||
self.projects
|
||||
.get_mut(&id.project)
|
||||
.and_then(|state| state.debug_sessions.get_mut(&id.session_id))
|
||||
}
|
||||
|
||||
fn add_debug_adapter_message(
|
||||
&mut self,
|
||||
id: SessionId,
|
||||
io_kind: IoKind,
|
||||
command: Option<SharedString>,
|
||||
message: SharedString,
|
||||
LogStoreMessage {
|
||||
id,
|
||||
kind: io_kind,
|
||||
command,
|
||||
message,
|
||||
}: LogStoreMessage,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(debug_client_state) = self.get_debug_adapter_state(id) else {
|
||||
let Some(debug_client_state) = self.get_debug_adapter_state(&id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -229,7 +259,7 @@ impl LogStore {
|
||||
if rpc_messages.last_message_kind != Some(kind) {
|
||||
Self::get_debug_adapter_entry(
|
||||
&mut rpc_messages.messages,
|
||||
id,
|
||||
id.to_owned(),
|
||||
kind.label().into(),
|
||||
LogKind::Rpc,
|
||||
cx,
|
||||
@@ -239,7 +269,7 @@ impl LogStore {
|
||||
|
||||
let entry = Self::get_debug_adapter_entry(
|
||||
&mut rpc_messages.messages,
|
||||
id,
|
||||
id.to_owned(),
|
||||
message,
|
||||
LogKind::Rpc,
|
||||
cx,
|
||||
@@ -260,12 +290,15 @@ impl LogStore {
|
||||
|
||||
fn add_debug_adapter_log(
|
||||
&mut self,
|
||||
id: SessionId,
|
||||
io_kind: IoKind,
|
||||
message: SharedString,
|
||||
LogStoreMessage {
|
||||
id,
|
||||
kind: io_kind,
|
||||
message,
|
||||
..
|
||||
}: LogStoreMessage,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(debug_adapter_state) = self.get_debug_adapter_state(id) else {
|
||||
let Some(debug_adapter_state) = self.get_debug_adapter_state(&id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -276,7 +309,7 @@ impl LogStore {
|
||||
|
||||
Self::get_debug_adapter_entry(
|
||||
&mut debug_adapter_state.log_messages,
|
||||
id,
|
||||
id.to_owned(),
|
||||
message,
|
||||
LogKind::Adapter,
|
||||
cx,
|
||||
@@ -286,13 +319,17 @@ impl LogStore {
|
||||
|
||||
fn get_debug_adapter_entry(
|
||||
log_lines: &mut VecDeque<SharedString>,
|
||||
id: SessionId,
|
||||
id: LogStoreEntryIdentifier<'static>,
|
||||
message: SharedString,
|
||||
kind: LogKind,
|
||||
cx: &mut Context<Self>,
|
||||
) -> SharedString {
|
||||
while log_lines.len() >= RpcMessages::MESSAGE_QUEUE_LIMIT {
|
||||
log_lines.pop_front();
|
||||
if let Some(excess) = log_lines
|
||||
.len()
|
||||
.checked_sub(RpcMessages::MESSAGE_QUEUE_LIMIT)
|
||||
&& excess > 0
|
||||
{
|
||||
log_lines.drain(..excess);
|
||||
}
|
||||
|
||||
let format_messages = DebuggerSettings::get_global(cx).format_dap_log_messages;
|
||||
@@ -322,118 +359,116 @@ impl LogStore {
|
||||
|
||||
fn add_debug_session(
|
||||
&mut self,
|
||||
session_id: SessionId,
|
||||
id: LogStoreEntryIdentifier<'static>,
|
||||
session: Entity<Session>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self
|
||||
.debug_sessions
|
||||
.iter_mut()
|
||||
.any(|adapter_state| adapter_state.id == session_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
maybe!({
|
||||
let project_entry = self.projects.get_mut(&id.project)?;
|
||||
let std::collections::btree_map::Entry::Vacant(state) =
|
||||
project_entry.debug_sessions.entry(id.session_id)
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let (adapter_name, has_adapter_logs) = session.read_with(cx, |session, _| {
|
||||
(
|
||||
session.adapter(),
|
||||
session
|
||||
.adapter_client()
|
||||
.map(|client| client.has_adapter_logs())
|
||||
.unwrap_or(false),
|
||||
)
|
||||
let (adapter_name, has_adapter_logs) = session.read_with(cx, |session, _| {
|
||||
(
|
||||
session.adapter(),
|
||||
session
|
||||
.adapter_client()
|
||||
.map_or(false, |client| client.has_adapter_logs()),
|
||||
)
|
||||
});
|
||||
|
||||
state.insert(DebugAdapterState::new(
|
||||
id.session_id,
|
||||
adapter_name,
|
||||
has_adapter_logs,
|
||||
));
|
||||
|
||||
self.clean_sessions(cx);
|
||||
|
||||
let io_tx = self.rpc_tx.clone();
|
||||
|
||||
let client = session.read(cx).adapter_client()?;
|
||||
let project = id.project.clone();
|
||||
let session_id = id.session_id;
|
||||
client.add_log_handler(
|
||||
move |kind, command, message| {
|
||||
io_tx
|
||||
.unbounded_send(LogStoreMessage {
|
||||
id: LogStoreEntryIdentifier {
|
||||
session_id,
|
||||
project: project.clone(),
|
||||
},
|
||||
kind,
|
||||
command: command.map(|command| command.to_owned().into()),
|
||||
message: message.to_owned().into(),
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
LogKind::Rpc,
|
||||
);
|
||||
|
||||
let log_io_tx = self.adapter_log_tx.clone();
|
||||
let project = id.project;
|
||||
client.add_log_handler(
|
||||
move |kind, command, message| {
|
||||
log_io_tx
|
||||
.unbounded_send(LogStoreMessage {
|
||||
id: LogStoreEntryIdentifier {
|
||||
session_id,
|
||||
project: project.clone(),
|
||||
},
|
||||
kind,
|
||||
command: command.map(|command| command.to_owned().into()),
|
||||
message: message.to_owned().into(),
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
LogKind::Adapter,
|
||||
);
|
||||
Some(())
|
||||
});
|
||||
|
||||
self.debug_sessions.push_back(DebugAdapterState::new(
|
||||
session_id,
|
||||
adapter_name,
|
||||
has_adapter_logs,
|
||||
));
|
||||
|
||||
self.clean_sessions(cx);
|
||||
|
||||
let io_tx = self.rpc_tx.clone();
|
||||
|
||||
let Some(client) = session.read(cx).adapter_client() else {
|
||||
return;
|
||||
};
|
||||
|
||||
client.add_log_handler(
|
||||
move |io_kind, command, message| {
|
||||
io_tx
|
||||
.unbounded_send((
|
||||
session_id,
|
||||
io_kind,
|
||||
command.map(|command| command.to_owned().into()),
|
||||
message.to_owned().into(),
|
||||
))
|
||||
.ok();
|
||||
},
|
||||
LogKind::Rpc,
|
||||
);
|
||||
|
||||
let log_io_tx = self.adapter_log_tx.clone();
|
||||
client.add_log_handler(
|
||||
move |io_kind, command, message| {
|
||||
log_io_tx
|
||||
.unbounded_send((
|
||||
session_id,
|
||||
io_kind,
|
||||
command.map(|command| command.to_owned().into()),
|
||||
message.to_owned().into(),
|
||||
))
|
||||
.ok();
|
||||
},
|
||||
LogKind::Adapter,
|
||||
);
|
||||
}
|
||||
|
||||
fn clean_sessions(&mut self, cx: &mut Context<Self>) {
|
||||
let mut to_remove = self.debug_sessions.len().saturating_sub(MAX_SESSIONS);
|
||||
self.debug_sessions.retain(|session| {
|
||||
if to_remove > 0 && session.is_terminated {
|
||||
to_remove -= 1;
|
||||
return false;
|
||||
}
|
||||
true
|
||||
self.projects.values_mut().for_each(|project| {
|
||||
let mut allowed_terminated_sessions = 10u32;
|
||||
project.debug_sessions.retain(|_, session| {
|
||||
if !session.is_terminated {
|
||||
return true;
|
||||
}
|
||||
allowed_terminated_sessions = allowed_terminated_sessions.saturating_sub(1);
|
||||
allowed_terminated_sessions > 0
|
||||
});
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn log_messages_for_session(
|
||||
&mut self,
|
||||
session_id: SessionId,
|
||||
id: &LogStoreEntryIdentifier<'_>,
|
||||
) -> Option<&mut VecDeque<SharedString>> {
|
||||
self.debug_sessions
|
||||
.iter_mut()
|
||||
.find(|session| session.id == session_id)
|
||||
self.get_debug_adapter_state(id)
|
||||
.map(|state| &mut state.log_messages)
|
||||
}
|
||||
|
||||
fn rpc_messages_for_session(
|
||||
&mut self,
|
||||
session_id: SessionId,
|
||||
id: &LogStoreEntryIdentifier<'_>,
|
||||
) -> Option<&mut VecDeque<SharedString>> {
|
||||
self.debug_sessions.iter_mut().find_map(|state| {
|
||||
if state.id == session_id {
|
||||
Some(&mut state.rpc_messages.messages)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
self.get_debug_adapter_state(id)
|
||||
.map(|state| &mut state.rpc_messages.messages)
|
||||
}
|
||||
|
||||
fn initialization_sequence_for_session(
|
||||
&mut self,
|
||||
session_id: SessionId,
|
||||
) -> Option<&mut Vec<SharedString>> {
|
||||
self.debug_sessions.iter_mut().find_map(|state| {
|
||||
if state.id == session_id {
|
||||
Some(&mut state.rpc_messages.initialization_sequence)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
id: &LogStoreEntryIdentifier<'_>,
|
||||
) -> Option<&Vec<SharedString>> {
|
||||
self.get_debug_adapter_state(&id)
|
||||
.map(|state| &state.rpc_messages.initialization_sequence)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,10 +488,11 @@ impl Render for DapLogToolbarItemView {
|
||||
return Empty.into_any_element();
|
||||
};
|
||||
|
||||
let (menu_rows, current_session_id) = log_view.update(cx, |log_view, cx| {
|
||||
let (menu_rows, current_session_id, project) = log_view.update(cx, |log_view, cx| {
|
||||
(
|
||||
log_view.menu_items(cx),
|
||||
log_view.current_view.map(|(session_id, _)| session_id),
|
||||
log_view.project.downgrade(),
|
||||
)
|
||||
});
|
||||
|
||||
@@ -484,6 +520,7 @@ impl Render for DapLogToolbarItemView {
|
||||
.menu(move |mut window, cx| {
|
||||
let log_view = log_view.clone();
|
||||
let menu_rows = menu_rows.clone();
|
||||
let project = project.clone();
|
||||
ContextMenu::build(&mut window, cx, move |mut menu, window, _cx| {
|
||||
for row in menu_rows.into_iter() {
|
||||
menu = menu.custom_row(move |_window, _cx| {
|
||||
@@ -509,8 +546,15 @@ impl Render for DapLogToolbarItemView {
|
||||
.child(Label::new(ADAPTER_LOGS))
|
||||
.into_any_element()
|
||||
},
|
||||
window.handler_for(&log_view, move |view, window, cx| {
|
||||
view.show_log_messages_for_adapter(row.session_id, window, cx);
|
||||
window.handler_for(&log_view, {
|
||||
let project = project.clone();
|
||||
let id = LogStoreEntryIdentifier {
|
||||
project: Cow::Owned(project),
|
||||
session_id: row.session_id,
|
||||
};
|
||||
move |view, window, cx| {
|
||||
view.show_log_messages_for_adapter(&id, window, cx);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -524,8 +568,15 @@ impl Render for DapLogToolbarItemView {
|
||||
.child(Label::new(RPC_MESSAGES))
|
||||
.into_any_element()
|
||||
},
|
||||
window.handler_for(&log_view, move |view, window, cx| {
|
||||
view.show_rpc_trace_for_server(row.session_id, window, cx);
|
||||
window.handler_for(&log_view, {
|
||||
let project = project.clone();
|
||||
let id = LogStoreEntryIdentifier {
|
||||
project: Cow::Owned(project),
|
||||
session_id: row.session_id,
|
||||
};
|
||||
move |view, window, cx| {
|
||||
view.show_rpc_trace_for_server(&id, window, cx);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.custom_entry(
|
||||
@@ -536,12 +587,17 @@ impl Render for DapLogToolbarItemView {
|
||||
.child(Label::new(INITIALIZATION_SEQUENCE))
|
||||
.into_any_element()
|
||||
},
|
||||
window.handler_for(&log_view, move |view, window, cx| {
|
||||
view.show_initialization_sequence_for_server(
|
||||
row.session_id,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
window.handler_for(&log_view, {
|
||||
let project = project.clone();
|
||||
let id = LogStoreEntryIdentifier {
|
||||
project: Cow::Owned(project),
|
||||
session_id: row.session_id,
|
||||
};
|
||||
move |view, window, cx| {
|
||||
view.show_initialization_sequence_for_server(
|
||||
&id, window, cx,
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -613,7 +669,9 @@ impl DapLogView {
|
||||
|
||||
let events_subscriptions = cx.subscribe(&log_store, |log_view, _, event, cx| match event {
|
||||
Event::NewLogEntry { id, entry, kind } => {
|
||||
if log_view.current_view == Some((*id, *kind)) {
|
||||
if log_view.current_view == Some((id.session_id, *kind))
|
||||
&& log_view.project == *id.project
|
||||
{
|
||||
log_view.editor.update(cx, |editor, cx| {
|
||||
editor.set_read_only(false);
|
||||
let last_point = editor.buffer().read(cx).len(cx);
|
||||
@@ -629,12 +687,18 @@ impl DapLogView {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let weak_project = project.downgrade();
|
||||
let state_info = log_store
|
||||
.read(cx)
|
||||
.debug_sessions
|
||||
.back()
|
||||
.map(|session| (session.id, session.has_adapter_logs));
|
||||
.projects
|
||||
.get(&weak_project)
|
||||
.and_then(|project| {
|
||||
project
|
||||
.debug_sessions
|
||||
.values()
|
||||
.next_back()
|
||||
.map(|session| (session.id, session.has_adapter_logs))
|
||||
});
|
||||
|
||||
let mut this = Self {
|
||||
editor,
|
||||
@@ -647,10 +711,14 @@ impl DapLogView {
|
||||
};
|
||||
|
||||
if let Some((session_id, have_adapter_logs)) = state_info {
|
||||
let id = LogStoreEntryIdentifier {
|
||||
session_id,
|
||||
project: Cow::Owned(weak_project),
|
||||
};
|
||||
if have_adapter_logs {
|
||||
this.show_log_messages_for_adapter(session_id, window, cx);
|
||||
this.show_log_messages_for_adapter(&id, window, cx);
|
||||
} else {
|
||||
this.show_rpc_trace_for_server(session_id, window, cx);
|
||||
this.show_rpc_trace_for_server(&id, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,31 +758,38 @@ impl DapLogView {
|
||||
fn menu_items(&self, cx: &App) -> Vec<DapMenuItem> {
|
||||
self.log_store
|
||||
.read(cx)
|
||||
.debug_sessions
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|state| DapMenuItem {
|
||||
session_id: state.id,
|
||||
adapter_name: state.adapter_name.clone(),
|
||||
has_adapter_logs: state.has_adapter_logs,
|
||||
selected_entry: self.current_view.map_or(LogKind::Adapter, |(_, kind)| kind),
|
||||
.projects
|
||||
.get(&self.project.downgrade())
|
||||
.map_or_else(Vec::new, |state| {
|
||||
state
|
||||
.debug_sessions
|
||||
.values()
|
||||
.rev()
|
||||
.map(|state| DapMenuItem {
|
||||
session_id: state.id,
|
||||
adapter_name: state.adapter_name.clone(),
|
||||
has_adapter_logs: state.has_adapter_logs,
|
||||
selected_entry: self
|
||||
.current_view
|
||||
.map_or(LogKind::Adapter, |(_, kind)| kind),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn show_rpc_trace_for_server(
|
||||
&mut self,
|
||||
session_id: SessionId,
|
||||
id: &LogStoreEntryIdentifier<'_>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let rpc_log = self.log_store.update(cx, |log_store, _| {
|
||||
log_store
|
||||
.rpc_messages_for_session(session_id)
|
||||
.rpc_messages_for_session(id)
|
||||
.map(|state| log_contents(state.iter().cloned()))
|
||||
});
|
||||
if let Some(rpc_log) = rpc_log {
|
||||
self.current_view = Some((session_id, LogKind::Rpc));
|
||||
self.current_view = Some((id.session_id, LogKind::Rpc));
|
||||
let (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, window, cx);
|
||||
let language = self.project.read(cx).languages().language_for_name("JSON");
|
||||
editor
|
||||
@@ -725,8 +800,7 @@ impl DapLogView {
|
||||
.expect("log buffer should be a singleton")
|
||||
.update(cx, |_, cx| {
|
||||
cx.spawn({
|
||||
let buffer = cx.entity();
|
||||
async move |_, cx| {
|
||||
async move |buffer, cx| {
|
||||
let language = language.await.ok();
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_language(language, cx);
|
||||
@@ -746,17 +820,17 @@ impl DapLogView {
|
||||
|
||||
fn show_log_messages_for_adapter(
|
||||
&mut self,
|
||||
session_id: SessionId,
|
||||
id: &LogStoreEntryIdentifier<'_>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let message_log = self.log_store.update(cx, |log_store, _| {
|
||||
log_store
|
||||
.log_messages_for_session(session_id)
|
||||
.log_messages_for_session(id)
|
||||
.map(|state| log_contents(state.iter().cloned()))
|
||||
});
|
||||
if let Some(message_log) = message_log {
|
||||
self.current_view = Some((session_id, LogKind::Adapter));
|
||||
self.current_view = Some((id.session_id, LogKind::Adapter));
|
||||
let (editor, editor_subscriptions) = Self::editor_for_logs(message_log, window, cx);
|
||||
editor
|
||||
.read(cx)
|
||||
@@ -775,17 +849,17 @@ impl DapLogView {
|
||||
|
||||
fn show_initialization_sequence_for_server(
|
||||
&mut self,
|
||||
session_id: SessionId,
|
||||
id: &LogStoreEntryIdentifier<'_>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let rpc_log = self.log_store.update(cx, |log_store, _| {
|
||||
log_store
|
||||
.initialization_sequence_for_session(session_id)
|
||||
.initialization_sequence_for_session(id)
|
||||
.map(|state| log_contents(state.iter().cloned()))
|
||||
});
|
||||
if let Some(rpc_log) = rpc_log {
|
||||
self.current_view = Some((session_id, LogKind::Rpc));
|
||||
self.current_view = Some((id.session_id, LogKind::Rpc));
|
||||
let (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, window, cx);
|
||||
let language = self.project.read(cx).languages().language_for_name("JSON");
|
||||
editor
|
||||
@@ -993,9 +1067,9 @@ impl Focusable for DapLogView {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
enum Event {
|
||||
NewLogEntry {
|
||||
id: SessionId,
|
||||
id: LogStoreEntryIdentifier<'static>,
|
||||
entry: SharedString,
|
||||
kind: LogKind,
|
||||
},
|
||||
@@ -1008,31 +1082,30 @@ impl EventEmitter<SearchEvent> for DapLogView {}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl LogStore {
|
||||
pub fn contained_session_ids(&self) -> Vec<SessionId> {
|
||||
self.debug_sessions
|
||||
.iter()
|
||||
.map(|session| session.id)
|
||||
.collect()
|
||||
pub fn has_projects(&self) -> bool {
|
||||
!self.projects.is_empty()
|
||||
}
|
||||
|
||||
pub fn rpc_messages_for_session_id(&self, session_id: SessionId) -> Vec<SharedString> {
|
||||
self.debug_sessions
|
||||
.iter()
|
||||
.find(|adapter_state| adapter_state.id == session_id)
|
||||
.expect("This session should exist if a test is calling")
|
||||
.rpc_messages
|
||||
.messages
|
||||
.clone()
|
||||
.into()
|
||||
pub fn contained_session_ids(&self, project: &WeakEntity<Project>) -> Vec<SessionId> {
|
||||
self.projects.get(project).map_or(vec![], |state| {
|
||||
state.debug_sessions.keys().copied().collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn log_messages_for_session_id(&self, session_id: SessionId) -> Vec<SharedString> {
|
||||
self.debug_sessions
|
||||
.iter()
|
||||
.find(|adapter_state| adapter_state.id == session_id)
|
||||
.expect("This session should exist if a test is calling")
|
||||
.log_messages
|
||||
.clone()
|
||||
.into()
|
||||
pub fn rpc_messages_for_session_id(
|
||||
&self,
|
||||
project: &WeakEntity<Project>,
|
||||
session_id: SessionId,
|
||||
) -> Vec<SharedString> {
|
||||
self.projects.get(&project).map_or(vec![], |state| {
|
||||
state
|
||||
.debug_sessions
|
||||
.get(&session_id)
|
||||
.expect("This session should exist if a test is calling")
|
||||
.rpc_messages
|
||||
.messages
|
||||
.clone()
|
||||
.into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ test-support = [
|
||||
[dependencies]
|
||||
alacritty_terminal.workspace = true
|
||||
anyhow.workspace = true
|
||||
bitflags.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
|
||||
@@ -5,7 +5,7 @@ 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,
|
||||
NewProcessModal, NewProcessMode, Pause, RerunSession, StepInto, StepOut, StepOver, Stop,
|
||||
ToggleExpandItem, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
@@ -25,7 +25,7 @@ use gpui::{
|
||||
use itertools::Itertools as _;
|
||||
use language::Buffer;
|
||||
use project::debugger::session::{Session, SessionStateEvent};
|
||||
use project::{Fs, ProjectPath, WorktreeId};
|
||||
use project::{DebugScenarioContext, Fs, ProjectPath, WorktreeId};
|
||||
use project::{Project, debugger::session::ThreadStatus};
|
||||
use rpc::proto::{self};
|
||||
use settings::Settings;
|
||||
@@ -100,7 +100,13 @@ impl DebugPanel {
|
||||
sessions: vec![],
|
||||
active_session: None,
|
||||
focus_handle,
|
||||
breakpoint_list: BreakpointList::new(None, workspace.weak_handle(), &project, cx),
|
||||
breakpoint_list: BreakpointList::new(
|
||||
None,
|
||||
workspace.weak_handle(),
|
||||
&project,
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
project,
|
||||
workspace: workspace.weak_handle(),
|
||||
context_menu: None,
|
||||
@@ -191,6 +197,7 @@ impl DebugPanel {
|
||||
.and_then(|buffer| buffer.read(cx).file())
|
||||
.map(|f| f.worktree_id(cx))
|
||||
});
|
||||
|
||||
let Some(worktree) = worktree
|
||||
.and_then(|id| self.project.read(cx).worktree_for_id(id, cx))
|
||||
.or_else(|| self.project.read(cx).visible_worktrees(cx).next())
|
||||
@@ -198,6 +205,7 @@ impl DebugPanel {
|
||||
log::debug!("Could not find a worktree to spawn the debug session in");
|
||||
return;
|
||||
};
|
||||
|
||||
self.debug_scenario_scheduled_last = true;
|
||||
if let Some(inventory) = self
|
||||
.project
|
||||
@@ -208,7 +216,15 @@ impl DebugPanel {
|
||||
.cloned()
|
||||
{
|
||||
inventory.update(cx, |inventory, _| {
|
||||
inventory.scenario_scheduled(scenario.clone());
|
||||
inventory.scenario_scheduled(
|
||||
scenario.clone(),
|
||||
// todo(debugger): Task context is cloned three times
|
||||
// once in Session,inventory, and in resolve scenario
|
||||
// we should wrap it in an RC instead to save some memory
|
||||
task_context.clone(),
|
||||
worktree_id,
|
||||
active_buffer.as_ref().map(|buffer| buffer.downgrade()),
|
||||
);
|
||||
})
|
||||
}
|
||||
let task = cx.spawn_in(window, {
|
||||
@@ -219,6 +235,16 @@ impl DebugPanel {
|
||||
let definition = debug_session
|
||||
.update_in(cx, |debug_session, window, cx| {
|
||||
debug_session.running_state().update(cx, |running, cx| {
|
||||
if scenario.build.is_some() {
|
||||
running.scenario = Some(scenario.clone());
|
||||
running.scenario_context = Some(DebugScenarioContext {
|
||||
active_buffer: active_buffer
|
||||
.as_ref()
|
||||
.map(|entity| entity.downgrade()),
|
||||
task_context: task_context.clone(),
|
||||
worktree_id: worktree_id,
|
||||
});
|
||||
};
|
||||
running.resolve_scenario(
|
||||
scenario,
|
||||
task_context,
|
||||
@@ -267,7 +293,8 @@ impl DebugPanel {
|
||||
return;
|
||||
};
|
||||
let workspace = self.workspace.clone();
|
||||
let Some(scenario) = task_inventory.read(cx).last_scheduled_scenario().cloned() else {
|
||||
let Some((scenario, context)) = task_inventory.read(cx).last_scheduled_scenario().cloned()
|
||||
else {
|
||||
window.defer(cx, move |window, cx| {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
@@ -278,28 +305,22 @@ impl DebugPanel {
|
||||
return;
|
||||
};
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let task_contexts = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
tasks_ui::task_contexts(workspace, window, cx)
|
||||
})?
|
||||
.await;
|
||||
let DebugScenarioContext {
|
||||
task_context,
|
||||
worktree_id,
|
||||
active_buffer,
|
||||
} = context;
|
||||
|
||||
let task_context = task_contexts.active_context().cloned().unwrap_or_default();
|
||||
let worktree_id = task_contexts.worktree();
|
||||
let active_buffer = active_buffer.and_then(|buffer| buffer.upgrade());
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.start_session(
|
||||
scenario.clone(),
|
||||
task_context,
|
||||
None,
|
||||
worktree_id,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
self.start_session(
|
||||
scenario,
|
||||
task_context,
|
||||
active_buffer,
|
||||
worktree_id,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) async fn register_session(
|
||||
@@ -752,16 +773,16 @@ impl DebugPanel {
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_state,
|
||||
|this, _, _window, cx| {
|
||||
this.restart_session(cx);
|
||||
|this, _, window, cx| {
|
||||
this.rerun_session(window, cx);
|
||||
},
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Restart",
|
||||
&Restart,
|
||||
"Rerun Session",
|
||||
&RerunSession,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
@@ -862,7 +883,7 @@ impl DebugPanel {
|
||||
let threads =
|
||||
running_state.update(cx, |running_state, cx| {
|
||||
let session = running_state.session();
|
||||
session.read(cx).is_running().then(|| {
|
||||
session.read(cx).is_started().then(|| {
|
||||
session.update(cx, |session, cx| {
|
||||
session.threads(cx)
|
||||
})
|
||||
@@ -1292,6 +1313,13 @@ impl Render for DebugPanel {
|
||||
}
|
||||
|
||||
v_flex()
|
||||
.when(!self.is_zoomed, |this| {
|
||||
this.when_else(
|
||||
self.position(window, cx) == DockPosition::Bottom,
|
||||
|this| this.max_h(self.size),
|
||||
|this| this.max_w(self.size),
|
||||
)
|
||||
})
|
||||
.size_full()
|
||||
.key_context("DebugPanel")
|
||||
.child(h_flex().children(self.top_controls_strip(window, cx)))
|
||||
@@ -1462,6 +1490,94 @@ impl Render for DebugPanel {
|
||||
if has_sessions {
|
||||
this.children(self.active_session.clone())
|
||||
} else {
|
||||
let docked_to_bottom = self.position(window, cx) == DockPosition::Bottom;
|
||||
let welcome_experience = v_flex()
|
||||
.when_else(
|
||||
docked_to_bottom,
|
||||
|this| this.w_2_3().h_full().pr_8(),
|
||||
|this| this.w_full().h_1_3(),
|
||||
)
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.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,
|
||||
);
|
||||
}),
|
||||
);
|
||||
let breakpoint_list =
|
||||
v_flex()
|
||||
.group("base-breakpoint-list")
|
||||
.items_start()
|
||||
.when_else(
|
||||
docked_to_bottom,
|
||||
|this| this.min_w_1_3().h_full(),
|
||||
|this| this.w_full().h_2_3(),
|
||||
)
|
||||
.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(),
|
||||
))
|
||||
.track_focus(&self.breakpoint_list.focus_handle(cx)),
|
||||
)
|
||||
.child(Divider::horizontal())
|
||||
.child(self.breakpoint_list.clone());
|
||||
this.child(
|
||||
v_flex()
|
||||
.h_full()
|
||||
@@ -1469,65 +1585,23 @@ 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);
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
div()
|
||||
.when_else(docked_to_bottom, Div::h_flex, Div::v_flex)
|
||||
.size_full()
|
||||
.map(|this| {
|
||||
if docked_to_bottom {
|
||||
this.items_start()
|
||||
.child(breakpoint_list)
|
||||
.child(Divider::vertical())
|
||||
.child(welcome_experience)
|
||||
} else {
|
||||
this.items_end()
|
||||
.child(welcome_experience)
|
||||
.child(Divider::horizontal())
|
||||
.child(breakpoint_list)
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1543,12 +1617,13 @@ impl workspace::DebuggerProvider for DebuggerProvider {
|
||||
definition: DebugScenario,
|
||||
context: TaskContext,
|
||||
buffer: Option<Entity<Buffer>>,
|
||||
worktree_id: Option<WorktreeId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
self.0.update(cx, |_, cx| {
|
||||
cx.defer_in(window, |this, window, cx| {
|
||||
this.start_session(definition, context, buffer, None, window, cx);
|
||||
cx.defer_in(window, move |this, window, cx| {
|
||||
this.start_session(definition, context, buffer, worktree_id, window, cx);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ actions!(
|
||||
Detach,
|
||||
Pause,
|
||||
Restart,
|
||||
RerunSession,
|
||||
StepInto,
|
||||
StepOver,
|
||||
StepOut,
|
||||
@@ -54,7 +55,8 @@ actions!(
|
||||
ShowStackTrace,
|
||||
ToggleThreadPicker,
|
||||
ToggleSessionPicker,
|
||||
RerunLastSession,
|
||||
#[action(deprecated_aliases = ["debugger::RerunLastSession"])]
|
||||
Rerun,
|
||||
ToggleExpandItem,
|
||||
]
|
||||
);
|
||||
@@ -74,17 +76,15 @@ pub fn init(cx: &mut App) {
|
||||
.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;
|
||||
};
|
||||
.register_action(|workspace: &mut Workspace, _: &Rerun, 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);
|
||||
})
|
||||
},
|
||||
)
|
||||
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| {
|
||||
@@ -210,6 +210,14 @@ pub fn init(cx: &mut App) {
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.on_action({
|
||||
let active_item = active_item.clone();
|
||||
move |_: &RerunSession, window, cx| {
|
||||
active_item
|
||||
.update(cx, |item, cx| item.rerun_session(window, cx))
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.on_action({
|
||||
let active_item = active_item.clone();
|
||||
move |_: &Stop, _, cx| {
|
||||
|
||||
@@ -4,6 +4,7 @@ use collections::HashMap;
|
||||
use gpui::{Animation, AnimationExt as _, Entity, Transformation, percentage};
|
||||
use project::debugger::session::{ThreadId, ThreadStatus};
|
||||
use ui::{ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
|
||||
use util::truncate_and_trailoff;
|
||||
|
||||
use crate::{
|
||||
debugger_panel::DebugPanel,
|
||||
@@ -12,6 +13,8 @@ use crate::{
|
||||
|
||||
impl DebugPanel {
|
||||
fn dropdown_label(label: impl Into<SharedString>) -> Label {
|
||||
const MAX_LABEL_CHARS: usize = 50;
|
||||
let label = truncate_and_trailoff(&label.into(), MAX_LABEL_CHARS);
|
||||
Label::new(label).size(LabelSize::Small)
|
||||
}
|
||||
|
||||
@@ -170,6 +173,8 @@ impl DebugPanel {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<DropdownMenu> {
|
||||
const MAX_LABEL_CHARS: usize = 150;
|
||||
|
||||
let running_state = running_state.clone();
|
||||
let running_state_read = running_state.read(cx);
|
||||
let thread_id = running_state_read.thread_id();
|
||||
@@ -202,6 +207,7 @@ impl DebugPanel {
|
||||
.is_empty()
|
||||
.then(|| format!("Tid: {}", thread.id))
|
||||
.unwrap_or_else(|| thread.name);
|
||||
let entry_name = truncate_and_trailoff(&entry_name, MAX_LABEL_CHARS);
|
||||
|
||||
this = this.entry(entry_name, None, move |window, cx| {
|
||||
running_state.update(cx, |running_state, cx| {
|
||||
|
||||
@@ -23,7 +23,9 @@ use gpui::{
|
||||
};
|
||||
use itertools::Itertools as _;
|
||||
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
|
||||
use project::{ProjectPath, TaskContexts, TaskSourceKind, task_store::TaskStore};
|
||||
use project::{
|
||||
DebugScenarioContext, ProjectPath, TaskContexts, TaskSourceKind, task_store::TaskStore,
|
||||
};
|
||||
use settings::{Settings, initial_local_debug_tasks_content};
|
||||
use task::{DebugScenario, RevealTarget, ZedDebugConfig};
|
||||
use theme::ThemeSettings;
|
||||
@@ -92,6 +94,7 @@ impl NewProcessModal {
|
||||
|
||||
cx.spawn_in(window, async move |workspace, cx| {
|
||||
let task_contexts = workspace.update_in(cx, |workspace, window, cx| {
|
||||
// todo(debugger): get the buffer here (if the active item is an editor) and store it so we can pass it to start_session later
|
||||
tasks_ui::task_contexts(workspace, window, cx)
|
||||
})?;
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
@@ -1110,7 +1113,11 @@ pub(super) struct TaskMode {
|
||||
|
||||
pub(super) struct DebugDelegate {
|
||||
task_store: Entity<TaskStore>,
|
||||
candidates: Vec<(Option<TaskSourceKind>, DebugScenario)>,
|
||||
candidates: Vec<(
|
||||
Option<TaskSourceKind>,
|
||||
DebugScenario,
|
||||
Option<DebugScenarioContext>,
|
||||
)>,
|
||||
selected_index: usize,
|
||||
matches: Vec<StringMatch>,
|
||||
prompt: String,
|
||||
@@ -1208,7 +1215,11 @@ impl DebugDelegate {
|
||||
|
||||
this.delegate.candidates = recent
|
||||
.into_iter()
|
||||
.map(|scenario| Self::get_scenario_kind(&languages, &dap_registry, scenario))
|
||||
.map(|(scenario, context)| {
|
||||
let (kind, scenario) =
|
||||
Self::get_scenario_kind(&languages, &dap_registry, scenario);
|
||||
(kind, scenario, Some(context))
|
||||
})
|
||||
.chain(
|
||||
scenarios
|
||||
.into_iter()
|
||||
@@ -1223,7 +1234,7 @@ impl DebugDelegate {
|
||||
.map(|(kind, scenario)| {
|
||||
let (language, scenario) =
|
||||
Self::get_scenario_kind(&languages, &dap_registry, scenario);
|
||||
(language.or(Some(kind)), scenario)
|
||||
(language.or(Some(kind)), scenario, None)
|
||||
}),
|
||||
)
|
||||
.collect();
|
||||
@@ -1269,7 +1280,7 @@ impl PickerDelegate for DebugDelegate {
|
||||
let candidates: Vec<_> = candidates
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, (_, candidate))| {
|
||||
.map(|(index, (_, candidate, _))| {
|
||||
StringMatchCandidate::new(index, candidate.label.as_ref())
|
||||
})
|
||||
.collect();
|
||||
@@ -1434,25 +1445,40 @@ impl PickerDelegate for DebugDelegate {
|
||||
.get(self.selected_index())
|
||||
.and_then(|match_candidate| self.candidates.get(match_candidate.candidate_id).cloned());
|
||||
|
||||
let Some((_, debug_scenario)) = debug_scenario else {
|
||||
let Some((_, debug_scenario, context)) = debug_scenario else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (task_context, worktree_id) = self
|
||||
.task_contexts
|
||||
.as_ref()
|
||||
.and_then(|task_contexts| {
|
||||
Some((
|
||||
task_contexts.active_context().cloned()?,
|
||||
task_contexts.worktree(),
|
||||
))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let context = context.unwrap_or_else(|| {
|
||||
self.task_contexts
|
||||
.as_ref()
|
||||
.and_then(|task_contexts| {
|
||||
Some(DebugScenarioContext {
|
||||
task_context: task_contexts.active_context().cloned()?,
|
||||
active_buffer: None,
|
||||
worktree_id: task_contexts.worktree(),
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
});
|
||||
let DebugScenarioContext {
|
||||
task_context,
|
||||
active_buffer,
|
||||
worktree_id,
|
||||
} = context;
|
||||
let active_buffer = active_buffer.and_then(|buffer| buffer.upgrade());
|
||||
|
||||
send_telemetry(&debug_scenario, TelemetrySpawnLocation::ScenarioList, cx);
|
||||
self.debug_panel
|
||||
.update(cx, |panel, cx| {
|
||||
panel.start_session(debug_scenario, task_context, None, worktree_id, window, cx);
|
||||
panel.start_session(
|
||||
debug_scenario,
|
||||
task_context,
|
||||
active_buffer,
|
||||
worktree_id,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ use rpc::proto;
|
||||
use running::RunningState;
|
||||
use std::{cell::OnceCell, sync::OnceLock};
|
||||
use ui::{Indicator, Tooltip, prelude::*};
|
||||
use util::truncate_and_trailoff;
|
||||
use workspace::{
|
||||
CollaboratorId, FollowableItem, ViewId, Workspace,
|
||||
item::{self, Item},
|
||||
@@ -126,7 +127,10 @@ impl DebugSession {
|
||||
}
|
||||
|
||||
pub(crate) fn label_element(&self, depth: usize, cx: &App) -> AnyElement {
|
||||
const MAX_LABEL_CHARS: usize = 150;
|
||||
|
||||
let label = self.label(cx);
|
||||
let label = truncate_and_trailoff(&label, MAX_LABEL_CHARS);
|
||||
|
||||
let is_terminated = self
|
||||
.running_state
|
||||
|
||||
@@ -33,7 +33,7 @@ use language::Buffer;
|
||||
use loaded_source_list::LoadedSourceList;
|
||||
use module_list::ModuleList;
|
||||
use project::{
|
||||
Project, WorktreeId,
|
||||
DebugScenarioContext, Project, WorktreeId,
|
||||
debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus},
|
||||
terminals::TerminalKind,
|
||||
};
|
||||
@@ -79,6 +79,8 @@ pub struct RunningState {
|
||||
pane_close_subscriptions: HashMap<EntityId, Subscription>,
|
||||
dock_axis: Axis,
|
||||
_schedule_serialize: Option<Task<()>>,
|
||||
pub(crate) scenario: Option<DebugScenario>,
|
||||
pub(crate) scenario_context: Option<DebugScenarioContext>,
|
||||
}
|
||||
|
||||
impl RunningState {
|
||||
@@ -697,8 +699,13 @@ impl RunningState {
|
||||
)
|
||||
});
|
||||
|
||||
let breakpoint_list =
|
||||
BreakpointList::new(Some(session.clone()), workspace.clone(), &project, cx);
|
||||
let breakpoint_list = BreakpointList::new(
|
||||
Some(session.clone()),
|
||||
workspace.clone(),
|
||||
&project,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
let _subscriptions = vec![
|
||||
cx.on_app_quit(move |this, cx| {
|
||||
@@ -826,6 +833,8 @@ impl RunningState {
|
||||
debug_terminal,
|
||||
dock_axis,
|
||||
_schedule_serialize: None,
|
||||
scenario: None,
|
||||
scenario_context: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -895,7 +904,7 @@ impl RunningState {
|
||||
|
||||
|
||||
let config_is_valid = request_type.is_ok();
|
||||
|
||||
let mut extra_config = Value::Null;
|
||||
let build_output = if let Some(build) = build {
|
||||
let (task_template, locator_name) = match build {
|
||||
BuildTaskDefinition::Template {
|
||||
@@ -925,6 +934,7 @@ impl RunningState {
|
||||
};
|
||||
|
||||
let locator_name = if let Some(locator_name) = locator_name {
|
||||
extra_config = config.clone();
|
||||
debug_assert!(!config_is_valid);
|
||||
Some(locator_name)
|
||||
} else if !config_is_valid {
|
||||
@@ -940,6 +950,7 @@ impl RunningState {
|
||||
});
|
||||
if let Ok(t) = task {
|
||||
t.await.and_then(|scenario| {
|
||||
extra_config = scenario.config;
|
||||
match scenario.build {
|
||||
Some(BuildTaskDefinition::Template {
|
||||
locator_name, ..
|
||||
@@ -1003,13 +1014,13 @@ impl RunningState {
|
||||
if !exit_status.success() {
|
||||
anyhow::bail!("Build failed");
|
||||
}
|
||||
Some((task.resolved.clone(), locator_name))
|
||||
Some((task.resolved.clone(), locator_name, extra_config))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if config_is_valid {
|
||||
} else if let Some((task, locator_name)) = build_output {
|
||||
} else if let Some((task, locator_name, extra_config)) = build_output {
|
||||
let locator_name =
|
||||
locator_name.with_context(|| {
|
||||
format!("Could not find a valid locator for a build task and configure is invalid with error: {}", request_type.err()
|
||||
@@ -1032,8 +1043,10 @@ 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?;
|
||||
.await?;
|
||||
config = scenario.config;
|
||||
util::merge_non_null_json_value_into(extra_config, &mut config);
|
||||
|
||||
Self::substitute_variables_in_config(&mut config, &task_context);
|
||||
} else {
|
||||
let Err(e) = request_type else {
|
||||
@@ -1516,6 +1529,34 @@ impl RunningState {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn rerun_session(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some((scenario, context)) = self.scenario.take().zip(self.scenario_context.take())
|
||||
&& scenario.build.is_some()
|
||||
{
|
||||
let DebugScenarioContext {
|
||||
task_context,
|
||||
active_buffer,
|
||||
worktree_id,
|
||||
} = context;
|
||||
let active_buffer = active_buffer.and_then(|buffer| buffer.upgrade());
|
||||
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.start_debug_session(
|
||||
scenario,
|
||||
task_context,
|
||||
active_buffer,
|
||||
worktree_id,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
self.restart_session(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restart_session(&self, cx: &mut Context<Self>) {
|
||||
self.session().update(cx, |state, cx| {
|
||||
state.restart(None, cx);
|
||||
|
||||
@@ -5,11 +5,11 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use dap::ExceptionBreakpointsFilter;
|
||||
use dap::{Capabilities, ExceptionBreakpointsFilter};
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
Action, AppContext, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy, Stateful,
|
||||
Task, UniformListScrollHandle, WeakEntity, uniform_list,
|
||||
Action, AppContext, ClickEvent, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy,
|
||||
Stateful, Task, UniformListScrollHandle, WeakEntity, actions, uniform_list,
|
||||
};
|
||||
use language::Point;
|
||||
use project::{
|
||||
@@ -21,16 +21,20 @@ 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,
|
||||
ActiveTheme, AnyElement, App, ButtonCommon, Clickable, Color, Context, Disableable, Div,
|
||||
Divider, FluentBuilder as _, Icon, IconButton, IconName, IconSize, Indicator,
|
||||
InteractiveElement, IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement,
|
||||
Render, RenderOnce, 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};
|
||||
|
||||
actions!(
|
||||
debugger,
|
||||
[PreviousBreakpointProperty, NextBreakpointProperty]
|
||||
);
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub(crate) enum SelectedBreakpointKind {
|
||||
Source,
|
||||
@@ -48,6 +52,8 @@ pub(crate) struct BreakpointList {
|
||||
focus_handle: FocusHandle,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
selected_ix: Option<usize>,
|
||||
input: Entity<Editor>,
|
||||
strip_mode: Option<ActiveBreakpointStripMode>,
|
||||
}
|
||||
|
||||
impl Focusable for BreakpointList {
|
||||
@@ -56,11 +62,19 @@ impl Focusable for BreakpointList {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum ActiveBreakpointStripMode {
|
||||
Log,
|
||||
Condition,
|
||||
HitCondition,
|
||||
}
|
||||
|
||||
impl BreakpointList {
|
||||
pub(crate) fn new(
|
||||
session: Option<Entity<Session>>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: &Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
let project = project.read(cx);
|
||||
@@ -70,7 +84,7 @@ impl BreakpointList {
|
||||
let scroll_handle = UniformListScrollHandle::new();
|
||||
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
|
||||
|
||||
cx.new(|_| Self {
|
||||
cx.new(|cx| Self {
|
||||
breakpoint_store,
|
||||
worktree_store,
|
||||
scrollbar_state,
|
||||
@@ -82,17 +96,28 @@ impl BreakpointList {
|
||||
focus_handle,
|
||||
scroll_handle,
|
||||
selected_ix: None,
|
||||
input: cx.new(|cx| Editor::single_line(window, cx)),
|
||||
strip_mode: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn edit_line_breakpoint(
|
||||
&mut self,
|
||||
&self,
|
||||
path: Arc<Path>,
|
||||
row: u32,
|
||||
action: BreakpointEditAction,
|
||||
cx: &mut Context<Self>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
self.breakpoint_store.update(cx, |breakpoint_store, cx| {
|
||||
Self::edit_line_breakpoint_inner(&self.breakpoint_store, path, row, action, cx);
|
||||
}
|
||||
fn edit_line_breakpoint_inner(
|
||||
breakpoint_store: &Entity<BreakpointStore>,
|
||||
path: Arc<Path>,
|
||||
row: u32,
|
||||
action: BreakpointEditAction,
|
||||
cx: &mut App,
|
||||
) {
|
||||
breakpoint_store.update(cx, |breakpoint_store, cx| {
|
||||
if let Some((buffer, breakpoint)) = breakpoint_store.breakpoint_at_row(&path, row, cx) {
|
||||
breakpoint_store.toggle_breakpoint(buffer, breakpoint, action, cx);
|
||||
} else {
|
||||
@@ -148,16 +173,63 @@ impl BreakpointList {
|
||||
})
|
||||
}
|
||||
|
||||
fn select_ix(&mut self, ix: Option<usize>, cx: &mut Context<Self>) {
|
||||
fn set_active_breakpoint_property(
|
||||
&mut self,
|
||||
prop: ActiveBreakpointStripMode,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
self.strip_mode = Some(prop);
|
||||
let placeholder = match prop {
|
||||
ActiveBreakpointStripMode::Log => "Set Log Message",
|
||||
ActiveBreakpointStripMode::Condition => "Set Condition",
|
||||
ActiveBreakpointStripMode::HitCondition => "Set Hit Condition",
|
||||
};
|
||||
let mut is_exception_breakpoint = true;
|
||||
let active_value = self.selected_ix.and_then(|ix| {
|
||||
self.breakpoints.get(ix).and_then(|bp| {
|
||||
if let BreakpointEntryKind::LineBreakpoint(bp) = &bp.kind {
|
||||
is_exception_breakpoint = false;
|
||||
match prop {
|
||||
ActiveBreakpointStripMode::Log => bp.breakpoint.message.clone(),
|
||||
ActiveBreakpointStripMode::Condition => bp.breakpoint.condition.clone(),
|
||||
ActiveBreakpointStripMode::HitCondition => {
|
||||
bp.breakpoint.hit_condition.clone()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
self.input.update(cx, |this, cx| {
|
||||
this.set_placeholder_text(placeholder, cx);
|
||||
this.set_read_only(is_exception_breakpoint);
|
||||
this.set_text(active_value.as_deref().unwrap_or(""), window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn select_ix(&mut self, ix: Option<usize>, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.selected_ix = ix;
|
||||
if let Some(ix) = ix {
|
||||
self.scroll_handle
|
||||
.scroll_to_item(ix, ScrollStrategy::Center);
|
||||
}
|
||||
if let Some(mode) = self.strip_mode {
|
||||
self.set_active_breakpoint_property(mode, window, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_next(&mut self, _: &menu::SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.strip_mode.is_some() {
|
||||
if self.input.focus_handle(cx).contains_focused(window, cx) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
let ix = match self.selected_ix {
|
||||
_ if self.breakpoints.len() == 0 => None,
|
||||
None => Some(0),
|
||||
@@ -169,15 +241,21 @@ impl BreakpointList {
|
||||
}
|
||||
}
|
||||
};
|
||||
self.select_ix(ix, cx);
|
||||
self.select_ix(ix, window, cx);
|
||||
}
|
||||
|
||||
fn select_previous(
|
||||
&mut self,
|
||||
_: &menu::SelectPrevious,
|
||||
_window: &mut Window,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.strip_mode.is_some() {
|
||||
if self.input.focus_handle(cx).contains_focused(window, cx) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
let ix = match self.selected_ix {
|
||||
_ if self.breakpoints.len() == 0 => None,
|
||||
None => Some(self.breakpoints.len() - 1),
|
||||
@@ -189,37 +267,105 @@ impl BreakpointList {
|
||||
}
|
||||
}
|
||||
};
|
||||
self.select_ix(ix, cx);
|
||||
self.select_ix(ix, window, cx);
|
||||
}
|
||||
|
||||
fn select_first(
|
||||
&mut self,
|
||||
_: &menu::SelectFirst,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.strip_mode.is_some() {
|
||||
if self.input.focus_handle(cx).contains_focused(window, cx) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
let ix = if self.breakpoints.len() > 0 {
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.select_ix(ix, cx);
|
||||
self.select_ix(ix, window, cx);
|
||||
}
|
||||
|
||||
fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.strip_mode.is_some() {
|
||||
if self.input.focus_handle(cx).contains_focused(window, cx) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
let ix = if self.breakpoints.len() > 0 {
|
||||
Some(self.breakpoints.len() - 1)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.select_ix(ix, cx);
|
||||
self.select_ix(ix, window, cx);
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.input.focus_handle(cx).contains_focused(window, cx) {
|
||||
self.focus_handle.focus(window);
|
||||
} else if self.strip_mode.is_some() {
|
||||
self.strip_mode.take();
|
||||
cx.notify();
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(mode) = self.strip_mode {
|
||||
let handle = self.input.focus_handle(cx);
|
||||
if handle.is_focused(window) {
|
||||
// Go back to the main strip. Save the result as well.
|
||||
let text = self.input.read(cx).text(cx);
|
||||
|
||||
match mode {
|
||||
ActiveBreakpointStripMode::Log => match &entry.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
Self::edit_line_breakpoint_inner(
|
||||
&self.breakpoint_store,
|
||||
line_breakpoint.breakpoint.path.clone(),
|
||||
line_breakpoint.breakpoint.row,
|
||||
BreakpointEditAction::EditLogMessage(Arc::from(text)),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
ActiveBreakpointStripMode::Condition => match &entry.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
Self::edit_line_breakpoint_inner(
|
||||
&self.breakpoint_store,
|
||||
line_breakpoint.breakpoint.path.clone(),
|
||||
line_breakpoint.breakpoint.row,
|
||||
BreakpointEditAction::EditCondition(Arc::from(text)),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
ActiveBreakpointStripMode::HitCondition => match &entry.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
Self::edit_line_breakpoint_inner(
|
||||
&self.breakpoint_store,
|
||||
line_breakpoint.breakpoint.path.clone(),
|
||||
line_breakpoint.breakpoint.row,
|
||||
BreakpointEditAction::EditHitCondition(Arc::from(text)),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
self.focus_handle.focus(window);
|
||||
} else {
|
||||
handle.focus(window);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
match &mut entry.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
let path = line_breakpoint.breakpoint.path.clone();
|
||||
@@ -233,12 +379,18 @@ impl BreakpointList {
|
||||
fn toggle_enable_breakpoint(
|
||||
&mut self,
|
||||
_: &ToggleEnableBreakpoint,
|
||||
_window: &mut Window,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else {
|
||||
return;
|
||||
};
|
||||
if self.strip_mode.is_some() {
|
||||
if self.input.focus_handle(cx).contains_focused(window, cx) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
match &mut entry.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
@@ -279,6 +431,50 @@ impl BreakpointList {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn previous_breakpoint_property(
|
||||
&mut self,
|
||||
_: &PreviousBreakpointProperty,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let next_mode = match self.strip_mode {
|
||||
Some(ActiveBreakpointStripMode::Log) => None,
|
||||
Some(ActiveBreakpointStripMode::Condition) => Some(ActiveBreakpointStripMode::Log),
|
||||
Some(ActiveBreakpointStripMode::HitCondition) => {
|
||||
Some(ActiveBreakpointStripMode::Condition)
|
||||
}
|
||||
None => Some(ActiveBreakpointStripMode::HitCondition),
|
||||
};
|
||||
if let Some(mode) = next_mode {
|
||||
self.set_active_breakpoint_property(mode, window, cx);
|
||||
} else {
|
||||
self.strip_mode.take();
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
fn next_breakpoint_property(
|
||||
&mut self,
|
||||
_: &NextBreakpointProperty,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let next_mode = match self.strip_mode {
|
||||
Some(ActiveBreakpointStripMode::Log) => Some(ActiveBreakpointStripMode::Condition),
|
||||
Some(ActiveBreakpointStripMode::Condition) => {
|
||||
Some(ActiveBreakpointStripMode::HitCondition)
|
||||
}
|
||||
Some(ActiveBreakpointStripMode::HitCondition) => None,
|
||||
None => Some(ActiveBreakpointStripMode::Log),
|
||||
};
|
||||
if let Some(mode) = next_mode {
|
||||
self.set_active_breakpoint_property(mode, window, cx);
|
||||
} else {
|
||||
self.strip_mode.take();
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
||||
self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |panel, cx| {
|
||||
@@ -294,20 +490,31 @@ impl BreakpointList {
|
||||
}))
|
||||
}
|
||||
|
||||
fn render_list(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render_list(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let selected_ix = self.selected_ix;
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let supported_breakpoint_properties = self
|
||||
.session
|
||||
.as_ref()
|
||||
.map(|session| SupportedBreakpointProperties::from(session.read(cx).capabilities()))
|
||||
.unwrap_or_else(SupportedBreakpointProperties::empty);
|
||||
let strip_mode = self.strip_mode;
|
||||
uniform_list(
|
||||
"breakpoint-list",
|
||||
self.breakpoints.len(),
|
||||
cx.processor(move |this, range: Range<usize>, window, cx| {
|
||||
cx.processor(move |this, range: Range<usize>, _, _| {
|
||||
range
|
||||
.clone()
|
||||
.zip(&mut this.breakpoints[range])
|
||||
.map(|(ix, breakpoint)| {
|
||||
breakpoint
|
||||
.render(ix, focus_handle.clone(), window, cx)
|
||||
.toggle_state(Some(ix) == selected_ix)
|
||||
.render(
|
||||
strip_mode,
|
||||
supported_breakpoint_properties,
|
||||
ix,
|
||||
Some(ix) == selected_ix,
|
||||
focus_handle.clone(),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
.collect()
|
||||
@@ -443,7 +650,6 @@ impl BreakpointList {
|
||||
|
||||
impl Render for BreakpointList {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
|
||||
// let old_len = self.breakpoints.len();
|
||||
let breakpoints = self.breakpoint_store.read(cx).all_source_breakpoints(cx);
|
||||
self.breakpoints.clear();
|
||||
let weak = cx.weak_entity();
|
||||
@@ -523,15 +729,46 @@ impl Render for BreakpointList {
|
||||
.on_action(cx.listener(Self::select_previous))
|
||||
.on_action(cx.listener(Self::select_first))
|
||||
.on_action(cx.listener(Self::select_last))
|
||||
.on_action(cx.listener(Self::dismiss))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::toggle_enable_breakpoint))
|
||||
.on_action(cx.listener(Self::unset_breakpoint))
|
||||
.on_action(cx.listener(Self::next_breakpoint_property))
|
||||
.on_action(cx.listener(Self::previous_breakpoint_property))
|
||||
.size_full()
|
||||
.m_0p5()
|
||||
.child(self.render_list(window, cx))
|
||||
.children(self.render_vertical_scrollbar(cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.child(self.render_list(cx))
|
||||
.children(self.render_vertical_scrollbar(cx)),
|
||||
)
|
||||
.when_some(self.strip_mode, |this, _| {
|
||||
this.child(Divider::horizontal()).child(
|
||||
h_flex()
|
||||
// .w_full()
|
||||
.m_0p5()
|
||||
.p_0p5()
|
||||
.border_1()
|
||||
.rounded_sm()
|
||||
.when(
|
||||
self.input.focus_handle(cx).contains_focused(window, cx),
|
||||
|this| {
|
||||
let colors = cx.theme().colors();
|
||||
let border = if self.input.read(cx).read_only(cx) {
|
||||
colors.border_disabled
|
||||
} else {
|
||||
colors.border_focused
|
||||
};
|
||||
this.border_color(border)
|
||||
},
|
||||
)
|
||||
.child(self.input.clone()),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct LineBreakpoint {
|
||||
name: SharedString,
|
||||
@@ -543,7 +780,10 @@ struct LineBreakpoint {
|
||||
impl LineBreakpoint {
|
||||
fn render(
|
||||
&mut self,
|
||||
props: SupportedBreakpointProperties,
|
||||
strip_mode: Option<ActiveBreakpointStripMode>,
|
||||
ix: usize,
|
||||
is_selected: bool,
|
||||
focus_handle: FocusHandle,
|
||||
weak: WeakEntity<BreakpointList>,
|
||||
) -> ListItem {
|
||||
@@ -594,15 +834,16 @@ impl LineBreakpoint {
|
||||
})
|
||||
.child(Indicator::icon(Icon::new(icon_name)).color(Color::Debugger))
|
||||
.on_mouse_down(MouseButton::Left, move |_, _, _| {});
|
||||
|
||||
ListItem::new(SharedString::from(format!(
|
||||
"breakpoint-ui-item-{:?}/{}:{}",
|
||||
self.dir, self.name, self.line
|
||||
)))
|
||||
.on_click({
|
||||
let weak = weak.clone();
|
||||
move |_, _, cx| {
|
||||
move |_, window, cx| {
|
||||
weak.update(cx, |breakpoint_list, cx| {
|
||||
breakpoint_list.select_ix(Some(ix), cx);
|
||||
breakpoint_list.select_ix(Some(ix), window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -613,39 +854,67 @@ impl LineBreakpoint {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
.py_1()
|
||||
h_flex()
|
||||
.w_full()
|
||||
.mr_4()
|
||||
.py_0p5()
|
||||
.gap_1()
|
||||
.min_h(px(26.))
|
||||
.justify_center()
|
||||
.justify_between()
|
||||
.id(SharedString::from(format!(
|
||||
"breakpoint-ui-on-click-go-to-line-{:?}/{}:{}",
|
||||
self.dir, self.name, self.line
|
||||
)))
|
||||
.on_click(move |_, window, cx| {
|
||||
weak.update(cx, |breakpoint_list, cx| {
|
||||
breakpoint_list.select_ix(Some(ix), cx);
|
||||
breakpoint_list.go_to_line_breakpoint(path.clone(), row, window, cx);
|
||||
})
|
||||
.ok();
|
||||
.on_click({
|
||||
let weak = weak.clone();
|
||||
move |_, window, cx| {
|
||||
weak.update(cx, |breakpoint_list, cx| {
|
||||
breakpoint_list.select_ix(Some(ix), window, cx);
|
||||
breakpoint_list.go_to_line_breakpoint(path.clone(), row, window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.cursor_pointer()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Label::new(format!("{}:{}", self.name, self.line))
|
||||
.size(LabelSize::Small)
|
||||
.line_height_style(ui::LineHeightStyle::UiLabel),
|
||||
)
|
||||
.children(self.dir.clone().map(|dir| {
|
||||
Label::new(dir)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.line_height_style(ui::LineHeightStyle::UiLabel)
|
||||
.children(self.dir.as_ref().and_then(|dir| {
|
||||
let path_without_root = Path::new(dir.as_ref())
|
||||
.components()
|
||||
.skip(1)
|
||||
.collect::<PathBuf>();
|
||||
path_without_root.components().next()?;
|
||||
Some(
|
||||
Label::new(path_without_root.to_string_lossy().into_owned())
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.line_height_style(ui::LineHeightStyle::UiLabel)
|
||||
.truncate(),
|
||||
)
|
||||
})),
|
||||
),
|
||||
)
|
||||
.when_some(self.dir.as_ref(), |this, parent_dir| {
|
||||
this.tooltip(Tooltip::text(format!("Worktree parent path: {parent_dir}")))
|
||||
})
|
||||
.child(BreakpointOptionsStrip {
|
||||
props,
|
||||
breakpoint: BreakpointEntry {
|
||||
kind: BreakpointEntryKind::LineBreakpoint(self.clone()),
|
||||
weak: weak,
|
||||
},
|
||||
is_selected,
|
||||
focus_handle,
|
||||
strip_mode,
|
||||
index: ix,
|
||||
}),
|
||||
)
|
||||
.toggle_state(is_selected)
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -658,7 +927,10 @@ struct ExceptionBreakpoint {
|
||||
impl ExceptionBreakpoint {
|
||||
fn render(
|
||||
&mut self,
|
||||
props: SupportedBreakpointProperties,
|
||||
strip_mode: Option<ActiveBreakpointStripMode>,
|
||||
ix: usize,
|
||||
is_selected: bool,
|
||||
focus_handle: FocusHandle,
|
||||
list: WeakEntity<BreakpointList>,
|
||||
) -> ListItem {
|
||||
@@ -669,15 +941,15 @@ impl ExceptionBreakpoint {
|
||||
};
|
||||
let id = SharedString::from(&self.id);
|
||||
let is_enabled = self.is_enabled;
|
||||
|
||||
let weak = list.clone();
|
||||
ListItem::new(SharedString::from(format!(
|
||||
"exception-breakpoint-ui-item-{}",
|
||||
self.id
|
||||
)))
|
||||
.on_click({
|
||||
let list = list.clone();
|
||||
move |_, _, cx| {
|
||||
list.update(cx, |list, cx| list.select_ix(Some(ix), cx))
|
||||
move |_, window, cx| {
|
||||
list.update(cx, |list, cx| list.select_ix(Some(ix), window, cx))
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
@@ -691,18 +963,21 @@ impl ExceptionBreakpoint {
|
||||
"exception-breakpoint-ui-item-{}-click-handler",
|
||||
self.id
|
||||
)))
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
if is_enabled {
|
||||
"Disable Exception Breakpoint"
|
||||
} else {
|
||||
"Enable Exception Breakpoint"
|
||||
},
|
||||
&ToggleEnableBreakpoint,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
if is_enabled {
|
||||
"Disable Exception Breakpoint"
|
||||
} else {
|
||||
"Enable Exception Breakpoint"
|
||||
},
|
||||
&ToggleEnableBreakpoint,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click({
|
||||
let list = list.clone();
|
||||
@@ -722,21 +997,40 @@ impl ExceptionBreakpoint {
|
||||
.child(Indicator::icon(Icon::new(IconName::Flame)).color(color)),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.py_1()
|
||||
.gap_1()
|
||||
.min_h(px(26.))
|
||||
.justify_center()
|
||||
.id(("exception-breakpoint-label", ix))
|
||||
h_flex()
|
||||
.w_full()
|
||||
.mr_4()
|
||||
.py_0p5()
|
||||
.justify_between()
|
||||
.child(
|
||||
Label::new(self.data.label.clone())
|
||||
.size(LabelSize::Small)
|
||||
.line_height_style(ui::LineHeightStyle::UiLabel),
|
||||
v_flex()
|
||||
.py_1()
|
||||
.gap_1()
|
||||
.min_h(px(26.))
|
||||
.justify_center()
|
||||
.id(("exception-breakpoint-label", ix))
|
||||
.child(
|
||||
Label::new(self.data.label.clone())
|
||||
.size(LabelSize::Small)
|
||||
.line_height_style(ui::LineHeightStyle::UiLabel),
|
||||
)
|
||||
.when_some(self.data.description.clone(), |el, description| {
|
||||
el.tooltip(Tooltip::text(description))
|
||||
}),
|
||||
)
|
||||
.when_some(self.data.description.clone(), |el, description| {
|
||||
el.tooltip(Tooltip::text(description))
|
||||
.child(BreakpointOptionsStrip {
|
||||
props,
|
||||
breakpoint: BreakpointEntry {
|
||||
kind: BreakpointEntryKind::ExceptionBreakpoint(self.clone()),
|
||||
weak: weak,
|
||||
},
|
||||
is_selected,
|
||||
focus_handle,
|
||||
strip_mode,
|
||||
index: ix,
|
||||
}),
|
||||
)
|
||||
.toggle_state(is_selected)
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -754,18 +1048,267 @@ struct BreakpointEntry {
|
||||
impl BreakpointEntry {
|
||||
fn render(
|
||||
&mut self,
|
||||
strip_mode: Option<ActiveBreakpointStripMode>,
|
||||
props: SupportedBreakpointProperties,
|
||||
ix: usize,
|
||||
is_selected: bool,
|
||||
focus_handle: FocusHandle,
|
||||
_: &mut Window,
|
||||
_: &mut App,
|
||||
) -> ListItem {
|
||||
match &mut self.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => line_breakpoint.render(
|
||||
props,
|
||||
strip_mode,
|
||||
ix,
|
||||
is_selected,
|
||||
focus_handle,
|
||||
self.weak.clone(),
|
||||
),
|
||||
BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => exception_breakpoint
|
||||
.render(
|
||||
props.for_exception_breakpoints(),
|
||||
strip_mode,
|
||||
ix,
|
||||
is_selected,
|
||||
focus_handle,
|
||||
self.weak.clone(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&self) -> SharedString {
|
||||
match &self.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => format!(
|
||||
"source-breakpoint-control-strip-{:?}:{}",
|
||||
line_breakpoint.breakpoint.path, line_breakpoint.breakpoint.row
|
||||
)
|
||||
.into(),
|
||||
BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => format!(
|
||||
"exception-breakpoint-control-strip--{}",
|
||||
exception_breakpoint.id
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn has_log(&self) -> bool {
|
||||
match &self.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
line_breakpoint.render(ix, focus_handle, self.weak.clone())
|
||||
line_breakpoint.breakpoint.message.is_some()
|
||||
}
|
||||
BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => {
|
||||
exception_breakpoint.render(ix, focus_handle, self.weak.clone())
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_condition(&self) -> bool {
|
||||
match &self.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
line_breakpoint.breakpoint.condition.is_some()
|
||||
}
|
||||
// We don't support conditions on exception breakpoints
|
||||
BreakpointEntryKind::ExceptionBreakpoint(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_hit_condition(&self) -> bool {
|
||||
match &self.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
line_breakpoint.breakpoint.hit_condition.is_some()
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SupportedBreakpointProperties: u32 {
|
||||
const LOG = 1 << 0;
|
||||
const CONDITION = 1 << 1;
|
||||
const HIT_CONDITION = 1 << 2;
|
||||
// Conditions for exceptions can be set only when exception filters are supported.
|
||||
const EXCEPTION_FILTER_OPTIONS = 1 << 3;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Capabilities> for SupportedBreakpointProperties {
|
||||
fn from(caps: &Capabilities) -> Self {
|
||||
let mut this = Self::empty();
|
||||
for (prop, offset) in [
|
||||
(caps.supports_log_points, Self::LOG),
|
||||
(caps.supports_conditional_breakpoints, Self::CONDITION),
|
||||
(
|
||||
caps.supports_hit_conditional_breakpoints,
|
||||
Self::HIT_CONDITION,
|
||||
),
|
||||
(
|
||||
caps.supports_exception_options,
|
||||
Self::EXCEPTION_FILTER_OPTIONS,
|
||||
),
|
||||
] {
|
||||
if prop.unwrap_or_default() {
|
||||
this.insert(offset);
|
||||
}
|
||||
}
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
impl SupportedBreakpointProperties {
|
||||
fn for_exception_breakpoints(self) -> Self {
|
||||
// TODO: we don't yet support conditions for exception breakpoints at the data layer, hence all props are disabled here.
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
#[derive(IntoElement)]
|
||||
struct BreakpointOptionsStrip {
|
||||
props: SupportedBreakpointProperties,
|
||||
breakpoint: BreakpointEntry,
|
||||
is_selected: bool,
|
||||
focus_handle: FocusHandle,
|
||||
strip_mode: Option<ActiveBreakpointStripMode>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl BreakpointOptionsStrip {
|
||||
fn is_toggled(&self, expected_mode: ActiveBreakpointStripMode) -> bool {
|
||||
self.is_selected && self.strip_mode == Some(expected_mode)
|
||||
}
|
||||
fn on_click_callback(
|
||||
&self,
|
||||
mode: ActiveBreakpointStripMode,
|
||||
) -> impl for<'a> Fn(&ClickEvent, &mut Window, &'a mut App) + use<> {
|
||||
let list = self.breakpoint.weak.clone();
|
||||
let ix = self.index;
|
||||
move |_, window, cx| {
|
||||
list.update(cx, |this, cx| {
|
||||
if this.strip_mode != Some(mode) {
|
||||
this.set_active_breakpoint_property(mode, window, cx);
|
||||
} else if this.selected_ix == Some(ix) {
|
||||
this.strip_mode.take();
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
fn add_border(
|
||||
&self,
|
||||
kind: ActiveBreakpointStripMode,
|
||||
available: bool,
|
||||
window: &Window,
|
||||
cx: &App,
|
||||
) -> impl Fn(Div) -> Div {
|
||||
move |this: Div| {
|
||||
// Avoid layout shifts in case there's no colored border
|
||||
let this = this.border_2().rounded_sm();
|
||||
if self.is_selected && self.strip_mode == Some(kind) {
|
||||
let theme = cx.theme().colors();
|
||||
if self.focus_handle.is_focused(window) {
|
||||
this.border_color(theme.border_selected)
|
||||
} else {
|
||||
this.border_color(theme.border_disabled)
|
||||
}
|
||||
} else if !available {
|
||||
this.border_color(cx.theme().colors().border_disabled)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl RenderOnce for BreakpointOptionsStrip {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let id = self.breakpoint.id();
|
||||
let supports_logs = self.props.contains(SupportedBreakpointProperties::LOG);
|
||||
let supports_condition = self
|
||||
.props
|
||||
.contains(SupportedBreakpointProperties::CONDITION);
|
||||
let supports_hit_condition = self
|
||||
.props
|
||||
.contains(SupportedBreakpointProperties::HIT_CONDITION);
|
||||
let has_logs = self.breakpoint.has_log();
|
||||
let has_condition = self.breakpoint.has_condition();
|
||||
let has_hit_condition = self.breakpoint.has_hit_condition();
|
||||
let style_for_toggle = |mode, is_enabled| {
|
||||
if is_enabled && self.strip_mode == Some(mode) && self.is_selected {
|
||||
ui::ButtonStyle::Filled
|
||||
} else {
|
||||
ui::ButtonStyle::Subtle
|
||||
}
|
||||
};
|
||||
let color_for_toggle = |is_enabled| {
|
||||
if is_enabled {
|
||||
ui::Color::Default
|
||||
} else {
|
||||
ui::Color::Muted
|
||||
}
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
div().map(self.add_border(ActiveBreakpointStripMode::Log, supports_logs, window, cx))
|
||||
.child(
|
||||
IconButton::new(
|
||||
SharedString::from(format!("{id}-log-toggle")),
|
||||
IconName::ScrollText,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.style(style_for_toggle(ActiveBreakpointStripMode::Log, has_logs))
|
||||
.icon_color(color_for_toggle(has_logs))
|
||||
.disabled(!supports_logs)
|
||||
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::Log))
|
||||
.on_click(self.on_click_callback(ActiveBreakpointStripMode::Log)).tooltip(|window, cx| Tooltip::with_meta("Set Log Message", None, "Set log message to display (instead of stopping) when a breakpoint is hit", window, cx))
|
||||
)
|
||||
.when(!has_logs && !self.is_selected, |this| this.invisible()),
|
||||
)
|
||||
.child(
|
||||
div().map(self.add_border(
|
||||
ActiveBreakpointStripMode::Condition,
|
||||
supports_condition,
|
||||
window, cx
|
||||
))
|
||||
.child(
|
||||
IconButton::new(
|
||||
SharedString::from(format!("{id}-condition-toggle")),
|
||||
IconName::SplitAlt,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.style(style_for_toggle(
|
||||
ActiveBreakpointStripMode::Condition,
|
||||
has_condition
|
||||
))
|
||||
.icon_color(color_for_toggle(has_condition))
|
||||
.disabled(!supports_condition)
|
||||
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::Condition))
|
||||
.on_click(self.on_click_callback(ActiveBreakpointStripMode::Condition))
|
||||
.tooltip(|window, cx| Tooltip::with_meta("Set Condition", None, "Set condition to evaluate when a breakpoint is hit. Program execution will stop only when the condition is met", window, cx))
|
||||
)
|
||||
.when(!has_condition && !self.is_selected, |this| this.invisible()),
|
||||
)
|
||||
.child(
|
||||
div().map(self.add_border(
|
||||
ActiveBreakpointStripMode::HitCondition,
|
||||
supports_hit_condition,window, cx
|
||||
))
|
||||
.child(
|
||||
IconButton::new(
|
||||
SharedString::from(format!("{id}-hit-condition-toggle")),
|
||||
IconName::ArrowDown10,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.style(style_for_toggle(
|
||||
ActiveBreakpointStripMode::HitCondition,
|
||||
has_hit_condition,
|
||||
))
|
||||
.icon_color(color_for_toggle(has_hit_condition))
|
||||
.disabled(!supports_hit_condition)
|
||||
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::HitCondition))
|
||||
.on_click(self.on_click_callback(ActiveBreakpointStripMode::HitCondition)).tooltip(|window, cx| Tooltip::with_meta("Set Hit Condition", None, "Set expression that controls how many hits of the breakpoint are ignored.", window, cx))
|
||||
)
|
||||
.when(!has_hit_condition && !self.is_selected, |this| {
|
||||
this.invisible()
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ impl Console {
|
||||
}
|
||||
|
||||
fn is_running(&self, cx: &Context<Self>) -> bool {
|
||||
self.session.read(cx).is_running()
|
||||
self.session.read(cx).is_started()
|
||||
}
|
||||
|
||||
fn handle_stack_frame_list_events(
|
||||
|
||||
@@ -4,7 +4,7 @@ use collections::HashMap;
|
||||
use dap::StackFrameId;
|
||||
use editor::{
|
||||
Anchor, Bias, DebugStackFrameLine, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer,
|
||||
RowHighlightOptions, ToPoint, scroll::Autoscroll,
|
||||
RowHighlightOptions, SelectionEffects, ToPoint, scroll::Autoscroll,
|
||||
};
|
||||
use gpui::{
|
||||
AnyView, App, AppContext, Entity, EventEmitter, Focusable, IntoElement, Render, SharedString,
|
||||
@@ -99,10 +99,11 @@ impl StackTraceView {
|
||||
if frame_anchor.excerpt_id
|
||||
!= editor.selections.newest_anchor().head().excerpt_id
|
||||
{
|
||||
let auto_scroll =
|
||||
Some(Autoscroll::center().for_anchor(frame_anchor));
|
||||
let effects = SelectionEffects::scroll(
|
||||
Autoscroll::center().for_anchor(frame_anchor),
|
||||
);
|
||||
|
||||
editor.change_selections(auto_scroll, window, cx, |selections| {
|
||||
editor.change_selections(effects, window, cx, |selections| {
|
||||
let selection_id = selections.new_selection_id();
|
||||
|
||||
let selection = Selection {
|
||||
|
||||