Compare commits
155 Commits
v0.181.6
...
hover-copy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e49727296 | ||
|
|
df3c7a73b5 | ||
|
|
0dc3dffe38 | ||
|
|
b306a0221b | ||
|
|
d385a60ed1 | ||
|
|
b27922129c | ||
|
|
6220b86f94 | ||
|
|
448db20eaa | ||
|
|
e4a6943c76 | ||
|
|
f03efeda73 | ||
|
|
862d0c07ca | ||
|
|
56ed5dcc89 | ||
|
|
b3f47dc5e0 | ||
|
|
fe1ae1860e | ||
|
|
c165729b3f | ||
|
|
22b937f27f | ||
|
|
fdaf2a27bf | ||
|
|
0414908c4a | ||
|
|
d3abc61728 | ||
|
|
e7a0f0e876 | ||
|
|
5996c58452 | ||
|
|
b6ee367ee0 | ||
|
|
aa026156f2 | ||
|
|
d5cc576b0c | ||
|
|
f3274851d9 | ||
|
|
500d8f2943 | ||
|
|
e3830d2ef5 | ||
|
|
4f9f443452 | ||
|
|
1556b446e7 | ||
|
|
5ca8a3e342 | ||
|
|
aeea3645ff | ||
|
|
a577a72f69 | ||
|
|
5a7222edc5 | ||
|
|
097aefeac4 | ||
|
|
99a9647b78 | ||
|
|
fa90b3a986 | ||
|
|
8049fc1038 | ||
|
|
85b811a783 | ||
|
|
d60dbbc791 | ||
|
|
656302ee4c | ||
|
|
956f359045 | ||
|
|
3b46fca64c | ||
|
|
d6d9c383cb | ||
|
|
8cfb9beb17 | ||
|
|
0708d476ca | ||
|
|
57669b4908 | ||
|
|
b1f7133a7b | ||
|
|
ac9e2f30bb | ||
|
|
a2fbe82c42 | ||
|
|
57d8c99473 | ||
|
|
41827372fe | ||
|
|
9949512b64 | ||
|
|
b9051e65d4 | ||
|
|
adbebb28dc | ||
|
|
caf0d6c5fa | ||
|
|
525755c28e | ||
|
|
ea0f5144c9 | ||
|
|
b78ac5410f | ||
|
|
2462b949bc | ||
|
|
ec7d28648a | ||
|
|
c1259c136e | ||
|
|
6ddad64af1 | ||
|
|
69d7ea7b60 | ||
|
|
d0e82b0538 | ||
|
|
10821aae2c | ||
|
|
03aadb4e5b | ||
|
|
8ab252c42d | ||
|
|
e74af03065 | ||
|
|
4bcd37a537 | ||
|
|
e3d212ac60 | ||
|
|
8b077f0c41 | ||
|
|
288da0f072 | ||
|
|
b8d05bb641 | ||
|
|
02e4267bc6 | ||
|
|
c2afc2271b | ||
|
|
7bc62de267 | ||
|
|
f3adf41c25 | ||
|
|
6162d9942d | ||
|
|
156dd32a35 | ||
|
|
2747915569 | ||
|
|
75b9a3b6a8 | ||
|
|
9bd3dbcf28 | ||
|
|
435fff94bd | ||
|
|
558d61b907 | ||
|
|
02a8ece074 | ||
|
|
1a899fda60 | ||
|
|
183f57f318 | ||
|
|
cc9cc12f7b | ||
|
|
1bc5618f61 | ||
|
|
ef8fe52877 | ||
|
|
982196343f | ||
|
|
cfe5620a2a | ||
|
|
5fe86f7e70 | ||
|
|
c94b587e1a | ||
|
|
43cb925a59 | ||
|
|
cf0d1e4229 | ||
|
|
2f5a4f7e80 | ||
|
|
80441f675b | ||
|
|
393d6560a3 | ||
|
|
3d48efad67 | ||
|
|
277a3f8d6f | ||
|
|
5e286897d3 | ||
|
|
9e38c45a9b | ||
|
|
1db3d92066 | ||
|
|
a7674d3edc | ||
|
|
ee4b6a8db4 | ||
|
|
c04c5812b6 | ||
|
|
cba96b5a38 | ||
|
|
8b5ea05163 | ||
|
|
ec40e2d85c | ||
|
|
819bb8fffb | ||
|
|
c6e2d20a02 | ||
|
|
7492ec3f67 | ||
|
|
4d8df0a00b | ||
|
|
3f71ae9897 | ||
|
|
2086f7d85b | ||
|
|
315f1bf168 | ||
|
|
0c82541f0a | ||
|
|
b9724d9cbe | ||
|
|
e5b347b03a | ||
|
|
e123c4bced | ||
|
|
ed3722023e | ||
|
|
ece4a1cd7c | ||
|
|
9986a21970 | ||
|
|
c674e8d62d | ||
|
|
e5e3e9ac8c | ||
|
|
399d19231b | ||
|
|
c98bcc72b8 | ||
|
|
fe27d11f08 | ||
|
|
9693eab098 | ||
|
|
e2aaf9b704 | ||
|
|
9abfbdff43 | ||
|
|
cd85b430e4 | ||
|
|
d3e4de7c72 | ||
|
|
ee950f5bc4 | ||
|
|
501b539286 | ||
|
|
444b7b8acb | ||
|
|
8a6ed4a2ca | ||
|
|
b4af5b2ce0 | ||
|
|
ee33d313e2 | ||
|
|
0a132779a1 | ||
|
|
d23c2d4b02 | ||
|
|
b9f10c0adb | ||
|
|
9f9746872e | ||
|
|
01ec6e0f77 | ||
|
|
07a77792c5 | ||
|
|
108ae0b5b0 | ||
|
|
500964a6fa | ||
|
|
0a58e54477 | ||
|
|
8539e23018 | ||
|
|
c7d27753ee | ||
|
|
b7b7f1ccdd | ||
|
|
142f9917d0 | ||
|
|
f8092bf0d2 | ||
|
|
0ba8432b0b |
51
.github/ISSUE_TEMPLATE/00_edit_predictions_bug_report.yml
vendored
Normal file
51
.github/ISSUE_TEMPLATE/00_edit_predictions_bug_report.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Edit Predictions Bug Report
|
||||
description: There is a bug related to Edit Predictions in Zed
|
||||
type: "Bug"
|
||||
labels: ["ai", "inline completion", "zeta"]
|
||||
title: "Edit Predictions: <a short description of the Edit Prediction bug>"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
|
||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
|
||||
Expected Behavior:
|
||||
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed Version and System Specs
|
||||
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
||||
placeholder: |
|
||||
Output of "zed: Copy System Specs Into Clipboard"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
description: |
|
||||
macOS: `~/Library/Logs/Zed/Zed.log`
|
||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
value: |
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
```
|
||||
|
||||
```
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
||||
validations:
|
||||
required: false
|
||||
51
.github/ISSUE_TEMPLATE/01_git_bug_report.yml
vendored
Normal file
51
.github/ISSUE_TEMPLATE/01_git_bug_report.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Git Bug Report
|
||||
description: There is a bug related to Git features in Zed
|
||||
type: "Bug"
|
||||
labels: ["git"]
|
||||
title: "Git: <a short description of the Git bug>"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
|
||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
|
||||
Expected Behavior:
|
||||
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed Version and System Specs
|
||||
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
||||
placeholder: |
|
||||
Output of "zed: Copy System Specs Into Clipboard"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
description: |
|
||||
macOS: `~/Library/Logs/Zed/Zed.log`
|
||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
value: |
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
```
|
||||
|
||||
```
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
||||
validations:
|
||||
required: false
|
||||
51
.github/ISSUE_TEMPLATE/02_agent_bug_report.yml
vendored
Normal file
51
.github/ISSUE_TEMPLATE/02_agent_bug_report.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Agent Panel Bug Report
|
||||
description: There is a bug related to the Agent Panel in Zed
|
||||
type: "Bug"
|
||||
labels: ["agent", "ai"]
|
||||
title: "Agent Panel: <a short description of the Agent Panel bug>"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
|
||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
|
||||
Expected Behavior:
|
||||
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed Version and System Specs
|
||||
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
||||
placeholder: |
|
||||
Output of "zed: Copy System Specs Into Clipboard"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
description: |
|
||||
macOS: `~/Library/Logs/Zed/Zed.log`
|
||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
value: |
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
```
|
||||
|
||||
```
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
||||
validations:
|
||||
required: false
|
||||
2
.github/actions/run_tests_windows/action.yml
vendored
2
.github/actions/run_tests_windows/action.yml
vendored
@@ -23,4 +23,4 @@ runs:
|
||||
- name: Run tests
|
||||
shell: pwsh
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: cargo nextest run --workspace --no-fail-fast
|
||||
run: cargo nextest run --workspace --no-fail-fast --config='profile.dev.debug="limited"'
|
||||
|
||||
49
.github/workflows/ci.yml
vendored
49
.github/workflows/ci.yml
vendored
@@ -131,13 +131,13 @@ jobs:
|
||||
- name: Check workspace-hack Cargo.toml is up-to-date
|
||||
run: |
|
||||
cargo hakari generate --diff || {
|
||||
echo "To fix, run script/update-workspace-hack";
|
||||
echo "To fix, run script/update-workspace-hack or script/update-workspace-hack.ps1";
|
||||
false
|
||||
}
|
||||
- name: Check all crates depend on workspace-hack
|
||||
run: |
|
||||
cargo hakari manage-deps --dry-run || {
|
||||
echo "To fix, run script/update-workspace-hack"
|
||||
echo "To fix, run script/update-workspace-hack or script/update-workspace-hack.ps1"
|
||||
false
|
||||
}
|
||||
|
||||
@@ -706,6 +706,51 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
nix-build:
|
||||
timeout-minutes: 60
|
||||
name: Nix Build
|
||||
continue-on-error: true
|
||||
if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
system:
|
||||
- os: x86 Linux
|
||||
runner: buildjet-16vcpu-ubuntu-2204
|
||||
install_nix: true
|
||||
- os: arm Mac
|
||||
runner: [macOS, ARM64, test]
|
||||
install_nix: false
|
||||
runs-on: ${{ matrix.system.runner }}
|
||||
env:
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
GIT_LFS_SKIP_SMUDGE: 1 # breaks the livekit rust sdk examples which we don't actually depend on
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
- name: Set path
|
||||
if: ${{ ! matrix.system.install_nix }}
|
||||
run: |
|
||||
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
|
||||
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
|
||||
|
||||
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
|
||||
if: ${{ matrix.system.install_nix }}
|
||||
with:
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: zed-industries
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
skipPush: true
|
||||
- run: nix build .#debug
|
||||
- name: Limit /nix/store to 50GB
|
||||
run: "[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d"
|
||||
|
||||
auto-release-preview:
|
||||
name: Auto release preview
|
||||
if: |
|
||||
|
||||
3
.github/workflows/release_nightly.yml
vendored
3
.github/workflows/release_nightly.yml
vendored
@@ -216,7 +216,8 @@ jobs:
|
||||
name: zed-industries
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
- run: nix build
|
||||
- run: nix-collect-garbage -d
|
||||
- name: Limit /nix/store to 50GB
|
||||
run: '[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d'
|
||||
|
||||
update-nightly-tag:
|
||||
name: Update nightly tag
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
[
|
||||
{
|
||||
"label": "Debug Zed with LLDB",
|
||||
"adapter": "lldb",
|
||||
"adapter": "LLDB",
|
||||
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||
"request": "launch",
|
||||
"cwd": "$ZED_WORKTREE_ROOT"
|
||||
},
|
||||
{
|
||||
"label": "Debug Zed with GDB",
|
||||
"adapter": "gdb",
|
||||
"adapter": "GDB",
|
||||
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||
"request": "launch",
|
||||
"cwd": "$ZED_WORKTREE_ROOT",
|
||||
|
||||
116
Cargo.lock
generated
116
Cargo.lock
generated
@@ -124,6 +124,43 @@ dependencies = [
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agent_eval"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent",
|
||||
"anyhow",
|
||||
"assistant_tool",
|
||||
"assistant_tools",
|
||||
"clap",
|
||||
"client",
|
||||
"collections",
|
||||
"context_server",
|
||||
"dap",
|
||||
"env_logger 0.11.8",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"gpui_tokio",
|
||||
"language",
|
||||
"language_model",
|
||||
"language_models",
|
||||
"node_runtime",
|
||||
"project",
|
||||
"prompt_store",
|
||||
"release_channel",
|
||||
"reqwest_client",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"settings",
|
||||
"smol",
|
||||
"tempfile",
|
||||
"util",
|
||||
"walkdir",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
@@ -579,43 +616,6 @@ dependencies = [
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assistant_eval"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent",
|
||||
"anyhow",
|
||||
"assistant_tool",
|
||||
"assistant_tools",
|
||||
"clap",
|
||||
"client",
|
||||
"collections",
|
||||
"context_server",
|
||||
"dap",
|
||||
"env_logger 0.11.8",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"gpui_tokio",
|
||||
"language",
|
||||
"language_model",
|
||||
"language_models",
|
||||
"node_runtime",
|
||||
"project",
|
||||
"prompt_store",
|
||||
"release_channel",
|
||||
"reqwest_client",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"settings",
|
||||
"smol",
|
||||
"tempfile",
|
||||
"util",
|
||||
"walkdir",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assistant_settings"
|
||||
version = "0.1.0"
|
||||
@@ -746,6 +746,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"language_model",
|
||||
"lsp",
|
||||
"open",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
@@ -1100,7 +1101,7 @@ source = "git+https://github.com/zed-industries/async-tls?rev=1e759a4b5e370f87dc
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"webpki-roots",
|
||||
]
|
||||
@@ -1647,7 +1648,7 @@ dependencies = [
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"rustls 0.21.12",
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -4049,7 +4050,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dap-types"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/zed-industries/dap-types?rev=bfd4af0#bfd4af084bbaa5f344e6925370d7642e41d0b5b8"
|
||||
source = "git+https://github.com/zed-industries/dap-types?rev=be69a016ba710191b9fdded28c8b042af4b617f7#be69a016ba710191b9fdded28c8b042af4b617f7"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -6607,7 +6608,7 @@ dependencies = [
|
||||
name = "http_client_tls"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"rustls-platform-verifier",
|
||||
"workspace-hack",
|
||||
]
|
||||
@@ -6706,7 +6707,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -7899,7 +7900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8682,7 +8683,6 @@ dependencies = [
|
||||
"collections",
|
||||
"ctor",
|
||||
"env_logger 0.11.8",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"itertools 0.14.0",
|
||||
@@ -11237,7 +11237,7 @@ dependencies = [
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"socket2",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
@@ -11256,7 +11256,7 @@ dependencies = [
|
||||
"rand 0.9.0",
|
||||
"ring",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.12",
|
||||
@@ -11864,7 +11864,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
@@ -12256,9 +12256,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.26"
|
||||
version = "0.23.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
|
||||
checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
@@ -12332,7 +12332,7 @@ dependencies = [
|
||||
"jni",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki 0.103.1",
|
||||
@@ -13430,7 +13430,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"rust_decimal",
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -14659,9 +14659,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.44.1"
|
||||
version = "1.44.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
|
||||
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes 1.10.1",
|
||||
@@ -14723,7 +14723,7 @@ version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
|
||||
dependencies = [
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -14783,7 +14783,7 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.2",
|
||||
@@ -15360,7 +15360,7 @@ dependencies = [
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.0",
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"rustls-pki-types",
|
||||
"sha1",
|
||||
"thiserror 2.0.12",
|
||||
@@ -17696,7 +17696,7 @@ dependencies = [
|
||||
"rust_decimal",
|
||||
"rustix 0.38.44",
|
||||
"rustix 1.0.5",
|
||||
"rustls 0.23.26",
|
||||
"rustls 0.23.25",
|
||||
"rustls-webpki 0.103.1",
|
||||
"scopeguard",
|
||||
"sea-orm",
|
||||
@@ -18085,7 +18085,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.181.6"
|
||||
version = "0.182.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"agent",
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -8,7 +8,7 @@ members = [
|
||||
"crates/assets",
|
||||
"crates/assistant",
|
||||
"crates/assistant_context_editor",
|
||||
"crates/assistant_eval",
|
||||
"crates/agent_eval",
|
||||
"crates/assistant_settings",
|
||||
"crates/assistant_slash_command",
|
||||
"crates/assistant_slash_commands",
|
||||
@@ -215,7 +215,7 @@ askpass = { path = "crates/askpass" }
|
||||
assets = { path = "crates/assets" }
|
||||
assistant = { path = "crates/assistant" }
|
||||
assistant_context_editor = { path = "crates/assistant_context_editor" }
|
||||
assistant_eval = { path = "crates/assistant_eval" }
|
||||
assistant_eval = { path = "crates/agent_eval" }
|
||||
assistant_settings = { path = "crates/assistant_settings" }
|
||||
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
||||
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
||||
@@ -425,7 +425,7 @@ core-foundation = "0.10.0"
|
||||
core-foundation-sys = "0.8.6"
|
||||
ctor = "0.4.0"
|
||||
dashmap = "6.0"
|
||||
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "bfd4af0" }
|
||||
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "be69a016ba710191b9fdded28c8b042af4b617f7" }
|
||||
derive_more = "0.99.17"
|
||||
dirs = "4.0"
|
||||
ec4rs = "1.1"
|
||||
@@ -465,6 +465,7 @@ mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] }
|
||||
nanoid = "0.4"
|
||||
nbformat = { version = "0.10.0" }
|
||||
nix = "0.29"
|
||||
objc = "0.2"
|
||||
open = "5.0.0"
|
||||
num-format = "0.4.4"
|
||||
ordered-float = "2.1.1"
|
||||
@@ -506,7 +507,7 @@ runtimelib = { version = "0.25.0", default-features = false, features = [
|
||||
rustc-demangle = "0.1.23"
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
rustc-hash = "2.1.0"
|
||||
rustls = { version = "0.23.26" }
|
||||
rustls = { version = "0.23.22" }
|
||||
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"] }
|
||||
@@ -669,7 +670,6 @@ workspace-hack = { path = "tooling/workspace-hack" }
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
debug = "limited"
|
||||
codegen-units = 16
|
||||
|
||||
[profile.dev.package]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.81-bookworm as builder
|
||||
FROM rust:1.86-bookworm as builder
|
||||
WORKDIR app
|
||||
COPY . .
|
||||
|
||||
|
||||
1
assets/icons/arrow_down_right.svg
Normal file
1
assets/icons/arrow_down_right.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-down-right-icon lucide-arrow-down-right"><path d="m7 7 10 10"/><path d="M17 7v10H7"/></svg>
|
||||
|
After Width: | Height: | Size: 300 B |
@@ -1,3 +1 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 2H6.5C6.5 1.86739 6.44732 1.74021 6.35355 1.64645C6.25979 1.55268 6.13261 1.5 6 1.5V2ZM2 1.5C1.72386 1.5 1.5 1.72386 1.5 2C1.5 2.27614 1.72386 2.5 2 2.5L2 1.5ZM5.5 6C5.5 6.27614 5.72386 6.5 6 6.5C6.27614 6.5 6.5 6.27614 6.5 6H5.5ZM1.64645 5.64645C1.45118 5.84171 1.45118 6.15829 1.64645 6.35355C1.84171 6.54882 2.15829 6.54882 2.35355 6.35355L1.64645 5.64645ZM6 1.5H2L2 2.5H6V1.5ZM5.5 2V6H6.5V2H5.5ZM5.64645 1.64645L1.64645 5.64645L2.35355 6.35355L6.35355 2.35355L5.64645 1.64645Z" fill="white"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up-right-icon lucide-arrow-up-right"><path d="M7 7h10v10"/><path d="M7 17 17 7"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 608 B After Width: | Height: | Size: 296 B |
1
assets/icons/bug_off.svg
Normal file
1
assets/icons/bug_off.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-bug-off-icon lucide-bug-off"><path d="M15 7.13V6a3 3 0 0 0-5.14-2.1L8 2"/><path d="M14.12 3.88 16 2"/><path d="M22 13h-4v-2a4 4 0 0 0-4-4h-1.3"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="m2 2 20 20"/><path d="M7.7 7.7A4 4 0 0 0 6 11v3a6 6 0 0 0 11.13 3.13"/><path d="M12 20v-8"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/></svg>
|
||||
|
After Width: | Height: | Size: 551 B |
1
assets/icons/circle_off.svg
Normal file
1
assets/icons/circle_off.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-circle-off-icon lucide-circle-off"><path d="m2 2 20 20"/><path d="M8.35 2.69A10 10 0 0 1 21.3 15.65"/><path d="M19.08 19.08A10 10 0 1 1 4.92 4.92"/></svg>
|
||||
|
After Width: | Height: | Size: 357 B |
1
assets/icons/power.svg
Normal file
1
assets/icons/power.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-power-icon lucide-power"><path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.77.04"/></svg>
|
||||
|
After Width: | Height: | Size: 294 B |
@@ -311,6 +311,7 @@
|
||||
"ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
|
||||
"ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||
"ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||
"ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes",
|
||||
"back": "pane::GoBack",
|
||||
"ctrl-alt--": "pane::GoBack",
|
||||
"ctrl-alt-_": "pane::GoForward",
|
||||
@@ -481,6 +482,8 @@
|
||||
"alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
|
||||
// also possible to spawn tasks by name:
|
||||
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
||||
// or by tag:
|
||||
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -455,7 +455,8 @@
|
||||
"cmd-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
|
||||
"cmd-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
|
||||
"cmd-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||
"cmd-k cmd-w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||
"cmd-k w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||
"cmd-k cmd-w": "workspace::CloseAllItemsAndPanes",
|
||||
"cmd-f": "project_search::ToggleFocus",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPreviousMatch",
|
||||
@@ -632,6 +633,8 @@
|
||||
"ctrl-alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
|
||||
// also possible to spawn tasks by name:
|
||||
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
||||
// or by tag:
|
||||
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
||||
}
|
||||
},
|
||||
// Bindings from Sublime Text
|
||||
|
||||
@@ -335,27 +335,106 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == helix_normal",
|
||||
"context": "vim_mode == helix_normal && !menu",
|
||||
"bindings": {
|
||||
"escape": "editor::Cancel",
|
||||
"ctrl-[": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"y": "editor::Copy",
|
||||
"shift-y": "vim::YankLine",
|
||||
"i": "vim::InsertBefore",
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
"a": "vim::InsertAfter",
|
||||
"d": "vim::HelixDelete",
|
||||
"w": "vim::NextWordStart",
|
||||
"e": "vim::NextWordEnd",
|
||||
"b": "vim::PreviousWordStart",
|
||||
"shift-a": "vim::InsertEndOfLine",
|
||||
"o": "vim::InsertLineBelow",
|
||||
"shift-o": "vim::InsertLineAbove",
|
||||
"~": "vim::ChangeCase",
|
||||
"ctrl-a": "vim::Increment",
|
||||
"ctrl-x": "vim::Decrement",
|
||||
"p": "vim::Paste",
|
||||
"shift-p": ["vim::Paste", { "before": true }],
|
||||
"u": "vim::Undo",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"r": "vim::PushReplace",
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"=": "vim::AutoIndent",
|
||||
"g u": "vim::PushLowercase",
|
||||
"g shift-u": "vim::PushUppercase",
|
||||
"g ~": "vim::PushOppositeCase",
|
||||
"\"": "vim::PushRegister",
|
||||
"g q": "vim::PushRewrap",
|
||||
"g w": "vim::PushRewrap",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePreviousItem",
|
||||
"insert": "vim::InsertBefore",
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode",
|
||||
"] d": "editor::GoToDiagnostic",
|
||||
"[ d": "editor::GoToPreviousDiagnostic",
|
||||
"] c": "editor::GoToHunk",
|
||||
"[ c": "editor::GoToPreviousHunk",
|
||||
// Goto mode
|
||||
"g n": "pane::ActivateNextItem",
|
||||
"g p": "pane::ActivatePreviousItem",
|
||||
// "tab": "pane::ActivateNextItem",
|
||||
// "shift-tab": "pane::ActivatePrevItem",
|
||||
"shift-h": "pane::ActivatePreviousItem",
|
||||
"shift-l": "pane::ActivateNextItem",
|
||||
"g l": "vim::EndOfLine",
|
||||
"g h": "vim::StartOfLine",
|
||||
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
|
||||
"g e": "vim::EndOfDocument",
|
||||
"g y": "editor::GoToTypeDefinition",
|
||||
"g r": "editor::FindAllReferences", // zed specific
|
||||
"g t": "vim::WindowTop",
|
||||
"g c": "vim::WindowMiddle",
|
||||
"g b": "vim::WindowBottom",
|
||||
|
||||
"h": "vim::Left",
|
||||
"j": "vim::Down",
|
||||
"k": "vim::Up",
|
||||
"l": "vim::Right"
|
||||
"x": "editor::SelectLine",
|
||||
"shift-x": "editor::SelectLine",
|
||||
// Window mode
|
||||
"space w h": "workspace::ActivatePaneLeft",
|
||||
"space w l": "workspace::ActivatePaneRight",
|
||||
"space w k": "workspace::ActivatePaneUp",
|
||||
"space w j": "workspace::ActivatePaneDown",
|
||||
"space w q": "pane::CloseActiveItem",
|
||||
"space w s": "pane::SplitRight",
|
||||
"space w r": "pane::SplitRight",
|
||||
"space w v": "pane::SplitDown",
|
||||
"space w d": "pane::SplitDown",
|
||||
// Space mode
|
||||
"space f": "file_finder::Toggle",
|
||||
"space k": "editor::Hover",
|
||||
"space s": "outline::Toggle",
|
||||
"space shift-s": "project_symbols::Toggle",
|
||||
"space d": "editor::GoToDiagnostic",
|
||||
"space r": "editor::Rename",
|
||||
"space a": "editor::ToggleCodeActions",
|
||||
"space h": "editor::SelectAllMatches",
|
||||
"space c": "editor::ToggleComments",
|
||||
"space y": "editor::Copy",
|
||||
"space p": "editor::Paste",
|
||||
// Match mode
|
||||
"m m": "vim::Matching",
|
||||
"m i w": ["workspace::SendKeystrokes", "v i w"],
|
||||
"shift-u": "editor::Redo",
|
||||
"ctrl-c": "editor::ToggleComments",
|
||||
"d": "vim::HelixDelete",
|
||||
"c": "vim::Substitute",
|
||||
"shift-c": "editor::AddSelectionBelow"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ShowCompletions",
|
||||
"ctrl-n": "editor::ShowCompletions"
|
||||
"ctrl-p": "editor::ShowWordCompletions",
|
||||
"ctrl-n": "editor::ShowWordCompletions"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,18 +6,9 @@ You are an AI assistant integrated into a code editor. You have the programming
|
||||
It will be up to you to decide which of these you are doing based on what the user has told you. When unclear, ask clarifying questions to understand the user's intent before proceeding.
|
||||
|
||||
You should only perform actions that modify the user's system if explicitly requested by the user:
|
||||
- If the user asks a question about how to accomplish a task, provide guidance or information, and use read-only tools (e.g., search) to assist. You may suggest potential actions, but do not directly modify the user's system without explicit instruction.
|
||||
- If the user asks a question about how to accomplish a task, provide guidance or information, and use read-only tools (e.g., search) to assist. You may suggest potential actions, but do not directly modify the user’s system without explicit instruction.
|
||||
- If the user clearly requests that you perform an action, carry out the action directly without explaining why you are doing so.
|
||||
|
||||
When answering questions, it's okay to give incomplete examples containing comments about what would go there in a real version. When being asked to directly perform tasks on the code base, you must ALWAYS make fully working code. You may never "simplify" the code by omitting or deleting functionality you know the user has requested, and you must NEVER write comments like "in a full version, this would..." - instead, you must actually implement the real version. Don't be lazy!
|
||||
|
||||
Note that project files are automatically backed up. The user can always get them back later if anything goes wrong, so there's
|
||||
no need to create backup files (e.g. `.bak` files) because these files will just take up unnecessary space on the user's disk.
|
||||
|
||||
When attempting to resolve issues around failing tests, never simply remove the failing tests. Unless the user explicitly asks you to remove tests, ALWAYS attempt to fix the code causing the tests to fail.
|
||||
|
||||
Ignore "TODO"-type comments unless they're relevant to the user's explicit request or the user specifically asks you to address them. It is, however, okay to include them in codebase summaries.
|
||||
|
||||
<style>
|
||||
Editing code:
|
||||
- Make sure to take previous edits into account.
|
||||
|
||||
@@ -1200,7 +1200,27 @@
|
||||
// When set to 0, waits indefinitely.
|
||||
//
|
||||
// Default: 0
|
||||
"lsp_fetch_timeout_ms": 0
|
||||
"lsp_fetch_timeout_ms": 0,
|
||||
// Controls what range to replace when accepting LSP completions.
|
||||
//
|
||||
// When LSP servers give an `InsertReplaceEdit` completion, they provides two ranges: `insert` and `replace`. Usually, `insert`
|
||||
// contains the word prefix before your cursor and `replace` contains the whole word.
|
||||
//
|
||||
// Effectively, this setting just changes whether Zed will use the received range for `insert` or `replace`, so the results may
|
||||
// differ depending on the underlying LSP server.
|
||||
//
|
||||
// Possible values:
|
||||
// 1. "insert"
|
||||
// Replaces text before the cursor, using the `insert` range described in the LSP specification.
|
||||
// 2. "replace"
|
||||
// Replaces text before and after the cursor, using the `replace` range described in the LSP specification.
|
||||
// 3. "replace_subsequence"
|
||||
// Behaves like `"replace"` if the text that would be replaced is a subsequence of the completion text,
|
||||
// and like `"insert"` otherwise.
|
||||
// 4. "replace_suffix"
|
||||
// Behaves like `"replace"` if the text after the cursor is a suffix of the completion, and like
|
||||
// `"insert"` otherwise.
|
||||
"lsp_insert_mode": "replace_suffix"
|
||||
},
|
||||
// Different settings for specific languages.
|
||||
"languages": {
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
// "args": ["--login"]
|
||||
// }
|
||||
// }
|
||||
"shell": "system"
|
||||
"shell": "system",
|
||||
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
|
||||
"tags": []
|
||||
}
|
||||
]
|
||||
|
||||
@@ -87,9 +87,9 @@
|
||||
"terminal.ansi.blue": "#83a598ff",
|
||||
"terminal.ansi.bright_blue": "#414f4aff",
|
||||
"terminal.ansi.dim_blue": "#c0d2cbff",
|
||||
"terminal.ansi.magenta": "#a89984ff",
|
||||
"terminal.ansi.bright_magenta": "#514a41ff",
|
||||
"terminal.ansi.dim_magenta": "#d2cabfff",
|
||||
"terminal.ansi.magenta": "#d3869bff",
|
||||
"terminal.ansi.bright_magenta": "#8e5868ff",
|
||||
"terminal.ansi.dim_magenta": "#ff9ebbff",
|
||||
"terminal.ansi.cyan": "#8ec07cff",
|
||||
"terminal.ansi.bright_cyan": "#45603eff",
|
||||
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
||||
@@ -472,9 +472,9 @@
|
||||
"terminal.ansi.blue": "#83a598ff",
|
||||
"terminal.ansi.bright_blue": "#414f4aff",
|
||||
"terminal.ansi.dim_blue": "#c0d2cbff",
|
||||
"terminal.ansi.magenta": "#a89984ff",
|
||||
"terminal.ansi.bright_magenta": "#514a41ff",
|
||||
"terminal.ansi.dim_magenta": "#d2cabfff",
|
||||
"terminal.ansi.magenta": "#d3869bff",
|
||||
"terminal.ansi.bright_magenta": "#8e5868ff",
|
||||
"terminal.ansi.dim_magenta": "#ff9ebbff",
|
||||
"terminal.ansi.cyan": "#8ec07cff",
|
||||
"terminal.ansi.bright_cyan": "#45603eff",
|
||||
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
||||
@@ -857,9 +857,9 @@
|
||||
"terminal.ansi.blue": "#83a598ff",
|
||||
"terminal.ansi.bright_blue": "#414f4aff",
|
||||
"terminal.ansi.dim_blue": "#c0d2cbff",
|
||||
"terminal.ansi.magenta": "#a89984ff",
|
||||
"terminal.ansi.bright_magenta": "#514a41ff",
|
||||
"terminal.ansi.dim_magenta": "#d2cabfff",
|
||||
"terminal.ansi.magenta": "#d3869bff",
|
||||
"terminal.ansi.bright_magenta": "#8e5868ff",
|
||||
"terminal.ansi.dim_magenta": "#ff9ebbff",
|
||||
"terminal.ansi.cyan": "#8ec07cff",
|
||||
"terminal.ansi.bright_cyan": "#45603eff",
|
||||
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
||||
@@ -1242,9 +1242,9 @@
|
||||
"terminal.ansi.blue": "#0b6678ff",
|
||||
"terminal.ansi.bright_blue": "#8fb0baff",
|
||||
"terminal.ansi.dim_blue": "#14333bff",
|
||||
"terminal.ansi.magenta": "#7c6f64ff",
|
||||
"terminal.ansi.bright_magenta": "#bcb5afff",
|
||||
"terminal.ansi.dim_magenta": "#3e3833ff",
|
||||
"terminal.ansi.magenta": "#8f3e71ff",
|
||||
"terminal.ansi.bright_magenta": "#c76da0ff",
|
||||
"terminal.ansi.dim_magenta": "#5c2848ff",
|
||||
"terminal.ansi.cyan": "#437b59ff",
|
||||
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||
@@ -1627,9 +1627,9 @@
|
||||
"terminal.ansi.blue": "#0b6678ff",
|
||||
"terminal.ansi.bright_blue": "#8fb0baff",
|
||||
"terminal.ansi.dim_blue": "#14333bff",
|
||||
"terminal.ansi.magenta": "#7c6f64ff",
|
||||
"terminal.ansi.bright_magenta": "#bcb5afff",
|
||||
"terminal.ansi.dim_magenta": "#3e3833ff",
|
||||
"terminal.ansi.magenta": "#8f3e71ff",
|
||||
"terminal.ansi.bright_magenta": "#c76da0ff",
|
||||
"terminal.ansi.dim_magenta": "#5c2848ff",
|
||||
"terminal.ansi.cyan": "#437b59ff",
|
||||
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||
@@ -2012,9 +2012,9 @@
|
||||
"terminal.ansi.blue": "#0b6678ff",
|
||||
"terminal.ansi.bright_blue": "#8fb0baff",
|
||||
"terminal.ansi.dim_blue": "#14333bff",
|
||||
"terminal.ansi.magenta": "#7c6f64ff",
|
||||
"terminal.ansi.bright_magenta": "#bcb5afff",
|
||||
"terminal.ansi.dim_magenta": "#3e3833ff",
|
||||
"terminal.ansi.magenta": "#8f3e71ff",
|
||||
"terminal.ansi.bright_magenta": "#c76da0ff",
|
||||
"terminal.ansi.dim_magenta": "#5c2848ff",
|
||||
"terminal.ansi.cyan": "#437b59ff",
|
||||
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||
|
||||
@@ -11,13 +11,22 @@ use language::{BinaryStatus, LanguageRegistry, LanguageServerId};
|
||||
use project::{
|
||||
EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,
|
||||
ProjectEnvironmentEvent,
|
||||
git_store::{GitStoreEvent, Repository},
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp::Reverse, fmt::Write, path::Path, sync::Arc, time::Duration};
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
fmt::Write,
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use ui::{ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
|
||||
use util::truncate_and_trailoff;
|
||||
use workspace::{StatusItemView, Workspace, item::ItemHandle};
|
||||
|
||||
const GIT_OPERATION_DELAY: Duration = Duration::from_millis(0);
|
||||
|
||||
actions!(activity_indicator, [ShowErrorMessage]);
|
||||
|
||||
pub enum Event {
|
||||
@@ -105,6 +114,15 @@ impl ActivityIndicator {
|
||||
)
|
||||
.detach();
|
||||
|
||||
cx.subscribe(
|
||||
&project.read(cx).git_store().clone(),
|
||||
|_, _, event: &GitStoreEvent, cx| match event {
|
||||
project::git_store::GitStoreEvent::JobsUpdated => cx.notify(),
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
if let Some(auto_updater) = auto_updater.as_ref() {
|
||||
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
|
||||
}
|
||||
@@ -285,6 +303,34 @@ impl ActivityIndicator {
|
||||
});
|
||||
}
|
||||
|
||||
let current_job = self
|
||||
.project
|
||||
.read(cx)
|
||||
.active_repository(cx)
|
||||
.map(|r| r.read(cx))
|
||||
.and_then(Repository::current_job);
|
||||
// Show any long-running git command
|
||||
if let Some(job_info) = current_job {
|
||||
if Instant::now() - job_info.start >= GIT_OPERATION_DELAY {
|
||||
return Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(delta)))
|
||||
},
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: job_info.message.into(),
|
||||
on_click: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Show any language server installation info.
|
||||
let mut downloading = SmallVec::<[_; 3]>::new();
|
||||
let mut checking_for_update = SmallVec::<[_; 3]>::new();
|
||||
|
||||
@@ -25,7 +25,6 @@ use language_model::{ConfiguredModel, LanguageModelRegistry, LanguageModelToolUs
|
||||
use markdown::parser::CodeBlockKind;
|
||||
use markdown::{Markdown, MarkdownElement, MarkdownStyle, ParsedMarkdown, without_fences};
|
||||
use project::ProjectItem as _;
|
||||
use rope::Point;
|
||||
use settings::{Settings as _, update_settings_file};
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
@@ -59,7 +58,7 @@ pub struct ActiveThread {
|
||||
expanded_thinking_segments: HashMap<(MessageId, usize), bool>,
|
||||
last_error: Option<ThreadError>,
|
||||
notifications: Vec<WindowHandle<AgentNotification>>,
|
||||
copied_code_block_ids: HashSet<(MessageId, usize)>,
|
||||
copied_code_block_ids: HashSet<usize>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
|
||||
feedback_message_editor: Option<Entity<Editor>>,
|
||||
@@ -290,8 +289,7 @@ fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
|
||||
}
|
||||
|
||||
fn render_markdown_code_block(
|
||||
message_id: MessageId,
|
||||
ix: usize,
|
||||
id: usize,
|
||||
kind: &CodeBlockKind,
|
||||
parsed_markdown: &ParsedMarkdown,
|
||||
codeblock_range: Range<usize>,
|
||||
@@ -368,7 +366,7 @@ fn render_markdown_code_block(
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.id(("code-block-header-label", ix))
|
||||
.id(("code-block-header-label", id))
|
||||
.w_full()
|
||||
.max_w_full()
|
||||
.px_1()
|
||||
@@ -399,40 +397,8 @@ fn render_markdown_code_block(
|
||||
.read(cx)
|
||||
.find_project_path(&path_range.path, cx)
|
||||
{
|
||||
let target = path_range.range.as_ref().map(|range| {
|
||||
Point::new(
|
||||
// Line number is 1-based
|
||||
range.start.line.saturating_sub(1),
|
||||
range.start.col.unwrap_or(0),
|
||||
)
|
||||
});
|
||||
let open_task = workspace.open_path(
|
||||
project_path,
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
let item = open_task.await?;
|
||||
if let Some(target) = target {
|
||||
if let Some(active_editor) =
|
||||
item.downcast::<Editor>()
|
||||
{
|
||||
active_editor
|
||||
.downgrade()
|
||||
.update_in(cx, |editor, window, cx| {
|
||||
editor
|
||||
.go_to_singleton_buffer_point(
|
||||
target, window, cx,
|
||||
);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
workspace
|
||||
.open_path(project_path, None, true, window, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
@@ -450,10 +416,7 @@ fn render_markdown_code_block(
|
||||
.element_background
|
||||
.blend(cx.theme().colors().editor_foreground.opacity(0.01));
|
||||
|
||||
let codeblock_was_copied = active_thread
|
||||
.read(cx)
|
||||
.copied_code_block_ids
|
||||
.contains(&(message_id, ix));
|
||||
let codeblock_was_copied = active_thread.read(cx).copied_code_block_ids.contains(&id);
|
||||
|
||||
let codeblock_header = h_flex()
|
||||
.p_1()
|
||||
@@ -466,7 +429,7 @@ fn render_markdown_code_block(
|
||||
.children(label)
|
||||
.child(
|
||||
IconButton::new(
|
||||
("copy-markdown-code", ix),
|
||||
("copy-markdown-code", id),
|
||||
if codeblock_was_copied {
|
||||
IconName::Check
|
||||
} else {
|
||||
@@ -476,12 +439,13 @@ fn render_markdown_code_block(
|
||||
.icon_color(Color::Muted)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(Tooltip::text("Copy Code"))
|
||||
.visible_on_hover("markdown-code-block")
|
||||
.on_click({
|
||||
let active_thread = active_thread.clone();
|
||||
let parsed_markdown = parsed_markdown.clone();
|
||||
move |_event, _window, cx| {
|
||||
active_thread.update(cx, |this, cx| {
|
||||
this.copied_code_block_ids.insert((message_id, ix));
|
||||
this.copied_code_block_ids.insert(id);
|
||||
|
||||
let code =
|
||||
without_fences(&parsed_markdown.source()[codeblock_range.clone()])
|
||||
@@ -494,7 +458,7 @@ fn render_markdown_code_block(
|
||||
|
||||
cx.update(|cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.copied_code_block_ids.remove(&(message_id, ix));
|
||||
this.copied_code_block_ids.remove(&id);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
@@ -1199,62 +1163,16 @@ impl ActiveThread {
|
||||
|
||||
let context_store = self.context_store.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
let thread = self.thread.read(cx);
|
||||
|
||||
let thread = self.thread.read(cx);
|
||||
// Get all the data we need from thread before we start using it in closures
|
||||
let checkpoint = thread.checkpoint_for_message(message_id);
|
||||
let context = thread.context_for_message(message_id).collect::<Vec<_>>();
|
||||
|
||||
let tool_uses = thread.tool_uses_for_message(message_id, cx);
|
||||
let has_tool_uses = !tool_uses.is_empty();
|
||||
let is_generating = thread.is_generating();
|
||||
|
||||
let is_first_message = ix == 0;
|
||||
let is_last_message = ix == self.messages.len() - 1;
|
||||
let show_feedback = is_last_message && message.role != Role::User;
|
||||
|
||||
let needs_confirmation = tool_uses.iter().any(|tool_use| tool_use.needs_confirmation);
|
||||
|
||||
let generating_label = (is_generating && is_last_message).then(|| {
|
||||
Label::new("Generating")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.with_animation(
|
||||
"generating-label",
|
||||
Animation::new(Duration::from_secs(1)).repeat(),
|
||||
|mut label, delta| {
|
||||
let text = match delta {
|
||||
d if d < 0.25 => "Generating",
|
||||
d if d < 0.5 => "Generating.",
|
||||
d if d < 0.75 => "Generating..",
|
||||
_ => "Generating...",
|
||||
};
|
||||
label.set_text(text);
|
||||
label
|
||||
},
|
||||
)
|
||||
.with_animation(
|
||||
"pulsating-label",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.6, 1.)),
|
||||
|label, delta| label.map_element(|label| label.alpha(delta)),
|
||||
)
|
||||
});
|
||||
|
||||
// Don't render user messages that are just there for returning tool results.
|
||||
if message.role == Role::User && thread.message_has_tool_results(message_id) {
|
||||
if let Some(generating_label) = generating_label {
|
||||
return h_flex()
|
||||
.w_full()
|
||||
.h_10()
|
||||
.py_1p5()
|
||||
.pl_4()
|
||||
.pb_3()
|
||||
.child(generating_label)
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
return Empty.into_any();
|
||||
}
|
||||
|
||||
@@ -1266,6 +1184,9 @@ impl ActiveThread {
|
||||
.filter(|(id, _)| *id == message_id)
|
||||
.map(|(_, state)| state.editor.clone());
|
||||
|
||||
let first_message = ix == 0;
|
||||
let show_feedback = ix == self.messages.len() - 1 && message.role != Role::User;
|
||||
|
||||
let colors = cx.theme().colors();
|
||||
let active_color = colors.element_active;
|
||||
let editor_bg_color = colors.editor_background;
|
||||
@@ -1434,7 +1355,7 @@ impl ActiveThread {
|
||||
Role::User => v_flex()
|
||||
.id(("message-container", ix))
|
||||
.map(|this| {
|
||||
if is_first_message {
|
||||
if first_message {
|
||||
this.pt_2()
|
||||
} else {
|
||||
this.pt_4()
|
||||
@@ -1552,11 +1473,15 @@ impl ActiveThread {
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.children(message_content)
|
||||
.when(has_tool_uses, |parent| {
|
||||
parent.children(
|
||||
tool_uses
|
||||
.into_iter()
|
||||
.map(|tool_use| self.render_tool_use(tool_use, window, cx)),
|
||||
.gap_2p5()
|
||||
.pb_2p5()
|
||||
.when(!tool_uses.is_empty(), |parent| {
|
||||
parent.child(
|
||||
div().children(
|
||||
tool_uses
|
||||
.into_iter()
|
||||
.map(|tool_use| self.render_tool_use(tool_use, window, cx)),
|
||||
),
|
||||
)
|
||||
}),
|
||||
Role::System => div().id(("message-container", ix)).py_1().px_2().child(
|
||||
@@ -1569,6 +1494,9 @@ impl ActiveThread {
|
||||
|
||||
v_flex()
|
||||
.w_full()
|
||||
.when(first_message, |parent| {
|
||||
parent.child(self.render_rules_item(cx))
|
||||
})
|
||||
.when_some(checkpoint, |parent, checkpoint| {
|
||||
let mut is_pending = false;
|
||||
let mut error = None;
|
||||
@@ -1638,56 +1566,65 @@ impl ActiveThread {
|
||||
.child(ui::Divider::horizontal()),
|
||||
)
|
||||
})
|
||||
.when(is_first_message, |parent| {
|
||||
parent.child(self.render_rules_item(cx))
|
||||
})
|
||||
.child(styled_message)
|
||||
.when(!needs_confirmation && generating_label.is_some(), |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.h_8()
|
||||
.mt_2()
|
||||
.mb_4()
|
||||
.ml_4()
|
||||
.py_1p5()
|
||||
.child(generating_label.unwrap()),
|
||||
)
|
||||
})
|
||||
.when(show_feedback && !is_generating, |parent| {
|
||||
parent.child(feedback_items).when_some(
|
||||
self.feedback_message_editor.clone(),
|
||||
|parent, feedback_editor| {
|
||||
let focus_handle = feedback_editor.focus_handle(cx);
|
||||
parent.child(
|
||||
v_flex()
|
||||
.key_context("AgentFeedbackMessageEditor")
|
||||
.on_action(cx.listener(|this, _: &menu::Cancel, _, cx| {
|
||||
this.feedback_message_editor = None;
|
||||
cx.notify();
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &menu::Confirm, _, cx| {
|
||||
this.submit_feedback_message(cx);
|
||||
cx.notify();
|
||||
}))
|
||||
.on_action(cx.listener(Self::confirm_editing_message))
|
||||
.my_3()
|
||||
.mx_4()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(feedback_editor)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.justify_end()
|
||||
.child(
|
||||
Button::new("dismiss-feedback-message", "Cancel")
|
||||
.when(
|
||||
show_feedback && !self.thread.read(cx).is_generating(),
|
||||
|parent| {
|
||||
parent.child(feedback_items).when_some(
|
||||
self.feedback_message_editor.clone(),
|
||||
|parent, feedback_editor| {
|
||||
let focus_handle = feedback_editor.focus_handle(cx);
|
||||
parent.child(
|
||||
v_flex()
|
||||
.key_context("AgentFeedbackMessageEditor")
|
||||
.on_action(cx.listener(|this, _: &menu::Cancel, _, cx| {
|
||||
this.feedback_message_editor = None;
|
||||
cx.notify();
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &menu::Confirm, _, cx| {
|
||||
this.submit_feedback_message(cx);
|
||||
cx.notify();
|
||||
}))
|
||||
.on_action(cx.listener(Self::confirm_editing_message))
|
||||
.mx_4()
|
||||
.mb_3()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(feedback_editor)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.justify_end()
|
||||
.child(
|
||||
Button::new("dismiss-feedback-message", "Cancel")
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.feedback_message_editor = None;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new(
|
||||
"submit-feedback-message",
|
||||
"Share Feedback",
|
||||
)
|
||||
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
@@ -1695,38 +1632,16 @@ impl ActiveThread {
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.feedback_message_editor = None;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new(
|
||||
"submit-feedback-message",
|
||||
"Share Feedback",
|
||||
)
|
||||
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(
|
||||
cx.listener(|this, _, _, cx| {
|
||||
this.submit_feedback_message(cx);
|
||||
cx.notify();
|
||||
}),
|
||||
})),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
@@ -1746,7 +1661,7 @@ impl ActiveThread {
|
||||
.segments
|
||||
.iter()
|
||||
.enumerate()
|
||||
.last()
|
||||
.next_back()
|
||||
.filter(|(_, segment)| matches!(segment, RenderedMessageSegment::Thinking { .. }))
|
||||
.map(|(index, _)| index)
|
||||
} else {
|
||||
@@ -1785,7 +1700,6 @@ impl ActiveThread {
|
||||
let active_thread = cx.entity();
|
||||
move |id, kind, parsed_markdown, range, window, cx| {
|
||||
render_markdown_code_block(
|
||||
message_id,
|
||||
id,
|
||||
kind,
|
||||
parsed_markdown,
|
||||
@@ -2209,7 +2123,6 @@ impl ActiveThread {
|
||||
if !tool_use.needs_confirmation {
|
||||
element.child(
|
||||
v_flex()
|
||||
.my_1p5()
|
||||
.child(
|
||||
h_flex()
|
||||
.group("disclosure-header")
|
||||
@@ -2281,7 +2194,6 @@ impl ActiveThread {
|
||||
)
|
||||
} else {
|
||||
v_flex()
|
||||
.my_3()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(self.tool_card_border_color(cx))
|
||||
@@ -2384,32 +2296,7 @@ impl ActiveThread {
|
||||
.border_t_1()
|
||||
.border_color(self.tool_card_border_color(cx))
|
||||
.rounded_b_lg()
|
||||
.child(
|
||||
Label::new("Waiting for Confirmation…")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.with_animation(
|
||||
"generating-label",
|
||||
Animation::new(Duration::from_secs(1)).repeat(),
|
||||
|mut label, delta| {
|
||||
let text = match delta {
|
||||
d if d < 0.25 => "Waiting for Confirmation",
|
||||
d if d < 0.5 => "Waiting for Confirmation.",
|
||||
d if d < 0.75 => "Waiting for Confirmation..",
|
||||
_ => "Waiting for Confirmation...",
|
||||
};
|
||||
label.set_text(text);
|
||||
label
|
||||
},
|
||||
)
|
||||
.with_animation(
|
||||
"pulsating-label",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.6, 1.)),
|
||||
|label, delta| label.map_element(|label| label.alpha(delta)),
|
||||
),
|
||||
)
|
||||
.child(Label::new("Action Confirmation").color(Color::Muted).size(LabelSize::Small))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
@@ -2524,7 +2411,7 @@ impl ActiveThread {
|
||||
};
|
||||
|
||||
div()
|
||||
.pt_2()
|
||||
.pt_1()
|
||||
.px_2p5()
|
||||
.child(
|
||||
h_flex()
|
||||
|
||||
@@ -28,7 +28,7 @@ use std::{
|
||||
time::Instant,
|
||||
};
|
||||
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
|
||||
pub struct BufferCodegen {
|
||||
alternatives: Vec<Entity<CodegenAlternative>>,
|
||||
@@ -601,7 +601,7 @@ impl CodegenAlternative {
|
||||
|
||||
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||
report_assistant_event(
|
||||
AssistantEvent {
|
||||
AssistantEventData {
|
||||
conversation_id: None,
|
||||
message_id,
|
||||
kind: AssistantKind::Inline,
|
||||
|
||||
@@ -112,6 +112,7 @@ impl ContextPickerCompletionProvider {
|
||||
icon_path: Some(mode.icon().path().into()),
|
||||
documentation: None,
|
||||
source: project::CompletionSource::Custom,
|
||||
insert_text_mode: None,
|
||||
// This ensures that when a user accepts this completion, the
|
||||
// completion menu will still be shown after "@category " is
|
||||
// inserted
|
||||
@@ -163,6 +164,7 @@ impl ContextPickerCompletionProvider {
|
||||
new_text,
|
||||
label: CodeLabel::plain(thread_entry.summary.to_string(), None),
|
||||
documentation: None,
|
||||
insert_text_mode: None,
|
||||
source: project::CompletionSource::Custom,
|
||||
icon_path: Some(icon_for_completion.path().into()),
|
||||
confirm: Some(confirm_completion_callback(
|
||||
@@ -209,6 +211,7 @@ impl ContextPickerCompletionProvider {
|
||||
documentation: None,
|
||||
source: project::CompletionSource::Custom,
|
||||
icon_path: Some(IconName::Globe.path().into()),
|
||||
insert_text_mode: None,
|
||||
confirm: Some(confirm_completion_callback(
|
||||
IconName::Globe.path().into(),
|
||||
url_to_fetch.clone(),
|
||||
@@ -290,6 +293,7 @@ impl ContextPickerCompletionProvider {
|
||||
documentation: None,
|
||||
source: project::CompletionSource::Custom,
|
||||
icon_path: Some(completion_icon_path),
|
||||
insert_text_mode: None,
|
||||
confirm: Some(confirm_completion_callback(
|
||||
crease_icon_path,
|
||||
file_name,
|
||||
@@ -352,6 +356,7 @@ impl ContextPickerCompletionProvider {
|
||||
documentation: None,
|
||||
source: project::CompletionSource::Custom,
|
||||
icon_path: Some(IconName::Code.path().into()),
|
||||
insert_text_mode: None,
|
||||
confirm: Some(confirm_completion_callback(
|
||||
IconName::Code.path().into(),
|
||||
symbol.name.clone().into(),
|
||||
|
||||
@@ -31,7 +31,7 @@ use project::LspAction;
|
||||
use project::{CodeAction, ProjectTransaction};
|
||||
use prompt_store::PromptBuilder;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
use ui::prelude::*;
|
||||
@@ -402,7 +402,7 @@ impl InlineAssistant {
|
||||
codegen_ranges.push(anchor_range);
|
||||
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).inline_assistant_model() {
|
||||
self.telemetry.report_assistant_event(AssistantEvent {
|
||||
self.telemetry.report_assistant_event(AssistantEventData {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
phase: AssistantPhase::Invoked,
|
||||
@@ -987,7 +987,7 @@ impl InlineAssistant {
|
||||
.map(|language| language.name())
|
||||
});
|
||||
report_assistant_event(
|
||||
AssistantEvent {
|
||||
AssistantEventData {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
message_id,
|
||||
|
||||
@@ -222,7 +222,8 @@ impl MessageEditor {
|
||||
|
||||
let thread = self.thread.clone();
|
||||
let context_store = self.context_store.clone();
|
||||
let checkpoint = self.project.read(cx).git_store().read(cx).checkpoint(cx);
|
||||
let git_store = self.project.read(cx).git_store().clone();
|
||||
let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let checkpoint = checkpoint.await.ok();
|
||||
|
||||
@@ -6,7 +6,7 @@ use language_model::{
|
||||
ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, report_assistant_event,
|
||||
};
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use terminal::Terminal;
|
||||
|
||||
pub struct TerminalCodegen {
|
||||
@@ -79,7 +79,7 @@ impl TerminalCodegen {
|
||||
|
||||
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||
report_assistant_event(
|
||||
AssistantEvent {
|
||||
AssistantEventData {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::InlineTerminal,
|
||||
message_id,
|
||||
|
||||
@@ -18,7 +18,7 @@ use language_model::{
|
||||
};
|
||||
use prompt_store::PromptBuilder;
|
||||
use std::sync::Arc;
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use terminal_view::TerminalView;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
@@ -292,7 +292,7 @@ impl TerminalInlineAssistant {
|
||||
let codegen = assist.codegen.read(cx);
|
||||
let executor = cx.background_executor().clone();
|
||||
report_assistant_event(
|
||||
AssistantEvent {
|
||||
AssistantEventData {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::InlineTerminal,
|
||||
message_id: codegen.message_id.clone(),
|
||||
|
||||
@@ -471,11 +471,11 @@ impl Thread {
|
||||
cx.emit(ThreadEvent::CheckpointChanged);
|
||||
cx.notify();
|
||||
|
||||
let project = self.project.read(cx);
|
||||
let restore = project
|
||||
.git_store()
|
||||
.read(cx)
|
||||
.restore_checkpoint(checkpoint.git_checkpoint.clone(), cx);
|
||||
let git_store = self.project().read(cx).git_store().clone();
|
||||
let restore = git_store.update(cx, |git_store, cx| {
|
||||
git_store.restore_checkpoint(checkpoint.git_checkpoint.clone(), cx)
|
||||
});
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let result = restore.await;
|
||||
this.update(cx, |this, cx| {
|
||||
@@ -506,11 +506,11 @@ impl Thread {
|
||||
};
|
||||
|
||||
let git_store = self.project.read(cx).git_store().clone();
|
||||
let final_checkpoint = git_store.read(cx).checkpoint(cx);
|
||||
let final_checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
|
||||
cx.spawn(async move |this, cx| match final_checkpoint.await {
|
||||
Ok(final_checkpoint) => {
|
||||
let equal = git_store
|
||||
.read_with(cx, |store, cx| {
|
||||
.update(cx, |store, cx| {
|
||||
store.compare_checkpoints(
|
||||
pending_checkpoint.git_checkpoint.clone(),
|
||||
final_checkpoint.clone(),
|
||||
@@ -522,7 +522,7 @@ impl Thread {
|
||||
|
||||
if equal {
|
||||
git_store
|
||||
.read_with(cx, |store, cx| {
|
||||
.update(cx, |store, cx| {
|
||||
store.delete_checkpoint(pending_checkpoint.git_checkpoint, cx)
|
||||
})?
|
||||
.detach();
|
||||
@@ -533,7 +533,7 @@ impl Thread {
|
||||
}
|
||||
|
||||
git_store
|
||||
.read_with(cx, |store, cx| {
|
||||
.update(cx, |store, cx| {
|
||||
store.delete_checkpoint(final_checkpoint, cx)
|
||||
})?
|
||||
.detach();
|
||||
@@ -1487,7 +1487,6 @@ impl Thread {
|
||||
tool_use_id.clone(),
|
||||
tool_name,
|
||||
output,
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.emit(ThreadEvent::ToolFinished {
|
||||
@@ -1651,10 +1650,10 @@ impl Thread {
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|repo| {
|
||||
repo.read_with(cx, |repo, _| {
|
||||
repo.update(cx, |repo, _| {
|
||||
let current_branch =
|
||||
repo.branch.as_ref().map(|branch| branch.name.to_string());
|
||||
repo.send_job(|state, _| async move {
|
||||
repo.send_job(None, |state, _| async move {
|
||||
let RepositoryState::Local { backend, .. } = state else {
|
||||
return GitState {
|
||||
remote_url: None,
|
||||
@@ -1832,7 +1831,7 @@ impl Thread {
|
||||
));
|
||||
|
||||
self.tool_use
|
||||
.insert_tool_output(tool_use_id.clone(), tool_name, err, cx);
|
||||
.insert_tool_output(tool_use_id.clone(), tool_name, err);
|
||||
|
||||
cx.emit(ThreadEvent::ToolFinished {
|
||||
tool_use_id,
|
||||
|
||||
@@ -7,11 +7,10 @@ use futures::FutureExt as _;
|
||||
use futures::future::Shared;
|
||||
use gpui::{App, SharedString, Task};
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequestMessage, LanguageModelToolResult,
|
||||
LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role,
|
||||
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
|
||||
LanguageModelToolUseId, MessageContent, Role,
|
||||
};
|
||||
use ui::IconName;
|
||||
use util::truncate_lines_to_byte_limit;
|
||||
|
||||
use crate::thread::MessageId;
|
||||
use crate::thread_store::SerializedMessage;
|
||||
@@ -332,32 +331,9 @@ impl ToolUseState {
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_name: Arc<str>,
|
||||
output: Result<String>,
|
||||
cx: &App,
|
||||
) -> Option<PendingToolUse> {
|
||||
match output {
|
||||
Ok(tool_result) => {
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
|
||||
const BYTES_PER_TOKEN_ESTIMATE: usize = 3;
|
||||
|
||||
// Protect from clearly large output
|
||||
let tool_output_limit = model_registry
|
||||
.default_model()
|
||||
.map(|model| model.model.max_token_count() * BYTES_PER_TOKEN_ESTIMATE)
|
||||
.unwrap_or(usize::MAX);
|
||||
|
||||
let tool_result = if tool_result.len() <= tool_output_limit {
|
||||
tool_result
|
||||
} else {
|
||||
let truncated = truncate_lines_to_byte_limit(&tool_result, tool_output_limit);
|
||||
|
||||
format!(
|
||||
"Tool result too long. The first {} bytes:\n\n{}",
|
||||
truncated.len(),
|
||||
truncated
|
||||
)
|
||||
};
|
||||
|
||||
self.tool_results.insert(
|
||||
tool_use_id.clone(),
|
||||
LanguageModelToolResult {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "assistant_eval"
|
||||
name = "agent_eval"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
@@ -9,7 +9,7 @@ license = "GPL-3.0-or-later"
|
||||
workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "assistant_eval"
|
||||
name = "agent_eval"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::git_commands::{run_git, setup_temp_repo};
|
||||
use crate::headless_assistant::{HeadlessAppState, HeadlessAssistant};
|
||||
use crate::{get_exercise_language, get_exercise_name, templates_eval::Template};
|
||||
use crate::{get_exercise_language, get_exercise_name};
|
||||
use agent::RequestKind;
|
||||
use anyhow::{Result, anyhow};
|
||||
use collections::HashMap;
|
||||
@@ -18,8 +18,6 @@ use std::{
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct EvalResult {
|
||||
pub exercise_name: String,
|
||||
pub template_name: String,
|
||||
pub score: String,
|
||||
pub diff: String,
|
||||
pub assistant_response: String,
|
||||
pub elapsed_time_ms: u128,
|
||||
@@ -29,7 +27,6 @@ pub struct EvalResult {
|
||||
pub output_tokens: usize,
|
||||
pub total_tokens: usize,
|
||||
pub tool_use_counts: usize,
|
||||
pub judge_model_name: String, // Added field for judge model name
|
||||
}
|
||||
|
||||
pub struct EvalOutput {
|
||||
@@ -251,29 +248,6 @@ pub async fn read_instructions(exercise_path: &Path) -> Result<String> {
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
pub async fn read_example_solution(exercise_path: &Path, language: &str) -> Result<String> {
|
||||
// Map the language to the file extension
|
||||
let language_extension = match language {
|
||||
"python" => "py",
|
||||
"go" => "go",
|
||||
"rust" => "rs",
|
||||
"typescript" => "ts",
|
||||
"javascript" => "js",
|
||||
"ruby" => "rb",
|
||||
"php" => "php",
|
||||
"bash" => "sh",
|
||||
"multi" => "diff",
|
||||
"internal" => "diff",
|
||||
_ => return Err(anyhow!("Unsupported language: {}", language)),
|
||||
};
|
||||
let example_path = exercise_path
|
||||
.join(".meta")
|
||||
.join(format!("example.{}", language_extension));
|
||||
println!("Reading example solution from: {}", example_path.display());
|
||||
let example = smol::unblock(move || std::fs::read_to_string(&example_path)).await?;
|
||||
Ok(example)
|
||||
}
|
||||
|
||||
pub async fn save_eval_results(exercise_path: &Path, results: Vec<EvalResult>) -> Result<()> {
|
||||
let eval_dir = exercise_path.join("evaluation");
|
||||
fs::create_dir_all(&eval_dir)?;
|
||||
@@ -311,12 +285,8 @@ pub async fn save_eval_results(exercise_path: &Path, results: Vec<EvalResult>) -
|
||||
// Group the new results by test name (exercise name)
|
||||
for result in results {
|
||||
let exercise_name = &result.exercise_name;
|
||||
let template_name = &result.template_name;
|
||||
|
||||
println!(
|
||||
"Adding result: exercise={}, template={}",
|
||||
exercise_name, template_name
|
||||
);
|
||||
println!("Adding result: exercise={}", exercise_name);
|
||||
|
||||
// Ensure the exercise entry exists
|
||||
if eval_data.get(exercise_name).is_none() {
|
||||
@@ -329,7 +299,7 @@ pub async fn save_eval_results(exercise_path: &Path, results: Vec<EvalResult>) -
|
||||
}
|
||||
|
||||
// Add this result under the timestamp with template name as key
|
||||
eval_data[exercise_name][×tamp][template_name] = serde_json::to_value(&result)?;
|
||||
eval_data[exercise_name][×tamp] = serde_json::to_value(&result)?;
|
||||
}
|
||||
|
||||
// Write back to file with pretty formatting
|
||||
@@ -344,9 +314,7 @@ pub async fn save_eval_results(exercise_path: &Path, results: Vec<EvalResult>) -
|
||||
|
||||
pub async fn run_exercise_eval(
|
||||
exercise_path: PathBuf,
|
||||
template: Template,
|
||||
model: Arc<dyn LanguageModel>,
|
||||
judge_model: Arc<dyn LanguageModel>,
|
||||
app_state: Arc<HeadlessAppState>,
|
||||
base_sha: String,
|
||||
_framework_path: PathBuf,
|
||||
@@ -359,68 +327,15 @@ pub async fn run_exercise_eval(
|
||||
"\n\nWhen writing the code for this prompt, use {} to achieve the goal.",
|
||||
language
|
||||
));
|
||||
let example_solution = read_example_solution(&exercise_path, &language).await?;
|
||||
|
||||
println!(
|
||||
"Running evaluation for exercise: {} with template: {}",
|
||||
exercise_name, template.name
|
||||
);
|
||||
println!("Running evaluation for exercise: {}", exercise_name);
|
||||
|
||||
// Create temporary directory with exercise files
|
||||
let temp_dir = setup_temp_repo(&exercise_path, &base_sha).await?;
|
||||
let temp_path = temp_dir.path().to_path_buf();
|
||||
|
||||
if template.name == "ProjectCreation" {
|
||||
for entry in fs::read_dir(&temp_path)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
// Skip directories that start with dot (like .docs, .meta, .git)
|
||||
if path.is_dir()
|
||||
&& path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.map(|name| name.starts_with("."))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Delete regular files
|
||||
if path.is_file() {
|
||||
println!(" Deleting file: {}", path.display());
|
||||
fs::remove_file(path)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Commit the deletion so it shows up in the diff
|
||||
run_git(&temp_path, &["add", "."]).await?;
|
||||
run_git(
|
||||
&temp_path,
|
||||
&["commit", "-m", "Remove root files for clean slate"],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let local_commit_sha = run_git(&temp_path, &["rev-parse", "HEAD"]).await?;
|
||||
|
||||
// Prepare prompt based on template
|
||||
let prompt = match template.name {
|
||||
"ProjectCreation" => format!(
|
||||
"I need to create a new implementation for this exercise. Please create all the necessary files in the best location.\n\n{}",
|
||||
instructions
|
||||
),
|
||||
"CodeModification" => format!(
|
||||
"I need help updating my code to meet these requirements. Please modify the appropriate files:\n\n{}",
|
||||
instructions
|
||||
),
|
||||
"ConversationalGuidance" => format!(
|
||||
"I'm trying to solve this coding exercise but I'm not sure where to start. Can you help me understand the requirements and guide me through the solution process without writing code for me?\n\n{}",
|
||||
instructions
|
||||
),
|
||||
_ => instructions.clone(),
|
||||
};
|
||||
|
||||
let start_time = SystemTime::now();
|
||||
|
||||
// Create a basic eval struct to work with the existing system
|
||||
@@ -430,7 +345,7 @@ pub async fn run_exercise_eval(
|
||||
url: format!("file://{}", temp_path.display()),
|
||||
base_sha: local_commit_sha, // Use the local commit SHA instead of the framework base SHA
|
||||
},
|
||||
user_prompt: prompt,
|
||||
user_prompt: instructions.clone(),
|
||||
};
|
||||
|
||||
// Run the evaluation
|
||||
@@ -441,79 +356,6 @@ pub async fn run_exercise_eval(
|
||||
// Get diff from git
|
||||
let diff = eval_output.diff.clone();
|
||||
|
||||
// For project creation template, we need to compare with reference implementation
|
||||
let judge_output = if template.name == "ProjectCreation" {
|
||||
let project_judge_prompt = template
|
||||
.content
|
||||
.replace(
|
||||
"<!-- ```requirements go here``` -->",
|
||||
&format!("```\n{}\n```", instructions),
|
||||
)
|
||||
.replace(
|
||||
"<!-- ```reference code goes here``` -->",
|
||||
&format!("```{}\n{}\n```", language, example_solution),
|
||||
)
|
||||
.replace(
|
||||
"<!-- ```git diff goes here``` -->",
|
||||
&format!("```\n{}\n```", diff),
|
||||
);
|
||||
|
||||
// Use the run_with_prompt method which we'll add to judge.rs
|
||||
let judge = crate::judge::Judge {
|
||||
original_diff: None,
|
||||
original_message: Some(project_judge_prompt),
|
||||
model: judge_model.clone(),
|
||||
};
|
||||
|
||||
cx.update(|cx| judge.run_with_prompt(cx))?.await?
|
||||
} else if template.name == "CodeModification" {
|
||||
// For CodeModification, we'll compare the example solution with the LLM-generated solution
|
||||
let code_judge_prompt = template
|
||||
.content
|
||||
.replace(
|
||||
"<!-- ```reference code goes here``` -->",
|
||||
&format!("```{}\n{}\n```", language, example_solution),
|
||||
)
|
||||
.replace(
|
||||
"<!-- ```git diff goes here``` -->",
|
||||
&format!("```\n{}\n```", diff),
|
||||
);
|
||||
|
||||
// Use the run_with_prompt method
|
||||
let judge = crate::judge::Judge {
|
||||
original_diff: None,
|
||||
original_message: Some(code_judge_prompt),
|
||||
model: judge_model.clone(),
|
||||
};
|
||||
|
||||
cx.update(|cx| judge.run_with_prompt(cx))?.await?
|
||||
} else {
|
||||
// Conversational template
|
||||
let conv_judge_prompt = template
|
||||
.content
|
||||
.replace(
|
||||
"<!-- ```query goes here``` -->",
|
||||
&format!("```\n{}\n```", instructions),
|
||||
)
|
||||
.replace(
|
||||
"<!-- ```transcript goes here``` -->",
|
||||
&format!("```\n{}\n```", eval_output.last_message),
|
||||
)
|
||||
.replace(
|
||||
"<!-- ```git diff goes here``` -->",
|
||||
&format!("```\n{}\n```", diff),
|
||||
);
|
||||
|
||||
// Use the run_with_prompt method for consistency
|
||||
let judge = crate::judge::Judge {
|
||||
original_diff: None,
|
||||
original_message: Some(conv_judge_prompt),
|
||||
model: judge_model.clone(),
|
||||
};
|
||||
|
||||
cx.update(|cx| judge.run_with_prompt(cx))?.await?
|
||||
};
|
||||
|
||||
let elapsed_time = start_time.elapsed()?;
|
||||
|
||||
// Calculate total tokens as the sum of input and output tokens
|
||||
@@ -522,14 +364,9 @@ pub async fn run_exercise_eval(
|
||||
let tool_use_counts = eval_output.tool_use_counts.values().sum::<u32>();
|
||||
let total_tokens = input_tokens + output_tokens;
|
||||
|
||||
// Get judge model name
|
||||
let judge_model_name = judge_model.id().0.to_string();
|
||||
|
||||
// Save results to evaluation directory
|
||||
let result = EvalResult {
|
||||
exercise_name: exercise_name.clone(),
|
||||
template_name: template.name.to_string(),
|
||||
score: judge_output.trim().to_string(),
|
||||
diff,
|
||||
assistant_response: eval_output.last_message.clone(),
|
||||
elapsed_time_ms: elapsed_time.as_millis(),
|
||||
@@ -541,7 +378,6 @@ pub async fn run_exercise_eval(
|
||||
output_tokens: output_tokens.try_into().unwrap(),
|
||||
total_tokens: total_tokens.try_into().unwrap(),
|
||||
tool_use_counts: tool_use_counts.try_into().unwrap(),
|
||||
judge_model_name, // Add judge model name to result
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
@@ -4,12 +4,10 @@ use assistant_tool::ToolWorkingSet;
|
||||
use client::{Client, UserStore};
|
||||
use collections::HashMap;
|
||||
use dap::DapRegistry;
|
||||
use futures::StreamExt;
|
||||
use gpui::{App, AsyncApp, Entity, SemanticVersion, Subscription, Task, prelude::*};
|
||||
use gpui::{App, Entity, SemanticVersion, Subscription, Task, prelude::*};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModel, LanguageModelProviderId, LanguageModelRegistry,
|
||||
LanguageModelRequest,
|
||||
};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::{Project, RealFs};
|
||||
@@ -246,34 +244,3 @@ pub fn authenticate_model_provider(
|
||||
let model_provider = model_registry.provider(&provider_id).unwrap();
|
||||
model_provider.authenticate(cx)
|
||||
}
|
||||
|
||||
pub async fn send_language_model_request(
|
||||
model: Arc<dyn LanguageModel>,
|
||||
request: LanguageModelRequest,
|
||||
cx: &mut AsyncApp,
|
||||
) -> anyhow::Result<String> {
|
||||
match model.stream_completion_text(request, &cx).await {
|
||||
Ok(mut stream) => {
|
||||
let mut full_response = String::new();
|
||||
|
||||
// Process the response stream
|
||||
while let Some(chunk_result) = stream.stream.next().await {
|
||||
match chunk_result {
|
||||
Ok(chunk_str) => {
|
||||
full_response.push_str(&chunk_str);
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(anyhow!(
|
||||
"Error receiving response from language model: {err}"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(full_response)
|
||||
}
|
||||
Err(err) => Err(anyhow!(
|
||||
"Failed to get response from language model. Error was: {err}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@ mod eval;
|
||||
mod get_exercise;
|
||||
mod git_commands;
|
||||
mod headless_assistant;
|
||||
mod judge;
|
||||
mod templates_eval;
|
||||
|
||||
use clap::Parser;
|
||||
use eval::{run_exercise_eval, save_eval_results};
|
||||
@@ -15,11 +13,10 @@ use headless_assistant::{authenticate_model_provider, find_model};
|
||||
use language_model::LanguageModelRegistry;
|
||||
use reqwest_client::ReqwestClient;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use templates_eval::all_templates;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
name = "assistant_eval",
|
||||
name = "agent_eval",
|
||||
disable_version_flag = true,
|
||||
before_help = "Tool eval runner"
|
||||
)]
|
||||
@@ -37,24 +34,17 @@ struct Args {
|
||||
/// Name of the model (default: "claude-3-7-sonnet-latest")
|
||||
#[arg(long, default_value = "claude-3-7-sonnet-latest")]
|
||||
model_name: String,
|
||||
/// Name of the judge model (default: value of `--model_name`).
|
||||
/// Name of the editor model (default: value of `--model_name`).
|
||||
#[arg(long)]
|
||||
judge_model_name: Option<String>,
|
||||
editor_model_name: Option<String>,
|
||||
/// Number of evaluations to run concurrently (default: 3)
|
||||
#[arg(short, long, default_value = "3")]
|
||||
#[arg(short, long, default_value = "5")]
|
||||
concurrency: usize,
|
||||
/// Maximum number of exercises to evaluate per language
|
||||
#[arg(long)]
|
||||
max_exercises_per_language: Option<usize>,
|
||||
}
|
||||
|
||||
// First, let's define the order in which templates should be executed
|
||||
const TEMPLATE_EXECUTION_ORDER: [&str; 3] = [
|
||||
"ProjectCreation",
|
||||
"CodeModification",
|
||||
"ConversationalGuidance",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let args = Args::parse();
|
||||
@@ -76,7 +66,7 @@ fn main() {
|
||||
let app_state = headless_assistant::init(cx);
|
||||
|
||||
let model = find_model(&args.model_name, cx).unwrap();
|
||||
let judge_model = if let Some(model_name) = &args.judge_model_name {
|
||||
let editor_model = if let Some(model_name) = &args.editor_model_name {
|
||||
find_model(model_name, cx).unwrap()
|
||||
} else {
|
||||
model.clone()
|
||||
@@ -87,7 +77,7 @@ fn main() {
|
||||
});
|
||||
|
||||
let model_provider_id = model.provider_id();
|
||||
let judge_model_provider_id = judge_model.provider_id();
|
||||
let editor_model_provider_id = editor_model.provider_id();
|
||||
|
||||
let framework_path_clone = framework_path.clone();
|
||||
let languages_clone = languages.clone();
|
||||
@@ -100,15 +90,17 @@ fn main() {
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
cx.update(|cx| authenticate_model_provider(judge_model_provider_id.clone(), cx))
|
||||
cx.update(|cx| authenticate_model_provider(editor_model_provider_id.clone(), cx))
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Read base SHA from setup.json
|
||||
println!("framework path: {}", framework_path_clone.display());
|
||||
|
||||
let base_sha = read_base_sha(&framework_path_clone).await.unwrap();
|
||||
|
||||
// Find all exercises for the specified languages
|
||||
println!("base sha: {}", base_sha);
|
||||
|
||||
let all_exercises = find_exercises(
|
||||
&framework_path_clone,
|
||||
&languages_clone
|
||||
@@ -140,23 +132,12 @@ fn main() {
|
||||
|
||||
println!("Will run {} exercises", exercises_to_run.len());
|
||||
|
||||
// Get all templates and sort them according to the execution order
|
||||
let mut templates = all_templates();
|
||||
templates.sort_by_key(|template| {
|
||||
TEMPLATE_EXECUTION_ORDER
|
||||
.iter()
|
||||
.position(|&name| name == template.name)
|
||||
.unwrap_or(usize::MAX)
|
||||
});
|
||||
|
||||
// Create exercise eval tasks - each exercise is a single task that will run templates sequentially
|
||||
let exercise_tasks: Vec<_> = exercises_to_run
|
||||
.into_iter()
|
||||
.map(|exercise_path| {
|
||||
let exercise_name = get_exercise_name(&exercise_path);
|
||||
let templates_clone = templates.clone();
|
||||
let model_clone = model.clone();
|
||||
let judge_model_clone = judge_model.clone();
|
||||
let app_state_clone = app_state.clone();
|
||||
let base_sha_clone = base_sha.clone();
|
||||
let framework_path_clone = framework_path_clone.clone();
|
||||
@@ -166,56 +147,22 @@ fn main() {
|
||||
println!("Processing exercise: {}", exercise_name);
|
||||
let mut exercise_results = Vec::new();
|
||||
|
||||
// Determine the language for this exercise
|
||||
let language = match get_exercise_language(&exercise_path) {
|
||||
Ok(lang) => lang,
|
||||
match run_exercise_eval(
|
||||
exercise_path.clone(),
|
||||
model_clone.clone(),
|
||||
app_state_clone.clone(),
|
||||
base_sha_clone.clone(),
|
||||
framework_path_clone.clone(),
|
||||
cx_clone.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
println!("Completed {}", exercise_name);
|
||||
exercise_results.push(result);
|
||||
}
|
||||
Err(err) => {
|
||||
println!(
|
||||
"Error determining language for {}: {}",
|
||||
exercise_name, err
|
||||
);
|
||||
return exercise_results;
|
||||
}
|
||||
};
|
||||
|
||||
// Run each template sequentially for this exercise
|
||||
for template in templates_clone {
|
||||
// For "multi" or "internal" language, only run the CodeModification template
|
||||
if (language == "multi" || language == "internal")
|
||||
&& template.name != "CodeModification"
|
||||
{
|
||||
println!(
|
||||
"Skipping {} template for {} language",
|
||||
template.name, language
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
match run_exercise_eval(
|
||||
exercise_path.clone(),
|
||||
template.clone(),
|
||||
model_clone.clone(),
|
||||
judge_model_clone.clone(),
|
||||
app_state_clone.clone(),
|
||||
base_sha_clone.clone(),
|
||||
framework_path_clone.clone(),
|
||||
cx_clone.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
println!(
|
||||
"Completed {} with template {} - score: {}",
|
||||
exercise_name, template.name, result.score
|
||||
);
|
||||
exercise_results.push(result);
|
||||
}
|
||||
Err(err) => {
|
||||
println!(
|
||||
"Error running {} with template {}: {}",
|
||||
exercise_name, template.name, err
|
||||
);
|
||||
}
|
||||
println!("Error running {}: {}", exercise_name, err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,38 +321,54 @@ pub async fn stream_completion(
|
||||
.map(|output| output.0)
|
||||
}
|
||||
|
||||
/// An individual rate limit.
|
||||
#[derive(Debug)]
|
||||
pub struct RateLimit {
|
||||
pub limit: usize,
|
||||
pub remaining: usize,
|
||||
pub reset: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl RateLimit {
|
||||
fn from_headers(resource: &str, headers: &HeaderMap<HeaderValue>) -> Result<Self> {
|
||||
let limit =
|
||||
get_header(&format!("anthropic-ratelimit-{resource}-limit"), headers)?.parse()?;
|
||||
let remaining = get_header(
|
||||
&format!("anthropic-ratelimit-{resource}-remaining"),
|
||||
headers,
|
||||
)?
|
||||
.parse()?;
|
||||
let reset = DateTime::parse_from_rfc3339(get_header(
|
||||
&format!("anthropic-ratelimit-{resource}-reset"),
|
||||
headers,
|
||||
)?)?
|
||||
.to_utc();
|
||||
|
||||
Ok(Self {
|
||||
limit,
|
||||
remaining,
|
||||
reset,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://docs.anthropic.com/en/api/rate-limits#response-headers>
|
||||
#[derive(Debug)]
|
||||
pub struct RateLimitInfo {
|
||||
pub requests_limit: usize,
|
||||
pub requests_remaining: usize,
|
||||
pub requests_reset: DateTime<Utc>,
|
||||
pub tokens_limit: usize,
|
||||
pub tokens_remaining: usize,
|
||||
pub tokens_reset: DateTime<Utc>,
|
||||
pub requests: Option<RateLimit>,
|
||||
pub tokens: Option<RateLimit>,
|
||||
pub input_tokens: Option<RateLimit>,
|
||||
pub output_tokens: Option<RateLimit>,
|
||||
}
|
||||
|
||||
impl RateLimitInfo {
|
||||
fn from_headers(headers: &HeaderMap<HeaderValue>) -> Result<Self> {
|
||||
let tokens_limit = get_header("anthropic-ratelimit-tokens-limit", headers)?.parse()?;
|
||||
let requests_limit = get_header("anthropic-ratelimit-requests-limit", headers)?.parse()?;
|
||||
let tokens_remaining =
|
||||
get_header("anthropic-ratelimit-tokens-remaining", headers)?.parse()?;
|
||||
let requests_remaining =
|
||||
get_header("anthropic-ratelimit-requests-remaining", headers)?.parse()?;
|
||||
let requests_reset = get_header("anthropic-ratelimit-requests-reset", headers)?;
|
||||
let tokens_reset = get_header("anthropic-ratelimit-tokens-reset", headers)?;
|
||||
let requests_reset = DateTime::parse_from_rfc3339(requests_reset)?.to_utc();
|
||||
let tokens_reset = DateTime::parse_from_rfc3339(tokens_reset)?.to_utc();
|
||||
|
||||
Ok(Self {
|
||||
requests_limit,
|
||||
tokens_limit,
|
||||
requests_remaining,
|
||||
tokens_remaining,
|
||||
requests_reset,
|
||||
tokens_reset,
|
||||
})
|
||||
fn from_headers(headers: &HeaderMap<HeaderValue>) -> Self {
|
||||
Self {
|
||||
requests: RateLimit::from_headers("requests", headers).log_err(),
|
||||
tokens: RateLimit::from_headers("tokens", headers).log_err(),
|
||||
input_tokens: RateLimit::from_headers("input-tokens", headers).log_err(),
|
||||
output_tokens: RateLimit::from_headers("output-tokens", headers).log_err(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,7 +434,7 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
}
|
||||
})
|
||||
.boxed();
|
||||
Ok((stream, rate_limits.log_err()))
|
||||
Ok((stream, Some(rate_limits)))
|
||||
} else {
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
|
||||
@@ -57,7 +57,7 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
use theme::ThemeSettings;
|
||||
@@ -315,7 +315,7 @@ impl InlineAssistant {
|
||||
if let Some(ConfiguredModel { model, .. }) =
|
||||
LanguageModelRegistry::read_global(cx).default_model()
|
||||
{
|
||||
self.telemetry.report_assistant_event(AssistantEvent {
|
||||
self.telemetry.report_assistant_event(AssistantEventData {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
phase: AssistantPhase::Invoked,
|
||||
@@ -892,7 +892,7 @@ impl InlineAssistant {
|
||||
.map(|language| language.name())
|
||||
});
|
||||
report_assistant_event(
|
||||
AssistantEvent {
|
||||
AssistantEventData {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
message_id,
|
||||
@@ -3148,7 +3148,7 @@ impl CodegenAlternative {
|
||||
|
||||
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||
report_assistant_event(
|
||||
AssistantEvent {
|
||||
AssistantEventData {
|
||||
conversation_id: None,
|
||||
message_id,
|
||||
kind: AssistantKind::Inline,
|
||||
|
||||
@@ -27,7 +27,7 @@ use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use terminal::Terminal;
|
||||
use terminal_view::TerminalView;
|
||||
use theme::ThemeSettings;
|
||||
@@ -324,7 +324,7 @@ impl TerminalInlineAssistant {
|
||||
let codegen = assist.codegen.read(cx);
|
||||
let executor = cx.background_executor().clone();
|
||||
report_assistant_event(
|
||||
AssistantEvent {
|
||||
AssistantEventData {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::InlineTerminal,
|
||||
message_id: codegen.message_id.clone(),
|
||||
@@ -1183,7 +1183,7 @@ impl Codegen {
|
||||
|
||||
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||
report_assistant_event(
|
||||
AssistantEvent {
|
||||
AssistantEventData {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::InlineTerminal,
|
||||
message_id,
|
||||
|
||||
@@ -40,7 +40,7 @@ use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use text::{BufferSnapshot, ToPoint};
|
||||
use ui::IconName;
|
||||
use util::{ResultExt, TryFutureExt, post_inc};
|
||||
@@ -2498,7 +2498,7 @@ impl AssistantContext {
|
||||
.language()
|
||||
.map(|language| language.name());
|
||||
report_assistant_event(
|
||||
AssistantEvent {
|
||||
AssistantEventData {
|
||||
conversation_id: Some(this.id.0.clone()),
|
||||
kind: AssistantKind::Panel,
|
||||
phase: AssistantPhase::Response,
|
||||
|
||||
@@ -127,6 +127,7 @@ impl SlashCommandCompletionProvider {
|
||||
new_text,
|
||||
label: command.label(cx),
|
||||
icon_path: None,
|
||||
insert_text_mode: None,
|
||||
confirm,
|
||||
source: CompletionSource::Custom,
|
||||
})
|
||||
@@ -228,6 +229,7 @@ impl SlashCommandCompletionProvider {
|
||||
new_text,
|
||||
documentation: None,
|
||||
confirm,
|
||||
insert_text_mode: None,
|
||||
source: CompletionSource::Custom,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# Tool Evals
|
||||
|
||||
A framework for evaluating and benchmarking the agent panel generations.
|
||||
|
||||
## Overview
|
||||
|
||||
Tool Evals provides a headless environment for running assistants evaluations on code repositories. It automates the process of:
|
||||
|
||||
1. Setting up test code and repositories
|
||||
2. Sending prompts to language models
|
||||
3. Allowing the assistant to use tools to modify code
|
||||
4. Collecting metrics on performance and tool usage
|
||||
5. Evaluating results against known good solutions
|
||||
|
||||
## How It Works
|
||||
|
||||
The system consists of several key components:
|
||||
|
||||
- **Eval**: Loads exercises from the zed-ace-framework repository, creates temporary repos, and executes evaluations
|
||||
- **HeadlessAssistant**: Provides a headless environment for running the AI assistant
|
||||
- **Judge**: Evaluates AI-generated solutions against reference implementations and assigns scores
|
||||
- **Templates**: Defines evaluation frameworks for different tasks (Project Creation, Code Modification, Conversational Guidance)
|
||||
|
||||
## Setup Requirements
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Rust and Cargo
|
||||
- Git
|
||||
- Python (for report generation)
|
||||
- Network access to clone repositories
|
||||
- Appropriate API keys for language models and git services (Anthropic, GitHub, etc.)
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Ensure you have the required API keys set, either from a dev run of Zed or via these environment variables:
|
||||
- `ZED_ANTHROPIC_API_KEY` for Claude models
|
||||
- `ZED_GITHUB_API_KEY` for GitHub API (or similar)
|
||||
|
||||
## Usage
|
||||
|
||||
### Running Evaluations
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
cargo run -p assistant_eval -- --all
|
||||
|
||||
# Run only specific languages
|
||||
cargo run -p assistant_eval -- --all --languages python,rust
|
||||
|
||||
# Limit concurrent evaluations
|
||||
cargo run -p assistant_eval -- --all --concurrency 5
|
||||
|
||||
# Limit number of exercises per language
|
||||
cargo run -p assistant_eval -- --all --max-exercises-per-language 3
|
||||
```
|
||||
|
||||
### Evaluation Template Types
|
||||
|
||||
The system supports three types of evaluation templates:
|
||||
|
||||
1. **ProjectCreation**: Tests the model's ability to create new implementations from scratch
|
||||
2. **CodeModification**: Tests the model's ability to modify existing code to meet new requirements
|
||||
3. **ConversationalGuidance**: Tests the model's ability to provide guidance without writing code
|
||||
|
||||
### Support Repo
|
||||
|
||||
The [zed-industries/zed-ace-framework](https://github.com/zed-industries/zed-ace-framework) contains the analytics and reporting scripts.
|
||||
@@ -1,37 +0,0 @@
|
||||
use crate::headless_assistant::send_language_model_request;
|
||||
use anyhow::anyhow;
|
||||
use gpui::{App, Task};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelRequest, LanguageModelRequestMessage, MessageContent, Role,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Judge {
|
||||
#[allow(dead_code)]
|
||||
pub original_diff: Option<String>,
|
||||
pub original_message: Option<String>,
|
||||
pub model: Arc<dyn LanguageModel>,
|
||||
}
|
||||
|
||||
impl Judge {
|
||||
pub fn run_with_prompt(&self, cx: &mut App) -> Task<anyhow::Result<String>> {
|
||||
let Some(prompt) = self.original_message.as_ref() else {
|
||||
return Task::ready(Err(anyhow!("No prompt provided in original_message")));
|
||||
};
|
||||
|
||||
let request = LanguageModelRequest {
|
||||
messages: vec![LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![MessageContent::Text(prompt.clone())],
|
||||
cache: false,
|
||||
}],
|
||||
temperature: Some(0.0),
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
};
|
||||
|
||||
let model = self.model.clone();
|
||||
let request = request.clone();
|
||||
cx.spawn(async move |cx| send_language_model_request(model, request, cx).await)
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Template {
|
||||
pub name: &'static str,
|
||||
pub content: &'static str,
|
||||
}
|
||||
|
||||
pub fn all_templates() -> Vec<Template> {
|
||||
vec![
|
||||
Template {
|
||||
name: "ProjectCreation",
|
||||
content: r#"
|
||||
# Project Creation Evaluation Template
|
||||
|
||||
## Instructions
|
||||
|
||||
Evaluate how well the AI assistant created a new implementation from scratch. Score it between 0.0 and 1.0 based on quality and fulfillment of requirements.
|
||||
- 1.0 = Perfect implementation that creates all necessary files with correct functionality.
|
||||
- 0.0 = Completely fails to create working files or meet requirements.
|
||||
|
||||
Note: A git diff output is required. If no code changes are provided (i.e., no git diff output), the score must be 0.0.
|
||||
|
||||
## Evaluation Criteria
|
||||
|
||||
Please consider the following aspects in order of importance:
|
||||
|
||||
1. **File Creation (25%)**
|
||||
- Did the assistant create all necessary files?
|
||||
- Are the files appropriately named and organized?
|
||||
- Did the assistant create a complete solution without missing components?
|
||||
|
||||
2. **Functional Correctness (40%)**
|
||||
- Does the implementation fulfill all specified requirements?
|
||||
- Does it handle edge cases properly?
|
||||
- Is it free of logical errors and bugs?
|
||||
- Do all components work together as expected?
|
||||
|
||||
3. **Code Quality (20%)**
|
||||
- Is the code well-structured, readable and well-documented?
|
||||
- Does it follow language-specific best practices?
|
||||
- Is there proper error handling?
|
||||
- Are naming conventions clear and consistent?
|
||||
|
||||
4. **Architecture Design (15%)**
|
||||
- Is the code modular and extensible?
|
||||
- Is there proper separation of concerns?
|
||||
- Are appropriate design patterns used?
|
||||
- Is the overall architecture appropriate for the requirements?
|
||||
|
||||
## Input
|
||||
|
||||
Requirements:
|
||||
<!-- ```requirements go here``` -->
|
||||
|
||||
Reference Implementation:
|
||||
<!-- ```reference code goes here``` -->
|
||||
|
||||
AI-Generated Implementation (git diff output):
|
||||
<!-- ```git diff goes here``` -->
|
||||
|
||||
## Output Format
|
||||
|
||||
THE ONLY OUTPUT SHOULD BE A SCORE BETWEEN 0.0 AND 1.0.
|
||||
|
||||
EXAMPLE ONE:
|
||||
|
||||
0.92
|
||||
|
||||
EXAMPLE TWO:
|
||||
|
||||
0.85
|
||||
|
||||
EXAMPLE THREE:
|
||||
|
||||
0.78
|
||||
"#,
|
||||
},
|
||||
Template {
|
||||
name: "CodeModification",
|
||||
content: r#"
|
||||
# Code Modification Evaluation Template
|
||||
|
||||
## Instructions
|
||||
|
||||
Evaluate how well the AI assistant modified existing code to meet requirements. Score between 0.0 and 1.0 based on quality and appropriateness of changes.
|
||||
- 1.0 = Perfect modifications that correctly implement all requirements.
|
||||
- 0.0 = Failed to make appropriate changes or introduced serious errors.
|
||||
|
||||
## Evaluation Criteria
|
||||
|
||||
Please consider the following aspects in order of importance:
|
||||
|
||||
1. **Functional Correctness (50%)**
|
||||
- Do the modifications correctly implement the requirements?
|
||||
- Did the assistant modify the right files and code sections?
|
||||
- Are the changes free of bugs and logical errors?
|
||||
- Do the modifications maintain compatibility with existing code?
|
||||
|
||||
2. **Modification Approach (25%)**
|
||||
- Are the changes minimal and focused on what needs to be changed?
|
||||
- Did the assistant avoid unnecessary modifications?
|
||||
- Are the changes integrated seamlessly with the existing codebase?
|
||||
- Did the assistant preserve the original code style and patterns?
|
||||
|
||||
3. **Code Quality (15%)**
|
||||
- Are the modifications well-structured and documented?
|
||||
- Do they follow the same conventions as the original code?
|
||||
- Is there proper error handling in the modified code?
|
||||
- Are the changes readable and maintainable?
|
||||
|
||||
4. **Solution Completeness (10%)**
|
||||
- Do the modifications completely address all requirements?
|
||||
- Are there any missing changes or overlooked requirements?
|
||||
- Did the assistant consider all necessary edge cases?
|
||||
|
||||
## Input
|
||||
|
||||
Original:
|
||||
<!-- ```reference code goes here``` -->
|
||||
|
||||
New (git diff output):
|
||||
<!-- ```git diff goes here``` -->
|
||||
|
||||
## Output Format
|
||||
|
||||
THE ONLY OUTPUT SHOULD BE A SCORE BETWEEN 0.0 AND 1.0.
|
||||
|
||||
EXAMPLE ONE:
|
||||
|
||||
0.92
|
||||
|
||||
EXAMPLE TWO:
|
||||
|
||||
0.85
|
||||
|
||||
EXAMPLE THREE:
|
||||
|
||||
0.78
|
||||
"#,
|
||||
},
|
||||
Template {
|
||||
name: "ConversationalGuidance",
|
||||
content: r#"
|
||||
# Conversational Guidance Evaluation Template
|
||||
|
||||
## Instructions
|
||||
|
||||
Evaluate the quality of the AI assistant's conversational guidance and score it between 0.0 and 1.0.
|
||||
- 1.0 = Perfect guidance with ideal information gathering, clarification, and advice without writing code.
|
||||
- 0.0 = Completely unhelpful, inappropriate guidance, or wrote code when it should not have.
|
||||
|
||||
## Evaluation Criteria
|
||||
|
||||
ABSOLUTE REQUIREMENT:
|
||||
- The assistant should NOT generate complete code solutions in conversation mode.
|
||||
- If the git diff shows the assistant wrote complete code, the score should be significantly reduced.
|
||||
|
||||
1. **Information Gathering Effectiveness (30%)**
|
||||
- Did the assistant ask relevant and precise questions?
|
||||
- Did it efficiently narrow down the problem scope?
|
||||
- Did it avoid unnecessary or redundant questions?
|
||||
- Was questioning appropriately paced and contextual?
|
||||
|
||||
2. **Conceptual Guidance (30%)**
|
||||
- Did the assistant provide high-level approaches and strategies?
|
||||
- Did it explain relevant concepts and algorithms?
|
||||
- Did it offer planning advice without implementing the solution?
|
||||
- Did it suggest a structured approach to solving the problem?
|
||||
|
||||
3. **Educational Value (20%)**
|
||||
- Did the assistant help the user understand the problem better?
|
||||
- Did it provide explanations that would help the user learn?
|
||||
- Did it guide without simply giving away answers?
|
||||
- Did it encourage the user to think through parts of the problem?
|
||||
|
||||
4. **Conversation Quality (20%)**
|
||||
- Was the conversation logically structured and easy to follow?
|
||||
- Did the assistant maintain appropriate context throughout?
|
||||
- Was the interaction helpful without being condescending?
|
||||
- Did the conversation reach a satisfactory conclusion with clear next steps?
|
||||
|
||||
## Input
|
||||
|
||||
Initial Query:
|
||||
<!-- ```query goes here``` -->
|
||||
|
||||
Conversation Transcript:
|
||||
<!-- ```transcript goes here``` -->
|
||||
|
||||
Git Diff:
|
||||
<!-- ```git diff goes here``` -->
|
||||
|
||||
## Output Format
|
||||
|
||||
THE ONLY OUTPUT SHOULD BE A SCORE BETWEEN 0.0 AND 1.0.
|
||||
|
||||
EXAMPLE ONE:
|
||||
|
||||
0.92
|
||||
|
||||
EXAMPLE TWO:
|
||||
|
||||
0.85
|
||||
|
||||
EXAMPLE THREE:
|
||||
|
||||
0.78
|
||||
"#,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -23,6 +23,7 @@ http_client.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
lsp.workspace = true
|
||||
project.workspace = true
|
||||
regex.workspace = true
|
||||
schemars.workspace = true
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod bash_tool;
|
||||
mod batch_tool;
|
||||
mod code_symbol_iter;
|
||||
mod code_symbols_tool;
|
||||
mod copy_path_tool;
|
||||
mod create_directory_tool;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use futures::io::BufReader;
|
||||
use futures::{AsyncBufReadExt, AsyncReadExt};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -16,7 +14,7 @@ use util::markdown::MarkdownString;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct BashToolInput {
|
||||
/// The bash one-liner command to execute.
|
||||
/// The bash command to execute as a one-liner.
|
||||
command: String,
|
||||
/// Working directory for the command. This must be one of the root directories of the project.
|
||||
cd: String,
|
||||
@@ -127,90 +125,29 @@ impl Tool for BashTool {
|
||||
// Add 2>&1 to merge stderr into stdout for proper interleaving.
|
||||
let command = format!("({}) 2>&1", input.command);
|
||||
|
||||
let mut cmd = new_smol_command("bash")
|
||||
let output = new_smol_command("bash")
|
||||
.arg("-c")
|
||||
.arg(&command)
|
||||
.current_dir(working_dir)
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to execute bash command")?;
|
||||
|
||||
// Capture stdout with a limit
|
||||
let stdout = cmd.stdout.take().unwrap();
|
||||
let mut reader = BufReader::new(stdout);
|
||||
let output_string = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
|
||||
const MESSAGE_1: &str = "Command output too long. The first ";
|
||||
const MESSAGE_2: &str = " bytes:\n\n";
|
||||
const ERR_MESSAGE_1: &str = "Command failed with exit code ";
|
||||
const ERR_MESSAGE_2: &str = "\n\n";
|
||||
|
||||
const STDOUT_LIMIT: usize = 8192;
|
||||
|
||||
const LIMIT: usize = STDOUT_LIMIT
|
||||
- (MESSAGE_1.len()
|
||||
+ (STDOUT_LIMIT.ilog10() as usize + 1) // byte count
|
||||
+ MESSAGE_2.len()
|
||||
+ ERR_MESSAGE_1.len()
|
||||
+ 3 // status code
|
||||
+ ERR_MESSAGE_2.len());
|
||||
|
||||
// Read one more byte to determine whether the output was truncated
|
||||
let mut buffer = vec![0; LIMIT + 1];
|
||||
let bytes_read = reader.read(&mut buffer).await?;
|
||||
|
||||
// Repeatedly fill the output reader's buffer without copying it.
|
||||
loop {
|
||||
let skipped_bytes = reader.fill_buf().await?;
|
||||
if skipped_bytes.is_empty() {
|
||||
break;
|
||||
}
|
||||
let skipped_bytes_len = skipped_bytes.len();
|
||||
reader.consume_unpin(skipped_bytes_len);
|
||||
}
|
||||
|
||||
let output_bytes = &buffer[..bytes_read];
|
||||
|
||||
// Let the process continue running
|
||||
let status = cmd.status().await.context("Failed to get command status")?;
|
||||
|
||||
let output_string = if bytes_read > LIMIT {
|
||||
// Valid to find `\n` in UTF-8 since 0-127 ASCII characters are not used in
|
||||
// multi-byte characters.
|
||||
let last_line_ix = output_bytes.iter().rposition(|b| *b == b'\n');
|
||||
let output_string = String::from_utf8_lossy(
|
||||
&output_bytes[..last_line_ix.unwrap_or(output_bytes.len())],
|
||||
);
|
||||
|
||||
format!(
|
||||
"{}{}{}{}",
|
||||
MESSAGE_1,
|
||||
output_string.len(),
|
||||
MESSAGE_2,
|
||||
output_string
|
||||
)
|
||||
} else {
|
||||
String::from_utf8_lossy(&output_bytes).into()
|
||||
};
|
||||
|
||||
let output_with_status = if status.success() {
|
||||
if output.status.success() {
|
||||
if output_string.is_empty() {
|
||||
"Command executed successfully.".to_string()
|
||||
Ok("Command executed successfully.".to_string())
|
||||
} else {
|
||||
output_string.to_string()
|
||||
Ok(output_string)
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
"{}{}{}{}",
|
||||
ERR_MESSAGE_1,
|
||||
status.code().unwrap_or(-1),
|
||||
ERR_MESSAGE_2,
|
||||
output_string,
|
||||
)
|
||||
};
|
||||
|
||||
debug_assert!(output_with_status.len() <= STDOUT_LIMIT);
|
||||
|
||||
Ok(output_with_status)
|
||||
Ok(format!(
|
||||
"Command failed with exit code {}\n{}",
|
||||
output.status.code().unwrap_or(-1),
|
||||
&output_string
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
88
crates/assistant_tools/src/code_symbol_iter.rs
Normal file
88
crates/assistant_tools/src/code_symbol_iter.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use project::DocumentSymbol;
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Entry {
|
||||
pub name: String,
|
||||
pub kind: lsp::SymbolKind,
|
||||
pub depth: u32,
|
||||
pub start_line: usize,
|
||||
pub end_line: usize,
|
||||
}
|
||||
|
||||
/// An iterator that filters document symbols based on a regex pattern.
|
||||
/// This iterator recursively traverses the document symbol tree, incrementing depth for child symbols.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CodeSymbolIterator<'a> {
|
||||
symbols: &'a [DocumentSymbol],
|
||||
regex: Option<Regex>,
|
||||
// Stack of (symbol, depth) pairs to process
|
||||
pending_symbols: Vec<(&'a DocumentSymbol, u32)>,
|
||||
current_index: usize,
|
||||
current_depth: u32,
|
||||
}
|
||||
|
||||
impl<'a> CodeSymbolIterator<'a> {
|
||||
pub fn new(symbols: &'a [DocumentSymbol], regex: Option<Regex>) -> Self {
|
||||
Self {
|
||||
symbols,
|
||||
regex,
|
||||
pending_symbols: Vec::new(),
|
||||
current_index: 0,
|
||||
current_depth: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for CodeSymbolIterator<'_> {
|
||||
type Item = Entry;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some((symbol, depth)) = self.pending_symbols.pop() {
|
||||
for child in symbol.children.iter().rev() {
|
||||
self.pending_symbols.push((child, depth + 1));
|
||||
}
|
||||
|
||||
return Some(Entry {
|
||||
name: symbol.name.clone(),
|
||||
kind: symbol.kind,
|
||||
depth,
|
||||
start_line: symbol.range.start.0.row as usize,
|
||||
end_line: symbol.range.end.0.row as usize,
|
||||
});
|
||||
}
|
||||
|
||||
while self.current_index < self.symbols.len() {
|
||||
let regex = self.regex.as_ref();
|
||||
let symbol = &self.symbols[self.current_index];
|
||||
self.current_index += 1;
|
||||
|
||||
if regex.is_none_or(|regex| regex.is_match(&symbol.name)) {
|
||||
// Push in reverse order to maintain traversal order
|
||||
for child in symbol.children.iter().rev() {
|
||||
self.pending_symbols.push((child, self.current_depth + 1));
|
||||
}
|
||||
|
||||
return Some(Entry {
|
||||
name: symbol.name.clone(),
|
||||
kind: symbol.kind,
|
||||
depth: self.current_depth,
|
||||
start_line: symbol.range.start.0.row as usize,
|
||||
end_line: symbol.range.end.0.row as usize,
|
||||
});
|
||||
} else {
|
||||
// Even if parent doesn't match, push children to check them later
|
||||
for child in symbol.children.iter().rev() {
|
||||
self.pending_symbols.push((child, self.current_depth + 1));
|
||||
}
|
||||
|
||||
// Check if any pending children match our criteria
|
||||
if let Some(result) = self.next() {
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
use std::fmt::Write;
|
||||
use std::fmt::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use collections::IndexMap;
|
||||
use gpui::{App, AsyncApp, Entity, Task};
|
||||
use language::{OutlineItem, ParseStatus, Point};
|
||||
use language::{CodeLabel, Language, LanguageRegistry};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::{Project, Symbol};
|
||||
use lsp::SymbolKind;
|
||||
use project::{DocumentSymbol, Project, Symbol};
|
||||
use regex::{Regex, RegexBuilder};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
|
||||
use crate::code_symbol_iter::{CodeSymbolIterator, Entry};
|
||||
use crate::schema::json_schema_for;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct CodeSymbolsInput {
|
||||
/// The relative path of the source code file to read and get the symbols for.
|
||||
@@ -177,28 +180,24 @@ pub async fn file_outline(
|
||||
action_log.buffer_read(buffer.clone(), cx);
|
||||
})?;
|
||||
|
||||
// Wait until the buffer has been fully parsed, so that we can read its outline.
|
||||
let mut parse_status = buffer.read_with(cx, |buffer, _| buffer.parse_status())?;
|
||||
while parse_status
|
||||
.recv()
|
||||
.await
|
||||
.map_or(false, |status| status != ParseStatus::Idle)
|
||||
{}
|
||||
let symbols = project
|
||||
.update(cx, |project, cx| project.document_symbols(&buffer, cx))?
|
||||
.await?;
|
||||
|
||||
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
|
||||
let Some(outline) = snapshot.outline(None) else {
|
||||
return Err(anyhow!("No outline information available for this file."));
|
||||
};
|
||||
if symbols.is_empty() {
|
||||
return Err(
|
||||
if buffer.read_with(cx, |buffer, _| buffer.snapshot().is_empty())? {
|
||||
anyhow!("This file is empty.")
|
||||
} else {
|
||||
anyhow!("No outline information available for this file.")
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
render_outline(
|
||||
outline
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|item| item.to_point(&snapshot)),
|
||||
regex,
|
||||
offset,
|
||||
)
|
||||
.await
|
||||
let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned())?;
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone())?;
|
||||
|
||||
render_outline(&symbols, language, language_registry, regex, offset).await
|
||||
}
|
||||
|
||||
async fn project_symbols(
|
||||
@@ -293,27 +292,61 @@ async fn project_symbols(
|
||||
}
|
||||
|
||||
async fn render_outline(
|
||||
items: impl IntoIterator<Item = OutlineItem<Point>>,
|
||||
symbols: &[DocumentSymbol],
|
||||
language: Option<Arc<Language>>,
|
||||
registry: Arc<LanguageRegistry>,
|
||||
regex: Option<Regex>,
|
||||
offset: u32,
|
||||
) -> Result<String> {
|
||||
const RESULTS_PER_PAGE_USIZE: usize = RESULTS_PER_PAGE as usize;
|
||||
let entries = CodeSymbolIterator::new(symbols, regex.clone())
|
||||
.skip(offset as usize)
|
||||
// Take 1 more than RESULTS_PER_PAGE so we can tell if there are more results.
|
||||
.take(RESULTS_PER_PAGE_USIZE.saturating_add(1))
|
||||
.collect::<Vec<Entry>>();
|
||||
let has_more = entries.len() > RESULTS_PER_PAGE_USIZE;
|
||||
|
||||
let mut items = items.into_iter().skip(offset as usize);
|
||||
// Get language-specific labels, if available
|
||||
let labels = match &language {
|
||||
Some(lang) => {
|
||||
let entries_for_labels: Vec<(String, SymbolKind)> = entries
|
||||
.iter()
|
||||
.take(RESULTS_PER_PAGE_USIZE)
|
||||
.map(|entry| (entry.name.clone(), entry.kind))
|
||||
.collect();
|
||||
|
||||
let entries = items
|
||||
.by_ref()
|
||||
.filter(|item| {
|
||||
regex
|
||||
.as_ref()
|
||||
.is_none_or(|regex| regex.is_match(&item.text))
|
||||
})
|
||||
.take(RESULTS_PER_PAGE_USIZE)
|
||||
.collect::<Vec<_>>();
|
||||
let has_more = items.next().is_some();
|
||||
let lang_name = lang.name();
|
||||
if let Some(lsp_adapter) = registry.lsp_adapters(&lang_name).first().cloned() {
|
||||
lsp_adapter
|
||||
.labels_for_symbols(&entries_for_labels, lang)
|
||||
.await
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let mut output = String::new();
|
||||
let entries_rendered = render_entries(&mut output, entries);
|
||||
|
||||
let entries_rendered = match &labels {
|
||||
Some(label_list) => render_entries(
|
||||
&mut output,
|
||||
entries
|
||||
.into_iter()
|
||||
.take(RESULTS_PER_PAGE_USIZE)
|
||||
.zip(label_list.iter())
|
||||
.map(|(entry, label)| (entry, label.as_ref())),
|
||||
),
|
||||
None => render_entries(
|
||||
&mut output,
|
||||
entries
|
||||
.into_iter()
|
||||
.take(RESULTS_PER_PAGE_USIZE)
|
||||
.map(|entry| (entry, None)),
|
||||
),
|
||||
};
|
||||
|
||||
// Calculate pagination information
|
||||
let page_start = offset + 1;
|
||||
@@ -339,19 +372,31 @@ async fn render_outline(
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn render_entries(output: &mut String, items: impl IntoIterator<Item = OutlineItem<Point>>) -> u32 {
|
||||
fn render_entries<'a>(
|
||||
output: &mut String,
|
||||
entries: impl IntoIterator<Item = (Entry, Option<&'a CodeLabel>)>,
|
||||
) -> u32 {
|
||||
let mut entries_rendered = 0;
|
||||
|
||||
for item in items {
|
||||
for (entry, label) in entries {
|
||||
// Indent based on depth ("" for level 0, " " for level 1, etc.)
|
||||
for _ in 0..item.depth {
|
||||
output.push(' ');
|
||||
for _ in 0..entry.depth {
|
||||
output.push_str(" ");
|
||||
}
|
||||
|
||||
match label {
|
||||
Some(label) => {
|
||||
output.push_str(label.text());
|
||||
}
|
||||
None => {
|
||||
write_symbol_kind(output, entry.kind).ok();
|
||||
output.push_str(&entry.name);
|
||||
}
|
||||
}
|
||||
output.push_str(&item.text);
|
||||
|
||||
// Add position information - convert to 1-based line numbers for display
|
||||
let start_line = item.range.start.row + 1;
|
||||
let end_line = item.range.end.row + 1;
|
||||
let start_line = entry.start_line + 1;
|
||||
let end_line = entry.end_line + 1;
|
||||
|
||||
if start_line == end_line {
|
||||
writeln!(output, " [L{}]", start_line).ok();
|
||||
@@ -363,3 +408,38 @@ fn render_entries(output: &mut String, items: impl IntoIterator<Item = OutlineIt
|
||||
|
||||
entries_rendered
|
||||
}
|
||||
|
||||
// We may not have a language server adapter to have language-specific
|
||||
// ways to translate SymbolKnd into a string. In that situation,
|
||||
// fall back on some reasonable default strings to render.
|
||||
fn write_symbol_kind(buf: &mut String, kind: SymbolKind) -> Result<(), fmt::Error> {
|
||||
match kind {
|
||||
SymbolKind::FILE => write!(buf, "file "),
|
||||
SymbolKind::MODULE => write!(buf, "module "),
|
||||
SymbolKind::NAMESPACE => write!(buf, "namespace "),
|
||||
SymbolKind::PACKAGE => write!(buf, "package "),
|
||||
SymbolKind::CLASS => write!(buf, "class "),
|
||||
SymbolKind::METHOD => write!(buf, "method "),
|
||||
SymbolKind::PROPERTY => write!(buf, "property "),
|
||||
SymbolKind::FIELD => write!(buf, "field "),
|
||||
SymbolKind::CONSTRUCTOR => write!(buf, "constructor "),
|
||||
SymbolKind::ENUM => write!(buf, "enum "),
|
||||
SymbolKind::INTERFACE => write!(buf, "interface "),
|
||||
SymbolKind::FUNCTION => write!(buf, "function "),
|
||||
SymbolKind::VARIABLE => write!(buf, "variable "),
|
||||
SymbolKind::CONSTANT => write!(buf, "constant "),
|
||||
SymbolKind::STRING => write!(buf, "string "),
|
||||
SymbolKind::NUMBER => write!(buf, "number "),
|
||||
SymbolKind::BOOLEAN => write!(buf, "boolean "),
|
||||
SymbolKind::ARRAY => write!(buf, "array "),
|
||||
SymbolKind::OBJECT => write!(buf, "object "),
|
||||
SymbolKind::KEY => write!(buf, "key "),
|
||||
SymbolKind::NULL => write!(buf, "null "),
|
||||
SymbolKind::ENUM_MEMBER => write!(buf, "enum member "),
|
||||
SymbolKind::STRUCT => write!(buf, "struct "),
|
||||
SymbolKind::EVENT => write!(buf, "event "),
|
||||
SymbolKind::OPERATOR => write!(buf, "operator "),
|
||||
SymbolKind::TYPE_PARAMETER => write!(buf, "type parameter "),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{code_symbols_tool::file_outline, schema::json_schema_for};
|
||||
use crate::code_symbols_tool::file_outline;
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use gpui::{App, Entity, Task};
|
||||
@@ -15,7 +16,7 @@ use util::markdown::MarkdownString;
|
||||
/// If the model requests to read a file whose size exceeds this, then
|
||||
/// the tool will return an error along with the model's symbol outline,
|
||||
/// and suggest trying again using line ranges from the outline.
|
||||
const MAX_FILE_SIZE_TO_READ: usize = 16384;
|
||||
const MAX_FILE_SIZE_TO_READ: usize = 4096;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ReadFileToolInput {
|
||||
|
||||
@@ -131,8 +131,9 @@ impl Render for Breadcrumbs {
|
||||
}),
|
||||
),
|
||||
None => element
|
||||
// Match the height of the `ButtonLike` in the other arm.
|
||||
// Match the height and padding of the `ButtonLike` in the other arm.
|
||||
.h(rems_from_px(22.))
|
||||
.pl_1()
|
||||
.child(breadcrumbs_stack),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use livekit_client::{self as livekit, TrackSid};
|
||||
use postage::{sink::Sink, stream::Stream, watch};
|
||||
use project::Project;
|
||||
use settings::Settings as _;
|
||||
use std::{any::Any, future::Future, mem, sync::Arc, time::Duration};
|
||||
use std::{any::Any, future::Future, mem, rc::Rc, sync::Arc, time::Duration};
|
||||
use util::{ResultExt, TryFutureExt, post_inc};
|
||||
|
||||
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
@@ -1594,7 +1594,7 @@ fn spawn_room_connection(
|
||||
|
||||
let muted_by_user = Room::mute_on_join(cx);
|
||||
this.live_kit = Some(LiveKitRoom {
|
||||
room: Arc::new(room),
|
||||
room: Rc::new(room),
|
||||
screen_track: LocalTrack::None,
|
||||
microphone_track: LocalTrack::None,
|
||||
next_publish_id: 0,
|
||||
@@ -1617,7 +1617,7 @@ fn spawn_room_connection(
|
||||
}
|
||||
|
||||
struct LiveKitRoom {
|
||||
room: Arc<livekit::Room>,
|
||||
room: Rc<livekit::Room>,
|
||||
screen_track: LocalTrack,
|
||||
microphone_track: LocalTrack,
|
||||
/// Tracks whether we're currently in a muted state due to auto-mute from deafening or manual mute performed by user.
|
||||
|
||||
@@ -7,6 +7,8 @@ fn main() {
|
||||
|
||||
if cfg!(target_os = "macos") {
|
||||
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.15.7");
|
||||
// Weakly link ScreenCaptureKit to ensure can be used on macOS 10.15+.
|
||||
println!("cargo:rustc-link-arg=-Wl,-weak_framework,ScreenCaptureKit");
|
||||
}
|
||||
|
||||
// Populate git sha environment variable if git is available
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::io::Write;
|
||||
use std::sync::LazyLock;
|
||||
use std::time::Instant;
|
||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use telemetry_events::{AssistantEvent, AssistantPhase, Event, EventRequestBody, EventWrapper};
|
||||
use telemetry_events::{AssistantEventData, AssistantPhase, Event, EventRequestBody, EventWrapper};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use worktree::{UpdatedEntriesSet, WorktreeId};
|
||||
|
||||
@@ -329,7 +329,7 @@ impl Telemetry {
|
||||
drop(state);
|
||||
}
|
||||
|
||||
pub fn report_assistant_event(self: &Arc<Self>, event: AssistantEvent) {
|
||||
pub fn report_assistant_event(self: &Arc<Self>, event: AssistantEventData) {
|
||||
let event_type = match event.phase {
|
||||
AssistantPhase::Response => "Assistant Responded",
|
||||
AssistantPhase::Invoked => "Assistant Invoked",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
alter table models
|
||||
add column max_input_tokens_per_minute bigint not null default 0,
|
||||
add column max_output_tokens_per_minute bigint not null default 0;
|
||||
@@ -127,7 +127,7 @@ async fn update_billing_preferences(
|
||||
|
||||
SnowflakeRow::new(
|
||||
"Spend Limit Updated",
|
||||
user.metrics_id,
|
||||
Some(user.metrics_id),
|
||||
user.admin,
|
||||
None,
|
||||
json!({
|
||||
|
||||
@@ -149,6 +149,37 @@ pub async fn post_crash(
|
||||
"crash report"
|
||||
);
|
||||
|
||||
if let Some(kinesis_client) = app.kinesis_client.clone() {
|
||||
if let Some(stream) = app.config.kinesis_stream.clone() {
|
||||
let properties = json!({
|
||||
"app_version": report.header.app_version,
|
||||
"os_version": report.header.os_version,
|
||||
"os_name": "macOS",
|
||||
"bundle_id": report.header.bundle_id,
|
||||
"incident_id": report.header.incident_id,
|
||||
"installation_id": installation_id,
|
||||
"description": description,
|
||||
"backtrace": summary,
|
||||
});
|
||||
let row = SnowflakeRow::new(
|
||||
"Crash Reported",
|
||||
None,
|
||||
false,
|
||||
Some(installation_id),
|
||||
properties,
|
||||
);
|
||||
let data = serde_json::to_vec(&row)?;
|
||||
kinesis_client
|
||||
.put_record()
|
||||
.stream_name(stream)
|
||||
.partition_key(row.insert_id.unwrap_or_default())
|
||||
.data(data.into())
|
||||
.send()
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
|
||||
let payload = slack::WebhookBody::new(|w| {
|
||||
w.add_section(|s| s.text(slack::Text::markdown(description)))
|
||||
@@ -314,6 +345,8 @@ pub async fn post_panic(
|
||||
.ok();
|
||||
}
|
||||
|
||||
let backtrace = panic.backtrace.join("\n");
|
||||
|
||||
tracing::error!(
|
||||
service = "client",
|
||||
version = %panic.app_version,
|
||||
@@ -322,10 +355,40 @@ pub async fn post_panic(
|
||||
incident_id = %incident_id,
|
||||
installation_id = %panic.installation_id.clone().unwrap_or_default(),
|
||||
description = %panic.payload,
|
||||
backtrace = %panic.backtrace.join("\n"),
|
||||
backtrace = %backtrace,
|
||||
"panic report"
|
||||
);
|
||||
|
||||
if let Some(kinesis_client) = app.kinesis_client.clone() {
|
||||
if let Some(stream) = app.config.kinesis_stream.clone() {
|
||||
let properties = json!({
|
||||
"app_version": panic.app_version,
|
||||
"os_name": panic.os_name,
|
||||
"os_version": panic.os_version,
|
||||
"incident_id": incident_id,
|
||||
"installation_id": panic.installation_id,
|
||||
"description": panic.payload,
|
||||
"backtrace": backtrace,
|
||||
});
|
||||
let row = SnowflakeRow::new(
|
||||
"Panic Reported",
|
||||
None,
|
||||
false,
|
||||
panic.installation_id.clone(),
|
||||
properties,
|
||||
);
|
||||
let data = serde_json::to_vec(&row)?;
|
||||
kinesis_client
|
||||
.put_record()
|
||||
.stream_name(stream)
|
||||
.partition_key(row.insert_id.unwrap_or_default())
|
||||
.data(data.into())
|
||||
.send()
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
let backtrace = if panic.backtrace.len() > 25 {
|
||||
let total = panic.backtrace.len();
|
||||
format!(
|
||||
@@ -711,7 +774,7 @@ pub struct SnowflakeRow {
|
||||
impl SnowflakeRow {
|
||||
pub fn new(
|
||||
event_type: impl Into<String>,
|
||||
metrics_id: Uuid,
|
||||
metrics_id: Option<Uuid>,
|
||||
is_staff: bool,
|
||||
system_id: Option<String>,
|
||||
event_properties: serde_json::Value,
|
||||
@@ -720,7 +783,7 @@ impl SnowflakeRow {
|
||||
time: chrono::Utc::now(),
|
||||
event_type: event_type.into(),
|
||||
device_id: system_id,
|
||||
user_id: Some(metrics_id.to_string()),
|
||||
user_id: metrics_id.map(|id| id.to_string()),
|
||||
insert_id: Some(uuid::Uuid::new_v4().to_string()),
|
||||
event_properties,
|
||||
user_properties: Some(json!({"is_staff": is_staff})),
|
||||
|
||||
@@ -316,10 +316,14 @@ async fn perform_completion(
|
||||
is_staff = claims.is_staff,
|
||||
provider = params.provider.to_string(),
|
||||
model = model,
|
||||
tokens_remaining = rate_limit_info.tokens_remaining,
|
||||
requests_remaining = rate_limit_info.requests_remaining,
|
||||
requests_reset = ?rate_limit_info.requests_reset,
|
||||
tokens_reset = ?rate_limit_info.tokens_reset,
|
||||
tokens_remaining = rate_limit_info.tokens.as_ref().map(|limits| limits.remaining),
|
||||
input_tokens_remaining = rate_limit_info.input_tokens.as_ref().map(|limits| limits.remaining),
|
||||
output_tokens_remaining = rate_limit_info.output_tokens.as_ref().map(|limits| limits.remaining),
|
||||
requests_remaining = rate_limit_info.requests.as_ref().map(|limits| limits.remaining),
|
||||
requests_reset = ?rate_limit_info.requests.as_ref().map(|limits| limits.reset),
|
||||
tokens_reset = ?rate_limit_info.tokens.as_ref().map(|limits| limits.reset),
|
||||
input_tokens_reset = ?rate_limit_info.input_tokens.as_ref().map(|limits| limits.reset),
|
||||
output_tokens_reset = ?rate_limit_info.output_tokens.as_ref().map(|limits| limits.reset),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -499,6 +503,10 @@ async fn check_usage_limit(
|
||||
model.max_requests_per_minute as usize / users_in_recent_minutes;
|
||||
let per_user_max_tokens_per_minute =
|
||||
model.max_tokens_per_minute as usize / users_in_recent_minutes;
|
||||
let per_user_max_input_tokens_per_minute =
|
||||
model.max_input_tokens_per_minute as usize / users_in_recent_minutes;
|
||||
let per_user_max_output_tokens_per_minute =
|
||||
model.max_output_tokens_per_minute as usize / users_in_recent_minutes;
|
||||
let per_user_max_tokens_per_day = model.max_tokens_per_day as usize / users_in_recent_days;
|
||||
|
||||
let usage = state
|
||||
@@ -506,29 +514,55 @@ async fn check_usage_limit(
|
||||
.get_usage(user_id, provider, model_name, Utc::now())
|
||||
.await?;
|
||||
|
||||
let checks = [
|
||||
(
|
||||
usage.requests_this_minute,
|
||||
per_user_max_requests_per_minute,
|
||||
UsageMeasure::RequestsPerMinute,
|
||||
),
|
||||
(
|
||||
usage.tokens_this_minute,
|
||||
per_user_max_tokens_per_minute,
|
||||
UsageMeasure::TokensPerMinute,
|
||||
),
|
||||
(
|
||||
usage.tokens_this_day,
|
||||
per_user_max_tokens_per_day,
|
||||
UsageMeasure::TokensPerDay,
|
||||
),
|
||||
];
|
||||
let checks = match (provider, model_name) {
|
||||
(LanguageModelProvider::Anthropic, "claude-3-7-sonnet") => vec![
|
||||
(
|
||||
usage.requests_this_minute,
|
||||
per_user_max_requests_per_minute,
|
||||
UsageMeasure::RequestsPerMinute,
|
||||
),
|
||||
(
|
||||
usage.input_tokens_this_minute,
|
||||
per_user_max_tokens_per_minute,
|
||||
UsageMeasure::InputTokensPerMinute,
|
||||
),
|
||||
(
|
||||
usage.output_tokens_this_minute,
|
||||
per_user_max_tokens_per_minute,
|
||||
UsageMeasure::OutputTokensPerMinute,
|
||||
),
|
||||
(
|
||||
usage.tokens_this_day,
|
||||
per_user_max_tokens_per_day,
|
||||
UsageMeasure::TokensPerDay,
|
||||
),
|
||||
],
|
||||
_ => vec![
|
||||
(
|
||||
usage.requests_this_minute,
|
||||
per_user_max_requests_per_minute,
|
||||
UsageMeasure::RequestsPerMinute,
|
||||
),
|
||||
(
|
||||
usage.tokens_this_minute,
|
||||
per_user_max_tokens_per_minute,
|
||||
UsageMeasure::TokensPerMinute,
|
||||
),
|
||||
(
|
||||
usage.tokens_this_day,
|
||||
per_user_max_tokens_per_day,
|
||||
UsageMeasure::TokensPerDay,
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
for (used, limit, usage_measure) in checks {
|
||||
if used > limit {
|
||||
let resource = match usage_measure {
|
||||
UsageMeasure::RequestsPerMinute => "requests_per_minute",
|
||||
UsageMeasure::TokensPerMinute => "tokens_per_minute",
|
||||
UsageMeasure::InputTokensPerMinute => "input_tokens_per_minute",
|
||||
UsageMeasure::OutputTokensPerMinute => "output_tokens_per_minute",
|
||||
UsageMeasure::TokensPerDay => "tokens_per_day",
|
||||
};
|
||||
|
||||
@@ -540,19 +574,24 @@ async fn check_usage_limit(
|
||||
is_staff = claims.is_staff,
|
||||
provider = provider.to_string(),
|
||||
model = model.name,
|
||||
usage_measure = resource,
|
||||
requests_this_minute = usage.requests_this_minute,
|
||||
tokens_this_minute = usage.tokens_this_minute,
|
||||
input_tokens_this_minute = usage.input_tokens_this_minute,
|
||||
output_tokens_this_minute = usage.output_tokens_this_minute,
|
||||
tokens_this_day = usage.tokens_this_day,
|
||||
users_in_recent_minutes = users_in_recent_minutes,
|
||||
users_in_recent_days = users_in_recent_days,
|
||||
max_requests_per_minute = per_user_max_requests_per_minute,
|
||||
max_tokens_per_minute = per_user_max_tokens_per_minute,
|
||||
max_input_tokens_per_minute = per_user_max_input_tokens_per_minute,
|
||||
max_output_tokens_per_minute = per_user_max_output_tokens_per_minute,
|
||||
max_tokens_per_day = per_user_max_tokens_per_day,
|
||||
);
|
||||
|
||||
SnowflakeRow::new(
|
||||
"Language Model Rate Limited",
|
||||
claims.metrics_id,
|
||||
Some(claims.metrics_id),
|
||||
claims.is_staff,
|
||||
claims.system_id.clone(),
|
||||
json!({
|
||||
@@ -561,6 +600,8 @@ async fn check_usage_limit(
|
||||
"users_in_recent_days": users_in_recent_days,
|
||||
"max_requests_per_minute": per_user_max_requests_per_minute,
|
||||
"max_tokens_per_minute": per_user_max_tokens_per_minute,
|
||||
"max_input_tokens_per_minute": per_user_max_input_tokens_per_minute,
|
||||
"max_output_tokens_per_minute": per_user_max_output_tokens_per_minute,
|
||||
"max_tokens_per_day": per_user_max_tokens_per_day,
|
||||
"plan": match claims.plan {
|
||||
Plan::Free => "free".to_string(),
|
||||
@@ -656,8 +697,12 @@ impl<S> Drop for TokenCountingStream<S> {
|
||||
login = claims.github_user_login,
|
||||
authn.jti = claims.jti,
|
||||
is_staff = claims.is_staff,
|
||||
provider = provider.to_string(),
|
||||
model = model,
|
||||
requests_this_minute = usage.requests_this_minute,
|
||||
tokens_this_minute = usage.tokens_this_minute,
|
||||
input_tokens_this_minute = usage.input_tokens_this_minute,
|
||||
output_tokens_this_minute = usage.output_tokens_this_minute,
|
||||
);
|
||||
|
||||
let properties = json!({
|
||||
@@ -674,7 +719,7 @@ impl<S> Drop for TokenCountingStream<S> {
|
||||
});
|
||||
SnowflakeRow::new(
|
||||
"Language Model Used",
|
||||
claims.metrics_id,
|
||||
Some(claims.metrics_id),
|
||||
claims.is_staff,
|
||||
claims.system_id.clone(),
|
||||
properties,
|
||||
@@ -726,6 +771,8 @@ pub fn log_usage_periodically(state: Arc<LlmState>) {
|
||||
model = usage.model,
|
||||
requests_this_minute = usage.requests_this_minute,
|
||||
tokens_this_minute = usage.tokens_this_minute,
|
||||
input_tokens_this_minute = usage.input_tokens_this_minute,
|
||||
output_tokens_this_minute = usage.output_tokens_this_minute,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ impl TokenUsage {
|
||||
pub struct Usage {
|
||||
pub requests_this_minute: usize,
|
||||
pub tokens_this_minute: usize,
|
||||
pub input_tokens_this_minute: usize,
|
||||
pub output_tokens_this_minute: usize,
|
||||
pub tokens_this_day: usize,
|
||||
pub tokens_this_month: TokenUsage,
|
||||
pub spending_this_month: Cents,
|
||||
@@ -39,6 +41,8 @@ pub struct ApplicationWideUsage {
|
||||
pub model: String,
|
||||
pub requests_this_minute: usize,
|
||||
pub tokens_this_minute: usize,
|
||||
pub input_tokens_this_minute: usize,
|
||||
pub output_tokens_this_minute: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
@@ -94,6 +98,10 @@ impl LlmDatabase {
|
||||
let past_minute = now - Duration::minutes(1);
|
||||
let requests_per_minute = self.usage_measure_ids[&UsageMeasure::RequestsPerMinute];
|
||||
let tokens_per_minute = self.usage_measure_ids[&UsageMeasure::TokensPerMinute];
|
||||
let input_tokens_per_minute =
|
||||
self.usage_measure_ids[&UsageMeasure::InputTokensPerMinute];
|
||||
let output_tokens_per_minute =
|
||||
self.usage_measure_ids[&UsageMeasure::OutputTokensPerMinute];
|
||||
|
||||
let mut results = Vec::new();
|
||||
for ((provider, model_name), model) in self.models.iter() {
|
||||
@@ -114,6 +122,8 @@ impl LlmDatabase {
|
||||
|
||||
let mut requests_this_minute = 0;
|
||||
let mut tokens_this_minute = 0;
|
||||
let mut input_tokens_this_minute = 0;
|
||||
let mut output_tokens_this_minute = 0;
|
||||
while let Some(usage) = usages.next().await {
|
||||
let usage = usage?;
|
||||
if usage.measure_id == requests_per_minute {
|
||||
@@ -136,6 +146,26 @@ impl LlmDatabase {
|
||||
.iter()
|
||||
.copied()
|
||||
.sum::<i64>() as usize;
|
||||
} else if usage.measure_id == input_tokens_per_minute {
|
||||
input_tokens_this_minute += Self::get_live_buckets(
|
||||
&usage,
|
||||
now.naive_utc(),
|
||||
UsageMeasure::InputTokensPerMinute,
|
||||
)
|
||||
.0
|
||||
.iter()
|
||||
.copied()
|
||||
.sum::<i64>() as usize;
|
||||
} else if usage.measure_id == output_tokens_per_minute {
|
||||
output_tokens_this_minute += Self::get_live_buckets(
|
||||
&usage,
|
||||
now.naive_utc(),
|
||||
UsageMeasure::OutputTokensPerMinute,
|
||||
)
|
||||
.0
|
||||
.iter()
|
||||
.copied()
|
||||
.sum::<i64>() as usize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +174,8 @@ impl LlmDatabase {
|
||||
model: model_name.clone(),
|
||||
requests_this_minute,
|
||||
tokens_this_minute,
|
||||
input_tokens_this_minute,
|
||||
output_tokens_this_minute,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -239,6 +271,10 @@ impl LlmDatabase {
|
||||
self.get_usage_for_measure(&usages, now, UsageMeasure::RequestsPerMinute)?;
|
||||
let tokens_this_minute =
|
||||
self.get_usage_for_measure(&usages, now, UsageMeasure::TokensPerMinute)?;
|
||||
let input_tokens_this_minute =
|
||||
self.get_usage_for_measure(&usages, now, UsageMeasure::InputTokensPerMinute)?;
|
||||
let output_tokens_this_minute =
|
||||
self.get_usage_for_measure(&usages, now, UsageMeasure::OutputTokensPerMinute)?;
|
||||
let tokens_this_day =
|
||||
self.get_usage_for_measure(&usages, now, UsageMeasure::TokensPerDay)?;
|
||||
let spending_this_month = if let Some(monthly_usage) = &monthly_usage {
|
||||
@@ -267,6 +303,8 @@ impl LlmDatabase {
|
||||
Ok(Usage {
|
||||
requests_this_minute,
|
||||
tokens_this_minute,
|
||||
input_tokens_this_minute,
|
||||
output_tokens_this_minute,
|
||||
tokens_this_day,
|
||||
tokens_this_month: TokenUsage {
|
||||
input: monthly_usage
|
||||
@@ -337,6 +375,31 @@ impl LlmDatabase {
|
||||
&tx,
|
||||
)
|
||||
.await?;
|
||||
let input_tokens_this_minute = self
|
||||
.update_usage_for_measure(
|
||||
user_id,
|
||||
is_staff,
|
||||
model.id,
|
||||
&usages,
|
||||
UsageMeasure::InputTokensPerMinute,
|
||||
now,
|
||||
// Cache read input tokens are not counted for the purposes of rate limits (but they are still billed).
|
||||
tokens.input + tokens.input_cache_creation,
|
||||
&tx,
|
||||
)
|
||||
.await?;
|
||||
let output_tokens_this_minute = self
|
||||
.update_usage_for_measure(
|
||||
user_id,
|
||||
is_staff,
|
||||
model.id,
|
||||
&usages,
|
||||
UsageMeasure::OutputTokensPerMinute,
|
||||
now,
|
||||
tokens.output,
|
||||
&tx,
|
||||
)
|
||||
.await?;
|
||||
let tokens_this_day = self
|
||||
.update_usage_for_measure(
|
||||
user_id,
|
||||
@@ -485,6 +548,8 @@ impl LlmDatabase {
|
||||
Ok(Usage {
|
||||
requests_this_minute,
|
||||
tokens_this_minute,
|
||||
input_tokens_this_minute,
|
||||
output_tokens_this_minute,
|
||||
tokens_this_day,
|
||||
tokens_this_month: TokenUsage {
|
||||
input: monthly_usage.input_tokens as usize,
|
||||
@@ -684,7 +749,9 @@ impl UsageMeasure {
|
||||
fn bucket_count(&self) -> usize {
|
||||
match self {
|
||||
UsageMeasure::RequestsPerMinute => MINUTE_BUCKET_COUNT,
|
||||
UsageMeasure::TokensPerMinute => MINUTE_BUCKET_COUNT,
|
||||
UsageMeasure::TokensPerMinute
|
||||
| UsageMeasure::InputTokensPerMinute
|
||||
| UsageMeasure::OutputTokensPerMinute => MINUTE_BUCKET_COUNT,
|
||||
UsageMeasure::TokensPerDay => DAY_BUCKET_COUNT,
|
||||
}
|
||||
}
|
||||
@@ -692,7 +759,9 @@ impl UsageMeasure {
|
||||
fn total_duration(&self) -> Duration {
|
||||
match self {
|
||||
UsageMeasure::RequestsPerMinute => Duration::minutes(1),
|
||||
UsageMeasure::TokensPerMinute => Duration::minutes(1),
|
||||
UsageMeasure::TokensPerMinute
|
||||
| UsageMeasure::InputTokensPerMinute
|
||||
| UsageMeasure::OutputTokensPerMinute => Duration::minutes(1),
|
||||
UsageMeasure::TokensPerDay => Duration::hours(24),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ pub struct Model {
|
||||
pub name: String,
|
||||
pub max_requests_per_minute: i64,
|
||||
pub max_tokens_per_minute: i64,
|
||||
pub max_input_tokens_per_minute: i64,
|
||||
pub max_output_tokens_per_minute: i64,
|
||||
pub max_tokens_per_day: i64,
|
||||
pub price_per_million_input_tokens: i32,
|
||||
pub price_per_million_cache_creation_input_tokens: i32,
|
||||
|
||||
@@ -8,6 +8,8 @@ use sea_orm::entity::prelude::*;
|
||||
pub enum UsageMeasure {
|
||||
RequestsPerMinute,
|
||||
TokensPerMinute,
|
||||
InputTokensPerMinute,
|
||||
OutputTokensPerMinute,
|
||||
TokensPerDay,
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,8 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
|
||||
Usage {
|
||||
requests_this_minute: 2,
|
||||
tokens_this_minute: 3000,
|
||||
input_tokens_this_minute: 3000,
|
||||
output_tokens_this_minute: 0,
|
||||
tokens_this_day: 3000,
|
||||
tokens_this_month: TokenUsage {
|
||||
input: 3000,
|
||||
@@ -102,6 +104,8 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
|
||||
Usage {
|
||||
requests_this_minute: 1,
|
||||
tokens_this_minute: 2000,
|
||||
input_tokens_this_minute: 2000,
|
||||
output_tokens_this_minute: 0,
|
||||
tokens_this_day: 3000,
|
||||
tokens_this_month: TokenUsage {
|
||||
input: 3000,
|
||||
@@ -140,6 +144,8 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
|
||||
Usage {
|
||||
requests_this_minute: 2,
|
||||
tokens_this_minute: 5000,
|
||||
input_tokens_this_minute: 5000,
|
||||
output_tokens_this_minute: 0,
|
||||
tokens_this_day: 6000,
|
||||
tokens_this_month: TokenUsage {
|
||||
input: 6000,
|
||||
@@ -160,6 +166,8 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
|
||||
Usage {
|
||||
requests_this_minute: 0,
|
||||
tokens_this_minute: 0,
|
||||
input_tokens_this_minute: 0,
|
||||
output_tokens_this_minute: 0,
|
||||
tokens_this_day: 5000,
|
||||
tokens_this_month: TokenUsage {
|
||||
input: 6000,
|
||||
@@ -197,6 +205,8 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
|
||||
Usage {
|
||||
requests_this_minute: 1,
|
||||
tokens_this_minute: 4000,
|
||||
input_tokens_this_minute: 4000,
|
||||
output_tokens_this_minute: 0,
|
||||
tokens_this_day: 9000,
|
||||
tokens_this_month: TokenUsage {
|
||||
input: 10000,
|
||||
@@ -240,6 +250,8 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
|
||||
Usage {
|
||||
requests_this_minute: 1,
|
||||
tokens_this_minute: 1500,
|
||||
input_tokens_this_minute: 1500,
|
||||
output_tokens_this_minute: 0,
|
||||
tokens_this_day: 1500,
|
||||
tokens_this_month: TokenUsage {
|
||||
input: 1000,
|
||||
@@ -278,6 +290,8 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
|
||||
Usage {
|
||||
requests_this_minute: 2,
|
||||
tokens_this_minute: 2800,
|
||||
input_tokens_this_minute: 2500,
|
||||
output_tokens_this_minute: 0,
|
||||
tokens_this_day: 2800,
|
||||
tokens_this_month: TokenUsage {
|
||||
input: 2000,
|
||||
|
||||
@@ -356,6 +356,7 @@ impl Server {
|
||||
.add_request_handler(forward_mutating_project_request::<proto::BlameBuffer>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::MultiLspQuery>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::RestartLanguageServers>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::StopLanguageServers>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::LinkedEditingRange>)
|
||||
.add_message_handler(create_buffer_for_peer)
|
||||
.add_request_handler(update_buffer)
|
||||
@@ -983,7 +984,7 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn snapshot<'a>(self: &'a Arc<Self>) -> ServerSnapshot<'a> {
|
||||
pub async fn snapshot(self: &Arc<Self>) -> ServerSnapshot {
|
||||
ServerSnapshot {
|
||||
connection_pool: ConnectionPoolGuard {
|
||||
guard: self.connection_pool.lock(),
|
||||
|
||||
@@ -674,7 +674,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
client_a
|
||||
.fs()
|
||||
.insert_tree(
|
||||
"/a",
|
||||
path!("/a"),
|
||||
json!({
|
||||
"1.txt": "one",
|
||||
"2.txt": "two",
|
||||
@@ -683,7 +683,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
|
||||
.await
|
||||
|
||||
@@ -6866,10 +6866,14 @@ async fn test_remote_git_branches(
|
||||
|
||||
assert_eq!(branches_b, branches_set);
|
||||
|
||||
cx_b.update(|cx| repo_b.read(cx).change_branch(new_branch.to_string()))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx_b.update(|cx| {
|
||||
repo_b.update(cx, |repository, _cx| {
|
||||
repository.change_branch(new_branch.to_string())
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -6892,18 +6896,18 @@ async fn test_remote_git_branches(
|
||||
|
||||
// Also try creating a new branch
|
||||
cx_b.update(|cx| {
|
||||
repo_b
|
||||
.read(cx)
|
||||
.create_branch("totally-new-branch".to_string())
|
||||
repo_b.update(cx, |repository, _cx| {
|
||||
repository.create_branch("totally-new-branch".to_string())
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
cx_b.update(|cx| {
|
||||
repo_b
|
||||
.read(cx)
|
||||
.change_branch("totally-new-branch".to_string())
|
||||
repo_b.update(cx, |repository, _cx| {
|
||||
repository.change_branch("totally-new-branch".to_string())
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
||||
@@ -283,7 +283,7 @@ async fn test_ssh_collaboration_git_branches(
|
||||
let repo_b = cx_b.update(|cx| project_b.read(cx).active_repository(cx).unwrap());
|
||||
|
||||
let branches_b = cx_b
|
||||
.update(|cx| repo_b.read(cx).branches())
|
||||
.update(|cx| repo_b.update(cx, |repo_b, _cx| repo_b.branches()))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
@@ -297,10 +297,14 @@ async fn test_ssh_collaboration_git_branches(
|
||||
|
||||
assert_eq!(&branches_b, &branches_set);
|
||||
|
||||
cx_b.update(|cx| repo_b.read(cx).change_branch(new_branch.to_string()))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx_b.update(|cx| {
|
||||
repo_b.update(cx, |repo_b, _cx| {
|
||||
repo_b.change_branch(new_branch.to_string())
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -325,18 +329,18 @@ async fn test_ssh_collaboration_git_branches(
|
||||
|
||||
// Also try creating a new branch
|
||||
cx_b.update(|cx| {
|
||||
repo_b
|
||||
.read(cx)
|
||||
.create_branch("totally-new-branch".to_string())
|
||||
repo_b.update(cx, |repo_b, _cx| {
|
||||
repo_b.create_branch("totally-new-branch".to_string())
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
cx_b.update(|cx| {
|
||||
repo_b
|
||||
.read(cx)
|
||||
.change_branch("totally-new-branch".to_string())
|
||||
repo_b.update(cx, |repo_b, _cx| {
|
||||
repo_b.change_branch("totally-new-branch".to_string())
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
||||
@@ -315,6 +315,7 @@ impl MessageEditor {
|
||||
icon_path: None,
|
||||
confirm: None,
|
||||
documentation: None,
|
||||
insert_text_mode: None,
|
||||
source: CompletionSource::Custom,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -35,7 +35,7 @@ use ui::{
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt, maybe};
|
||||
use workspace::{
|
||||
OpenChannelNotes, Workspace,
|
||||
Deafen, LeaveCall, Mute, OpenChannelNotes, ScreenShare, ShareProject, Workspace,
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
notifications::{DetachAndPromptErr, NotifyResultExt, NotifyTaskExt},
|
||||
};
|
||||
@@ -80,6 +80,57 @@ pub fn init(cx: &mut App) {
|
||||
});
|
||||
}
|
||||
});
|
||||
// TODO: make it possible to bind this one to a held key for push to talk?
|
||||
// how to make "toggle_on_modifiers_press" contextual?
|
||||
workspace.register_action(|_, _: &Mute, window, cx| {
|
||||
let room = ActiveCall::global(cx).read(cx).room().cloned();
|
||||
if let Some(room) = room {
|
||||
window.defer(cx, move |_window, cx| {
|
||||
room.update(cx, |room, cx| room.toggle_mute(cx))
|
||||
});
|
||||
}
|
||||
});
|
||||
workspace.register_action(|_, _: &Deafen, window, cx| {
|
||||
let room = ActiveCall::global(cx).read(cx).room().cloned();
|
||||
if let Some(room) = room {
|
||||
window.defer(cx, move |_window, cx| {
|
||||
room.update(cx, |room, cx| room.toggle_deafen(cx))
|
||||
});
|
||||
}
|
||||
});
|
||||
workspace.register_action(|_, _: &LeaveCall, window, cx| {
|
||||
CollabPanel::leave_call(window, cx);
|
||||
});
|
||||
workspace.register_action(|workspace, _: &ShareProject, window, cx| {
|
||||
let project = workspace.project().clone();
|
||||
println!("{project:?}");
|
||||
window.defer(cx, move |_window, cx| {
|
||||
ActiveCall::global(cx).update(cx, move |call, cx| {
|
||||
if let Some(room) = call.room() {
|
||||
println!("{room:?}");
|
||||
if room.read(cx).is_sharing_project() {
|
||||
call.unshare_project(project, cx).ok();
|
||||
} else {
|
||||
call.share_project(project, cx).detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
workspace.register_action(|_, _: &ScreenShare, window, cx| {
|
||||
let room = ActiveCall::global(cx).read(cx).room().cloned();
|
||||
if let Some(room) = room {
|
||||
window.defer(cx, move |_window, cx| {
|
||||
room.update(cx, |room, cx| {
|
||||
if room.is_screen_sharing() {
|
||||
room.unshare_screen(cx).ok();
|
||||
} else {
|
||||
room.share_screen(cx).detach_and_log_err(cx);
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ mod tests {
|
||||
use language::{
|
||||
Point,
|
||||
language_settings::{
|
||||
AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
|
||||
AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, LspInsertMode,
|
||||
WordsCompletionMode,
|
||||
},
|
||||
};
|
||||
@@ -294,6 +294,7 @@ mod tests {
|
||||
words: WordsCompletionMode::Disabled,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -525,6 +526,7 @@ mod tests {
|
||||
words: WordsCompletionMode::Disabled,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::ffi::OsStr;
|
||||
use anyhow::{Result, bail};
|
||||
use async_trait::async_trait;
|
||||
use gpui::AsyncApp;
|
||||
use task::{DebugAdapterConfig, DebugTaskDefinition};
|
||||
use task::{DebugAdapterConfig, DebugRequestType, DebugTaskDefinition};
|
||||
|
||||
use crate::*;
|
||||
|
||||
@@ -74,13 +74,37 @@ impl DebugAdapter for GdbDebugAdapter {
|
||||
}
|
||||
|
||||
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
|
||||
let mut args = json!({
|
||||
"request": match config.request {
|
||||
DebugRequestType::Launch(_) => "launch",
|
||||
DebugRequestType::Attach(_) => "attach",
|
||||
},
|
||||
});
|
||||
|
||||
let map = args.as_object_mut().unwrap();
|
||||
match &config.request {
|
||||
dap::DebugRequestType::Attach(attach_config) => {
|
||||
json!({"pid": attach_config.process_id})
|
||||
DebugRequestType::Attach(attach) => {
|
||||
map.insert("pid".into(), attach.process_id.into());
|
||||
}
|
||||
dap::DebugRequestType::Launch(launch_config) => {
|
||||
json!({"program": launch_config.program, "cwd": launch_config.cwd})
|
||||
|
||||
DebugRequestType::Launch(launch) => {
|
||||
map.insert("program".into(), launch.program.clone().into());
|
||||
|
||||
if !launch.args.is_empty() {
|
||||
map.insert("args".into(), launch.args.clone().into());
|
||||
}
|
||||
|
||||
if let Some(stop_on_entry) = config.stop_on_entry {
|
||||
map.insert(
|
||||
"stopAtBeginningOfMainSubprogram".into(),
|
||||
stop_on_entry.into(),
|
||||
);
|
||||
}
|
||||
if let Some(cwd) = launch.cwd.as_ref() {
|
||||
map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,16 +83,25 @@ impl DebugAdapter for GoDebugAdapter {
|
||||
}
|
||||
|
||||
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
|
||||
match &config.request {
|
||||
let mut args = match &config.request {
|
||||
dap::DebugRequestType::Attach(attach_config) => {
|
||||
json!({
|
||||
"processId": attach_config.process_id
|
||||
"processId": attach_config.process_id,
|
||||
})
|
||||
}
|
||||
dap::DebugRequestType::Launch(launch_config) => json!({
|
||||
"program": launch_config.program,
|
||||
"cwd": launch_config.cwd,
|
||||
"args": launch_config.args
|
||||
}),
|
||||
};
|
||||
|
||||
let map = args.as_object_mut().unwrap();
|
||||
|
||||
if let Some(stop_on_entry) = config.stop_on_entry {
|
||||
map.insert("stopOnEntry".into(), stop_on_entry.into());
|
||||
}
|
||||
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,14 +134,17 @@ impl DebugAdapter for JsDebugAdapter {
|
||||
}
|
||||
DebugRequestType::Launch(launch) => {
|
||||
map.insert("program".into(), launch.program.clone().into());
|
||||
map.insert(
|
||||
"cwd".into(),
|
||||
launch
|
||||
.cwd
|
||||
.as_ref()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.into(),
|
||||
);
|
||||
|
||||
if !launch.args.is_empty() {
|
||||
map.insert("args".into(), launch.args.clone().into());
|
||||
}
|
||||
|
||||
if let Some(stop_on_entry) = config.stop_on_entry {
|
||||
map.insert("stopOnEntry".into(), stop_on_entry.into());
|
||||
}
|
||||
if let Some(cwd) = launch.cwd.as_ref() {
|
||||
map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
args
|
||||
|
||||
@@ -81,14 +81,17 @@ impl DebugAdapter for LldbDebugAdapter {
|
||||
}
|
||||
DebugRequestType::Launch(launch) => {
|
||||
map.insert("program".into(), launch.program.clone().into());
|
||||
map.insert(
|
||||
"cwd".into(),
|
||||
launch
|
||||
.cwd
|
||||
.as_ref()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.into(),
|
||||
);
|
||||
|
||||
if !launch.args.is_empty() {
|
||||
map.insert("args".into(), launch.args.clone().into());
|
||||
}
|
||||
|
||||
if let Some(stop_on_entry) = config.stop_on_entry {
|
||||
map.insert("stopOnEntry".into(), stop_on_entry.into());
|
||||
}
|
||||
if let Some(cwd) = launch.cwd.as_ref() {
|
||||
map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
args
|
||||
|
||||
@@ -118,6 +118,8 @@ impl DebugAdapter for PhpDebugAdapter {
|
||||
json!({
|
||||
"program": launch_config.program,
|
||||
"cwd": launch_config.cwd,
|
||||
"args": launch_config.args,
|
||||
"stopOnEntry": config.stop_on_entry.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,22 +126,31 @@ impl DebugAdapter for PythonDebugAdapter {
|
||||
}
|
||||
|
||||
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
|
||||
let mut args = json!({
|
||||
"request": match config.request {
|
||||
DebugRequestType::Launch(_) => "launch",
|
||||
DebugRequestType::Attach(_) => "attach",
|
||||
},
|
||||
"subProcess": true,
|
||||
"redirectOutput": true,
|
||||
});
|
||||
let map = args.as_object_mut().unwrap();
|
||||
match &config.request {
|
||||
DebugRequestType::Launch(launch_config) => {
|
||||
json!({
|
||||
"program": launch_config.program,
|
||||
"subProcess": true,
|
||||
"cwd": launch_config.cwd,
|
||||
"redirectOutput": true,
|
||||
})
|
||||
DebugRequestType::Attach(attach) => {
|
||||
map.insert("processId".into(), attach.process_id.into());
|
||||
}
|
||||
dap::DebugRequestType::Attach(attach_config) => {
|
||||
json!({
|
||||
"subProcess": true,
|
||||
"redirectOutput": true,
|
||||
"processId": attach_config.process_id
|
||||
})
|
||||
DebugRequestType::Launch(launch) => {
|
||||
map.insert("program".into(), launch.program.clone().into());
|
||||
map.insert("args".into(), launch.args.clone().into());
|
||||
|
||||
if let Some(stop_on_entry) = config.stop_on_entry {
|
||||
map.insert("stopOnEntry".into(), stop_on_entry.into());
|
||||
}
|
||||
if let Some(cwd) = launch.cwd.as_ref() {
|
||||
map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ impl AttachModal {
|
||||
pub fn new(
|
||||
project: Entity<project::Project>,
|
||||
debug_config: task::DebugTaskDefinition,
|
||||
modal: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
@@ -74,13 +75,14 @@ impl AttachModal {
|
||||
})
|
||||
.collect();
|
||||
processes.sort_by_key(|k| k.name.clone());
|
||||
Self::with_processes(project, debug_config, processes, window, cx)
|
||||
Self::with_processes(project, debug_config, processes, modal, window, cx)
|
||||
}
|
||||
|
||||
pub(super) fn with_processes(
|
||||
project: Entity<project::Project>,
|
||||
debug_config: task::DebugTaskDefinition,
|
||||
processes: Vec<Candidate>,
|
||||
modal: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
@@ -103,6 +105,7 @@ impl AttachModal {
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.modal(modal)
|
||||
});
|
||||
Self {
|
||||
_subscription: cx.subscribe(&picker, |_, _, _, cx| {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::session::DebugSession;
|
||||
use crate::{
|
||||
ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, Pause, Restart, StepBack,
|
||||
StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
|
||||
};
|
||||
use crate::{new_session_modal::NewSessionModal, session::DebugSession};
|
||||
use anyhow::{Result, anyhow};
|
||||
use collections::HashMap;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
@@ -13,7 +17,10 @@ use gpui::{
|
||||
};
|
||||
use project::{
|
||||
Project,
|
||||
debugger::dap_store::{self, DapStore},
|
||||
debugger::{
|
||||
dap_store::{self, DapStore},
|
||||
session::ThreadStatus,
|
||||
},
|
||||
terminals::TerminalKind,
|
||||
};
|
||||
use rpc::proto::{self};
|
||||
@@ -21,13 +28,10 @@ use settings::Settings;
|
||||
use std::{any::TypeId, path::PathBuf};
|
||||
use task::DebugTaskDefinition;
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
|
||||
use workspace::{
|
||||
ClearAllBreakpoints, Continue, Disconnect, Pane, Pause, Restart, StepBack, StepInto, StepOut,
|
||||
StepOver, Stop, ToggleIgnoreBreakpoints, Workspace,
|
||||
Workspace,
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
pane,
|
||||
};
|
||||
|
||||
pub enum DebugPanelEvent {
|
||||
@@ -50,11 +54,14 @@ pub enum DebugPanelEvent {
|
||||
actions!(debug_panel, [ToggleFocus]);
|
||||
pub struct DebugPanel {
|
||||
size: Pixels,
|
||||
pane: Entity<Pane>,
|
||||
sessions: Vec<Entity<DebugSession>>,
|
||||
active_session: Option<Entity<DebugSession>>,
|
||||
/// This represents the last debug definition that was created in the new session modal
|
||||
pub(crate) past_debug_definition: Option<DebugTaskDefinition>,
|
||||
project: WeakEntity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
pub(crate) last_inert_config: Option<DebugTaskDefinition>,
|
||||
}
|
||||
|
||||
impl DebugPanel {
|
||||
@@ -66,100 +73,17 @@ impl DebugPanel {
|
||||
cx.new(|cx| {
|
||||
let project = workspace.project().clone();
|
||||
let dap_store = project.read(cx).dap_store();
|
||||
let weak_workspace = workspace.weak_handle();
|
||||
let debug_panel = cx.weak_entity();
|
||||
let pane = cx.new(|cx| {
|
||||
let mut pane = Pane::new(
|
||||
workspace.weak_handle(),
|
||||
project.clone(),
|
||||
Default::default(),
|
||||
None,
|
||||
gpui::NoAction.boxed_clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
pane.set_can_split(None);
|
||||
pane.set_can_navigate(true, cx);
|
||||
pane.display_nav_history_buttons(None);
|
||||
pane.set_should_display_tab_bar(|_window, _cx| true);
|
||||
pane.set_close_pane_if_empty(true, cx);
|
||||
pane.set_render_tab_bar_buttons(cx, {
|
||||
let project = project.clone();
|
||||
let weak_workspace = weak_workspace.clone();
|
||||
let debug_panel = debug_panel.clone();
|
||||
move |_, _, cx| {
|
||||
let project = project.clone();
|
||||
let weak_workspace = weak_workspace.clone();
|
||||
(
|
||||
None,
|
||||
Some(
|
||||
h_flex()
|
||||
.child(
|
||||
IconButton::new("new-debug-session", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let debug_panel = debug_panel.clone();
|
||||
|
||||
cx.listener(move |pane, _, window, cx| {
|
||||
let config = debug_panel
|
||||
.read_with(cx, |this: &DebugPanel, _| {
|
||||
this.last_inert_config.clone()
|
||||
})
|
||||
.log_err()
|
||||
.flatten();
|
||||
|
||||
pane.add_item(
|
||||
Box::new(DebugSession::inert(
|
||||
project.clone(),
|
||||
weak_workspace.clone(),
|
||||
debug_panel.clone(),
|
||||
config,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
)
|
||||
}
|
||||
});
|
||||
pane.add_item(
|
||||
Box::new(DebugSession::inert(
|
||||
project.clone(),
|
||||
weak_workspace.clone(),
|
||||
debug_panel.clone(),
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
pane
|
||||
});
|
||||
|
||||
let _subscriptions = vec![
|
||||
cx.observe(&pane, |_, _, cx| cx.notify()),
|
||||
cx.subscribe_in(&pane, window, Self::handle_pane_event),
|
||||
cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event),
|
||||
];
|
||||
let _subscriptions =
|
||||
vec![cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event)];
|
||||
|
||||
let debug_panel = Self {
|
||||
pane,
|
||||
size: px(300.),
|
||||
sessions: vec![],
|
||||
active_session: None,
|
||||
_subscriptions,
|
||||
last_inert_config: None,
|
||||
past_debug_definition: None,
|
||||
focus_handle: cx.focus_handle(),
|
||||
project: project.downgrade(),
|
||||
workspace: workspace.weak_handle(),
|
||||
};
|
||||
@@ -188,7 +112,7 @@ impl DebugPanel {
|
||||
cx.observe(&debug_panel, |_, debug_panel, cx| {
|
||||
let (has_active_session, supports_restart, support_step_back) = debug_panel
|
||||
.update(cx, |this, cx| {
|
||||
this.active_session(cx)
|
||||
this.active_session()
|
||||
.map(|item| {
|
||||
let running = item.read(cx).mode().as_running().cloned();
|
||||
|
||||
@@ -250,11 +174,8 @@ impl DebugPanel {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn active_session(&self, cx: &App) -> Option<Entity<DebugSession>> {
|
||||
self.pane
|
||||
.read(cx)
|
||||
.active_item()
|
||||
.and_then(|panel| panel.downcast::<DebugSession>())
|
||||
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
|
||||
self.active_session.clone()
|
||||
}
|
||||
|
||||
pub fn debug_panel_items_by_client(
|
||||
@@ -262,11 +183,9 @@ impl DebugPanel {
|
||||
client_id: &SessionId,
|
||||
cx: &Context<Self>,
|
||||
) -> Vec<Entity<DebugSession>> {
|
||||
self.pane
|
||||
.read(cx)
|
||||
.items()
|
||||
.filter_map(|item| item.downcast::<DebugSession>())
|
||||
.filter(|item| item.read(cx).session_id(cx) == Some(*client_id))
|
||||
self.sessions
|
||||
.iter()
|
||||
.filter(|item| item.read(cx).session_id(cx) == *client_id)
|
||||
.map(|item| item.clone())
|
||||
.collect()
|
||||
}
|
||||
@@ -276,15 +195,14 @@ impl DebugPanel {
|
||||
client_id: SessionId,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Entity<DebugSession>> {
|
||||
self.pane
|
||||
.read(cx)
|
||||
.items()
|
||||
.filter_map(|item| item.downcast::<DebugSession>())
|
||||
self.sessions
|
||||
.iter()
|
||||
.find(|item| {
|
||||
let item = item.read(cx);
|
||||
|
||||
item.session_id(cx) == Some(client_id)
|
||||
item.session_id(cx) == client_id
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn handle_dap_store_event(
|
||||
@@ -295,7 +213,7 @@ impl DebugPanel {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
dap_store::DapStoreEvent::DebugClientStarted(session_id) => {
|
||||
dap_store::DapStoreEvent::DebugSessionInitialized(session_id) => {
|
||||
let Some(session) = dap_store.read(cx).session_by_id(session_id) else {
|
||||
return log::error!(
|
||||
"Couldn't get session with id: {session_id:?} from DebugClientStarted event"
|
||||
@@ -306,10 +224,11 @@ impl DebugPanel {
|
||||
return log::error!("Debug Panel out lived it's weak reference to Project");
|
||||
};
|
||||
|
||||
if self.pane.read_with(cx, |pane, cx| {
|
||||
pane.items_of_type::<DebugSession>()
|
||||
.any(|item| item.read(cx).session_id(cx) == Some(*session_id))
|
||||
}) {
|
||||
if self
|
||||
.sessions
|
||||
.iter()
|
||||
.any(|item| item.read(cx).session_id(cx) == *session_id)
|
||||
{
|
||||
// We already have an item for this session.
|
||||
return;
|
||||
}
|
||||
@@ -322,11 +241,8 @@ impl DebugPanel {
|
||||
cx,
|
||||
);
|
||||
|
||||
self.pane.update(cx, |pane, cx| {
|
||||
pane.add_item(Box::new(session_item), true, true, None, window, cx);
|
||||
window.focus(&pane.focus_handle(cx));
|
||||
cx.notify();
|
||||
});
|
||||
self.sessions.push(session_item.clone());
|
||||
self.activate_session(session_item, window, cx);
|
||||
}
|
||||
dap_store::DapStoreEvent::RunInTerminal {
|
||||
title,
|
||||
@@ -420,55 +336,283 @@ impl DebugPanel {
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_pane_event(
|
||||
fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
|
||||
let active_session = self.active_session.clone();
|
||||
|
||||
Some(
|
||||
h_flex()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.p_1()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.child(
|
||||
h_flex().gap_2().w_full().when_some(
|
||||
active_session
|
||||
.as_ref()
|
||||
.and_then(|session| session.read(cx).mode().as_running()),
|
||||
|this, running_session| {
|
||||
let thread_status = running_session
|
||||
.read(cx)
|
||||
.thread_status(cx)
|
||||
.unwrap_or(project::debugger::session::ThreadStatus::Exited);
|
||||
let capabilities = running_session.read(cx).capabilities(cx);
|
||||
this.map(|this| {
|
||||
if thread_status == ThreadStatus::Running {
|
||||
this.child(
|
||||
IconButton::new("debug-pause", IconName::DebugPause)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.pause_thread(cx);
|
||||
},
|
||||
))
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Pause program")(window, cx)
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
this.child(
|
||||
IconButton::new("debug-continue", IconName::DebugContinue)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| this.continue_thread(cx),
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Continue program")(window, cx)
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
.child(
|
||||
IconButton::new("debug-step-over", IconName::ArrowRight)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.step_over(cx);
|
||||
},
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Step over")(window, cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-step-out", IconName::ArrowUpRight)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.step_out(cx);
|
||||
},
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Step out")(window, cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-step-into", IconName::ArrowDownRight)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.step_in(cx);
|
||||
},
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Step in")(window, cx)
|
||||
}),
|
||||
)
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
IconButton::new(
|
||||
"debug-enable-breakpoint",
|
||||
IconName::DebugDisabledBreakpoint,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.disabled(thread_status != ThreadStatus::Stopped),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-disable-breakpoint", IconName::CircleOff)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.disabled(thread_status != ThreadStatus::Stopped),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-disable-all-breakpoints", IconName::BugOff)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.disabled(
|
||||
thread_status == ThreadStatus::Exited
|
||||
|| thread_status == ThreadStatus::Ended,
|
||||
)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.toggle_ignore_breakpoints(cx);
|
||||
},
|
||||
))
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Disable all breakpoints")(window, cx)
|
||||
}),
|
||||
)
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
IconButton::new("debug-restart", IconName::DebugRestart)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.restart_session(cx);
|
||||
},
|
||||
))
|
||||
.disabled(
|
||||
!capabilities.supports_restart_request.unwrap_or_default(),
|
||||
)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Restart")(window, cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-stop", IconName::Power)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.stop_thread(cx);
|
||||
},
|
||||
))
|
||||
.disabled(
|
||||
thread_status != ThreadStatus::Stopped
|
||||
&& thread_status != ThreadStatus::Running,
|
||||
)
|
||||
.tooltip({
|
||||
let label = if capabilities
|
||||
.supports_terminate_threads_request
|
||||
.unwrap_or_default()
|
||||
{
|
||||
"Terminate Thread"
|
||||
} else {
|
||||
"Terminate all Threads"
|
||||
};
|
||||
move |window, cx| Tooltip::text(label)(window, cx)
|
||||
}),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.when_some(
|
||||
active_session
|
||||
.as_ref()
|
||||
.and_then(|session| session.read(cx).mode().as_running())
|
||||
.cloned(),
|
||||
|this, session| {
|
||||
this.child(
|
||||
session.update(cx, |this, cx| this.thread_dropdown(window, cx)),
|
||||
)
|
||||
.child(Divider::vertical())
|
||||
},
|
||||
)
|
||||
.when_some(active_session.as_ref(), |this, session| {
|
||||
let sessions = self.sessions.clone();
|
||||
let weak = cx.weak_entity();
|
||||
let label = session.read(cx).label(cx);
|
||||
this.child(DropdownMenu::new(
|
||||
"debugger-session-list",
|
||||
label,
|
||||
ContextMenu::build(window, cx, move |mut this, _, cx| {
|
||||
for item in sessions {
|
||||
let weak = weak.clone();
|
||||
this = this.entry(
|
||||
session.read(cx).label(cx),
|
||||
None,
|
||||
move |window, cx| {
|
||||
weak.update(cx, |panel, cx| {
|
||||
panel.activate_session(
|
||||
item.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
);
|
||||
}
|
||||
this
|
||||
}),
|
||||
))
|
||||
.child(Divider::vertical())
|
||||
})
|
||||
.child(
|
||||
IconButton::new("debug-new-session", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let workspace = self.workspace.clone();
|
||||
let weak_panel = cx.weak_entity();
|
||||
let past_debug_definition = self.past_debug_definition.clone();
|
||||
move |_, window, cx| {
|
||||
let weak_panel = weak_panel.clone();
|
||||
let past_debug_definition = past_debug_definition.clone();
|
||||
|
||||
let _ = workspace.update(cx, |this, cx| {
|
||||
let workspace = cx.weak_entity();
|
||||
this.toggle_modal(window, cx, |window, cx| {
|
||||
NewSessionModal::new(
|
||||
past_debug_definition,
|
||||
weak_panel,
|
||||
workspace,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::for_action(
|
||||
"New Debug Session",
|
||||
&CreateDebuggingSession,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn activate_session(
|
||||
&mut self,
|
||||
_: &Entity<Pane>,
|
||||
event: &pane::Event,
|
||||
session_item: Entity<DebugSession>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
pane::Event::Remove { .. } => cx.emit(PanelEvent::Close),
|
||||
pane::Event::ZoomIn => cx.emit(PanelEvent::ZoomIn),
|
||||
pane::Event::ZoomOut => cx.emit(PanelEvent::ZoomOut),
|
||||
pane::Event::AddItem { item } => {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
item.added_to_pane(workspace, self.pane.clone(), window, cx)
|
||||
})
|
||||
.ok();
|
||||
debug_assert!(self.sessions.contains(&session_item));
|
||||
session_item.focus_handle(cx).focus(window);
|
||||
session_item.update(cx, |this, cx| {
|
||||
if let Some(running) = this.mode().as_running() {
|
||||
running.update(cx, |this, cx| {
|
||||
this.go_to_selected_stack_frame(window, cx);
|
||||
});
|
||||
}
|
||||
pane::Event::RemovedItem { item } => {
|
||||
if let Some(debug_session) = item.downcast::<DebugSession>() {
|
||||
debug_session.update(cx, |session, cx| {
|
||||
session.shutdown(cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
pane::Event::ActivateItem {
|
||||
local: _,
|
||||
focus_changed,
|
||||
} => {
|
||||
if *focus_changed {
|
||||
if let Some(debug_session) = self
|
||||
.pane
|
||||
.read(cx)
|
||||
.active_item()
|
||||
.and_then(|item| item.downcast::<DebugSession>())
|
||||
{
|
||||
if let Some(running) = debug_session
|
||||
.read_with(cx, |session, _| session.mode().as_running().cloned())
|
||||
{
|
||||
running.update(cx, |running, cx| {
|
||||
running.go_to_selected_stack_frame(window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
self.active_session = Some(session_item);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,16 +621,12 @@ impl EventEmitter<DebugPanelEvent> for DebugPanel {}
|
||||
impl EventEmitter<project::Event> for DebugPanel {}
|
||||
|
||||
impl Focusable for DebugPanel {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.pane.focus_handle(cx)
|
||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Panel for DebugPanel {
|
||||
fn pane(&self) -> Option<Entity<Pane>> {
|
||||
Some(self.pane.clone())
|
||||
}
|
||||
|
||||
fn persistent_name() -> &'static str {
|
||||
"DebugPanel"
|
||||
}
|
||||
@@ -507,7 +647,7 @@ impl Panel for DebugPanel {
|
||||
) {
|
||||
}
|
||||
|
||||
fn size(&self, _window: &Window, _cx: &App) -> Pixels {
|
||||
fn size(&self, _window: &Window, _: &App) -> Pixels {
|
||||
self.size
|
||||
}
|
||||
|
||||
@@ -538,42 +678,51 @@ impl Panel for DebugPanel {
|
||||
fn activation_priority(&self) -> u32 {
|
||||
9
|
||||
}
|
||||
fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if active && self.pane.read(cx).items_len() == 0 {
|
||||
let Some(project) = self.project.clone().upgrade() else {
|
||||
return;
|
||||
};
|
||||
let config = self.last_inert_config.clone();
|
||||
let panel = cx.weak_entity();
|
||||
// todo: We need to revisit it when we start adding stopped items to pane (as that'll cause us to add two items).
|
||||
self.pane.update(cx, |this, cx| {
|
||||
this.add_item(
|
||||
Box::new(DebugSession::inert(
|
||||
project,
|
||||
self.workspace.clone(),
|
||||
panel,
|
||||
config,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
|
||||
}
|
||||
|
||||
impl Render for DebugPanel {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let has_sessions = self.sessions.len() > 0;
|
||||
debug_assert_eq!(has_sessions, self.active_session.is_some());
|
||||
|
||||
v_flex()
|
||||
.key_context("DebugPanel")
|
||||
.track_focus(&self.focus_handle(cx))
|
||||
.size_full()
|
||||
.child(self.pane.clone())
|
||||
.key_context("DebugPanel")
|
||||
.child(h_flex().children(self.top_controls_strip(window, cx)))
|
||||
.track_focus(&self.focus_handle(cx))
|
||||
.map(|this| {
|
||||
if has_sessions {
|
||||
this.children(self.active_session.clone())
|
||||
} else {
|
||||
this.child(
|
||||
v_flex()
|
||||
.h_full()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
h_flex().child(
|
||||
Label::new("No Debugging Sessions")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex().flex_shrink().child(
|
||||
Button::new("spawn-new-session-empty-state", "New Session")
|
||||
.size(ButtonSize::Large)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(
|
||||
CreateDebuggingSession.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,38 @@
|
||||
use dap::debugger_settings::DebuggerSettings;
|
||||
use debugger_panel::{DebugPanel, ToggleFocus};
|
||||
use feature_flags::{Debugger, FeatureFlagViewExt};
|
||||
use gpui::App;
|
||||
use gpui::{App, actions};
|
||||
use new_session_modal::NewSessionModal;
|
||||
use session::DebugSession;
|
||||
use settings::Settings;
|
||||
use workspace::{
|
||||
Pause, Restart, ShutdownDebugAdapters, StepBack, StepInto, StepOver, Stop,
|
||||
ToggleIgnoreBreakpoints, Workspace,
|
||||
};
|
||||
use workspace::{ShutdownDebugAdapters, Workspace};
|
||||
|
||||
pub mod attach_modal;
|
||||
pub mod debugger_panel;
|
||||
pub mod session;
|
||||
mod new_session_modal;
|
||||
pub(crate) mod session;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod tests;
|
||||
|
||||
actions!(
|
||||
debugger,
|
||||
[
|
||||
Start,
|
||||
Continue,
|
||||
Disconnect,
|
||||
Pause,
|
||||
Restart,
|
||||
StepInto,
|
||||
StepOver,
|
||||
StepOut,
|
||||
StepBack,
|
||||
Stop,
|
||||
ToggleIgnoreBreakpoints,
|
||||
ClearAllBreakpoints,
|
||||
CreateDebuggingSession,
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
DebuggerSettings::register(cx);
|
||||
@@ -31,80 +49,80 @@ pub fn init(cx: &mut App) {
|
||||
workspace.toggle_panel_focus::<DebugPanel>(window, cx);
|
||||
})
|
||||
.register_action(|workspace, _: &Pause, _, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session(cx)
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.pause_thread(cx))
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session()
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.pause_thread(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &Restart, _, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session(cx)
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.restart_session(cx))
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session()
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.restart_session(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &StepInto, _, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session(cx)
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.step_in(cx))
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session()
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.step_in(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &StepOver, _, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session(cx)
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.step_over(cx))
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session()
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.step_over(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &StepBack, _, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session(cx)
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.step_back(cx))
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session()
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.step_back(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &Stop, _, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session(cx)
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.stop_thread(cx))
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session()
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.stop_thread(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &ToggleIgnoreBreakpoints, _, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session(cx)
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session()
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(
|
||||
@@ -115,6 +133,24 @@ pub fn init(cx: &mut App) {
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
.register_action(
|
||||
|workspace: &mut Workspace, _: &CreateDebuggingSession, window, cx| {
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
let weak_panel = debug_panel.downgrade();
|
||||
let weak_workspace = cx.weak_entity();
|
||||
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
NewSessionModal::new(
|
||||
debug_panel.read(cx).past_debug_definition.clone(),
|
||||
weak_panel,
|
||||
weak_workspace,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
})
|
||||
})
|
||||
|
||||
633
crates/debugger_ui/src/new_session_modal.rs
Normal file
633
crates/debugger_ui/src/new_session_modal.rs
Normal file
@@ -0,0 +1,633 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
ops::Not,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use dap::DebugRequestType;
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{
|
||||
App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, TextStyle,
|
||||
WeakEntity,
|
||||
};
|
||||
use settings::Settings;
|
||||
use task::{DebugTaskDefinition, LaunchConfig};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
|
||||
ContextMenu, Disableable, DropdownMenu, FluentBuilder, InteractiveElement, IntoElement, Label,
|
||||
LabelCommon as _, ParentElement, RenderOnce, SharedString, Styled, StyledExt, ToggleButton,
|
||||
ToggleState, Toggleable, Window, div, h_flex, relative, rems, v_flex,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct NewSessionModal {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
debug_panel: WeakEntity<DebugPanel>,
|
||||
mode: NewSessionMode,
|
||||
stop_on_entry: ToggleState,
|
||||
debugger: Option<SharedString>,
|
||||
last_selected_profile_name: Option<SharedString>,
|
||||
}
|
||||
|
||||
fn suggested_label(request: &DebugRequestType, debugger: &str) -> String {
|
||||
match request {
|
||||
DebugRequestType::Launch(config) => {
|
||||
let last_path_component = Path::new(&config.program)
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy())
|
||||
.unwrap_or_else(|| Cow::Borrowed(&config.program));
|
||||
|
||||
format!("{} ({debugger})", last_path_component)
|
||||
}
|
||||
DebugRequestType::Attach(config) => format!(
|
||||
"pid: {} ({debugger})",
|
||||
config.process_id.unwrap_or(u32::MAX)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
impl NewSessionModal {
|
||||
pub(super) fn new(
|
||||
past_debug_definition: Option<DebugTaskDefinition>,
|
||||
debug_panel: WeakEntity<DebugPanel>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let debugger = past_debug_definition
|
||||
.as_ref()
|
||||
.map(|def| def.adapter.clone().into());
|
||||
|
||||
let stop_on_entry = past_debug_definition
|
||||
.as_ref()
|
||||
.and_then(|def| def.stop_on_entry);
|
||||
|
||||
let launch_config = match past_debug_definition.map(|def| def.request) {
|
||||
Some(DebugRequestType::Launch(launch_config)) => Some(launch_config),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Self {
|
||||
workspace: workspace.clone(),
|
||||
debugger,
|
||||
debug_panel,
|
||||
mode: NewSessionMode::launch(launch_config, window, cx),
|
||||
stop_on_entry: stop_on_entry
|
||||
.map(Into::into)
|
||||
.unwrap_or(ToggleState::Unselected),
|
||||
last_selected_profile_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_config(&self, cx: &App) -> Option<DebugTaskDefinition> {
|
||||
let request = self.mode.debug_task(cx);
|
||||
|
||||
Some(DebugTaskDefinition {
|
||||
adapter: self.debugger.clone()?.to_string(),
|
||||
label: suggested_label(&request, self.debugger.as_deref()?),
|
||||
request,
|
||||
initialize_args: None,
|
||||
tcp_connection: None,
|
||||
locator: None,
|
||||
stop_on_entry: match self.stop_on_entry {
|
||||
ToggleState::Selected => Some(true),
|
||||
_ => None,
|
||||
},
|
||||
})
|
||||
}
|
||||
fn start_new_session(&self, cx: &mut Context<Self>) -> Result<()> {
|
||||
let workspace = self.workspace.clone();
|
||||
let config = self
|
||||
.debug_config(cx)
|
||||
.ok_or_else(|| anyhow!("Failed to create a debug config"))?;
|
||||
|
||||
let _ = self.debug_panel.update(cx, |panel, _| {
|
||||
panel.past_debug_definition = Some(config.clone());
|
||||
});
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let project = workspace.update(cx, |workspace, _| workspace.project().clone())?;
|
||||
let task =
|
||||
project.update(cx, |this, cx| this.start_debug_session(config.into(), cx))?;
|
||||
let spawn_result = task.await;
|
||||
if spawn_result.is_ok() {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
spawn_result?;
|
||||
anyhow::Result::<_, anyhow::Error>::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_attach_picker(
|
||||
attach: &Entity<AttachMode>,
|
||||
selected_debugger: &str,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
attach.update(cx, |this, cx| {
|
||||
if selected_debugger != this.debug_definition.adapter {
|
||||
this.debug_definition.adapter = selected_debugger.into();
|
||||
if let Some(project) = this
|
||||
.workspace
|
||||
.read_with(cx, |workspace, _| workspace.project().clone())
|
||||
.ok()
|
||||
{
|
||||
this.attach_picker = Some(cx.new(|cx| {
|
||||
let modal = AttachModal::new(
|
||||
project,
|
||||
this.debug_definition.clone(),
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
window.focus(&modal.focus_handle(cx));
|
||||
|
||||
modal
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
fn adapter_drop_down_menu(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> ui::DropdownMenu {
|
||||
let workspace = self.workspace.clone();
|
||||
let weak = cx.weak_entity();
|
||||
let debugger = self.debugger.clone();
|
||||
DropdownMenu::new(
|
||||
"dap-adapter-picker",
|
||||
debugger
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| &SELECT_DEBUGGER_LABEL)
|
||||
.clone(),
|
||||
ContextMenu::build(window, cx, move |mut menu, _, cx| {
|
||||
let setter_for_name = |name: SharedString| {
|
||||
let weak = weak.clone();
|
||||
move |window: &mut Window, cx: &mut App| {
|
||||
weak.update(cx, |this, cx| {
|
||||
this.debugger = Some(name.clone());
|
||||
cx.notify();
|
||||
if let NewSessionMode::Attach(attach) = &this.mode {
|
||||
Self::update_attach_picker(&attach, &name, window, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
|
||||
let available_adapters = workspace
|
||||
.update(cx, |this, cx| {
|
||||
this.project()
|
||||
.read(cx)
|
||||
.debug_adapters()
|
||||
.enumerate_adapters()
|
||||
})
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
|
||||
for adapter in available_adapters {
|
||||
menu = menu.entry(adapter.0.clone(), None, setter_for_name(adapter.0.clone()));
|
||||
}
|
||||
menu
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn debug_config_drop_down_menu(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> ui::DropdownMenu {
|
||||
let workspace = self.workspace.clone();
|
||||
let weak = cx.weak_entity();
|
||||
let last_profile = self.last_selected_profile_name.clone();
|
||||
DropdownMenu::new(
|
||||
"debug-config-menu",
|
||||
last_profile.unwrap_or_else(|| SELECT_SCENARIO_LABEL.clone()),
|
||||
ContextMenu::build(window, cx, move |mut menu, _, cx| {
|
||||
let setter_for_name = |task: DebugTaskDefinition| {
|
||||
let weak = weak.clone();
|
||||
let workspace = workspace.clone();
|
||||
move |window: &mut Window, cx: &mut App| {
|
||||
weak.update(cx, |this, cx| {
|
||||
this.last_selected_profile_name = Some(SharedString::from(&task.label));
|
||||
this.debugger = Some(task.adapter.clone().into());
|
||||
|
||||
match &task.request {
|
||||
DebugRequestType::Launch(launch_config) => {
|
||||
this.mode = NewSessionMode::launch(
|
||||
Some(launch_config.clone()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
DebugRequestType::Attach(_) => {
|
||||
this.mode = NewSessionMode::attach(
|
||||
this.debugger.clone(),
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
if let Some((debugger, attach)) =
|
||||
this.debugger.as_ref().zip(this.mode.as_attach())
|
||||
{
|
||||
Self::update_attach_picker(&attach, &debugger, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
|
||||
let available_adapters: Vec<DebugTaskDefinition> = workspace
|
||||
.update(cx, |this, cx| {
|
||||
this.project()
|
||||
.read(cx)
|
||||
.task_store()
|
||||
.read(cx)
|
||||
.task_inventory()
|
||||
.iter()
|
||||
.flat_map(|task_inventory| task_inventory.read(cx).list_debug_tasks())
|
||||
.cloned()
|
||||
.filter_map(|task| task.try_into().ok())
|
||||
.collect()
|
||||
})
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
|
||||
for debug_definition in available_adapters {
|
||||
menu = menu.entry(
|
||||
debug_definition.label.clone(),
|
||||
None,
|
||||
setter_for_name(debug_definition),
|
||||
);
|
||||
}
|
||||
menu
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LaunchMode {
|
||||
program: Entity<Editor>,
|
||||
cwd: Entity<Editor>,
|
||||
}
|
||||
|
||||
impl LaunchMode {
|
||||
fn new(
|
||||
past_launch_config: Option<LaunchConfig>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
let (past_program, past_cwd) = past_launch_config
|
||||
.map(|config| (Some(config.program), config.cwd))
|
||||
.unwrap_or_else(|| (None, None));
|
||||
|
||||
let program = cx.new(|cx| Editor::single_line(window, cx));
|
||||
program.update(cx, |this, cx| {
|
||||
this.set_placeholder_text("Program path", cx);
|
||||
|
||||
if let Some(past_program) = past_program {
|
||||
this.set_text(past_program, window, cx);
|
||||
};
|
||||
});
|
||||
let cwd = cx.new(|cx| Editor::single_line(window, cx));
|
||||
cwd.update(cx, |this, cx| {
|
||||
this.set_placeholder_text("Working Directory", cx);
|
||||
if let Some(past_cwd) = past_cwd {
|
||||
this.set_text(past_cwd.to_string_lossy(), window, cx);
|
||||
};
|
||||
});
|
||||
cx.new(|_| Self { program, cwd })
|
||||
}
|
||||
|
||||
fn debug_task(&self, cx: &App) -> task::LaunchConfig {
|
||||
let path = self.cwd.read(cx).text(cx);
|
||||
task::LaunchConfig {
|
||||
program: self.program.read(cx).text(cx),
|
||||
cwd: path.is_empty().not().then(|| PathBuf::from(path)),
|
||||
args: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AttachMode {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
debug_definition: DebugTaskDefinition,
|
||||
attach_picker: Option<Entity<AttachModal>>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl AttachMode {
|
||||
fn new(
|
||||
debugger: Option<SharedString>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
let debug_definition = DebugTaskDefinition {
|
||||
label: "Attach New Session Setup".into(),
|
||||
request: dap::DebugRequestType::Attach(task::AttachConfig { process_id: None }),
|
||||
tcp_connection: None,
|
||||
adapter: debugger.clone().unwrap_or_default().into(),
|
||||
locator: None,
|
||||
initialize_args: None,
|
||||
stop_on_entry: Some(false),
|
||||
};
|
||||
|
||||
let attach_picker = if let Some(project) = debugger.and(
|
||||
workspace
|
||||
.read_with(cx, |workspace, _| workspace.project().clone())
|
||||
.ok(),
|
||||
) {
|
||||
Some(cx.new(|cx| {
|
||||
let modal = AttachModal::new(project, debug_definition.clone(), false, window, cx);
|
||||
window.focus(&modal.focus_handle(cx));
|
||||
|
||||
modal
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
cx.new(|cx| Self {
|
||||
workspace,
|
||||
debug_definition,
|
||||
attach_picker,
|
||||
focus_handle: cx.focus_handle(),
|
||||
})
|
||||
}
|
||||
fn debug_task(&self) -> task::AttachConfig {
|
||||
task::AttachConfig { process_id: None }
|
||||
}
|
||||
}
|
||||
|
||||
static SELECT_DEBUGGER_LABEL: SharedString = SharedString::new_static("Select Debugger");
|
||||
static SELECT_SCENARIO_LABEL: SharedString = SharedString::new_static("Select Profile");
|
||||
|
||||
#[derive(Clone)]
|
||||
enum NewSessionMode {
|
||||
Launch(Entity<LaunchMode>),
|
||||
Attach(Entity<AttachMode>),
|
||||
}
|
||||
|
||||
impl NewSessionMode {
|
||||
fn debug_task(&self, cx: &App) -> DebugRequestType {
|
||||
match self {
|
||||
NewSessionMode::Launch(entity) => entity.read(cx).debug_task(cx).into(),
|
||||
NewSessionMode::Attach(entity) => entity.read(cx).debug_task().into(),
|
||||
}
|
||||
}
|
||||
fn as_attach(&self) -> Option<&Entity<AttachMode>> {
|
||||
if let NewSessionMode::Attach(entity) = self {
|
||||
Some(entity)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for NewSessionMode {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
match &self {
|
||||
NewSessionMode::Launch(entity) => entity.read(cx).program.focus_handle(cx),
|
||||
NewSessionMode::Attach(entity) => entity.read(cx).focus_handle.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for LaunchMode {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
v_flex()
|
||||
.p_2()
|
||||
.w_full()
|
||||
.gap_3()
|
||||
.track_focus(&self.program.focus_handle(cx))
|
||||
.child(
|
||||
div().child(
|
||||
Label::new("Program")
|
||||
.size(ui::LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(render_editor(&self.program, window, cx))
|
||||
.child(
|
||||
div().child(
|
||||
Label::new("Working Directory")
|
||||
.size(ui::LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(render_editor(&self.cwd, window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for AttachMode {
|
||||
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
|
||||
v_flex().w_full().children(self.attach_picker.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for NewSessionMode {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl ui::IntoElement {
|
||||
match self {
|
||||
NewSessionMode::Launch(entity) => entity.update(cx, |this, cx| {
|
||||
this.clone().render(window, cx).into_any_element()
|
||||
}),
|
||||
NewSessionMode::Attach(entity) => entity.update(cx, |this, cx| {
|
||||
this.clone().render(window, cx).into_any_element()
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NewSessionMode {
|
||||
fn attach(
|
||||
debugger: Option<SharedString>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
Self::Attach(AttachMode::new(debugger, workspace, window, cx))
|
||||
}
|
||||
fn launch(past_launch_config: Option<LaunchConfig>, window: &mut Window, cx: &mut App) -> Self {
|
||||
Self::Launch(LaunchMode::new(past_launch_config, window, cx))
|
||||
}
|
||||
}
|
||||
fn render_editor(editor: &Entity<Editor>, window: &mut Window, cx: &App) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let theme = cx.theme();
|
||||
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_features: settings.buffer_font.features.clone(),
|
||||
font_size: settings.buffer_font_size(cx).into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
background_color: Some(theme.colors().editor_background),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let element = EditorElement::new(
|
||||
editor,
|
||||
EditorStyle {
|
||||
background: theme.colors().editor_background,
|
||||
local_player: theme.players().local(),
|
||||
text: text_style,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
div()
|
||||
.rounded_md()
|
||||
.p_1()
|
||||
.border_1()
|
||||
.border_color(theme.colors().border_variant)
|
||||
.when(
|
||||
editor.focus_handle(cx).contains_focused(window, cx),
|
||||
|this| this.border_color(theme.colors().border_focused),
|
||||
)
|
||||
.child(element)
|
||||
.bg(theme.colors().editor_background)
|
||||
}
|
||||
|
||||
impl Render for NewSessionModal {
|
||||
fn render(
|
||||
&mut self,
|
||||
window: &mut ui::Window,
|
||||
cx: &mut ui::Context<Self>,
|
||||
) -> impl ui::IntoElement {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.w(rems(34.))
|
||||
.elevation_3(cx)
|
||||
.bg(cx.theme().colors().elevated_surface_background)
|
||||
.on_action(cx.listener(|_, _: &menu::Cancel, _, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_around()
|
||||
.p_2()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_start()
|
||||
.w_full()
|
||||
.child(
|
||||
ToggleButton::new(
|
||||
"debugger-session-ui-launch-button",
|
||||
"New Session",
|
||||
)
|
||||
.size(ButtonSize::Default)
|
||||
.style(ui::ButtonStyle::Subtle)
|
||||
.toggle_state(matches!(self.mode, NewSessionMode::Launch(_)))
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.mode = NewSessionMode::launch(None, window, cx);
|
||||
this.mode.focus_handle(cx).focus(window);
|
||||
cx.notify();
|
||||
}))
|
||||
.first(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new(
|
||||
"debugger-session-ui-attach-button",
|
||||
"Attach to Process",
|
||||
)
|
||||
.size(ButtonSize::Default)
|
||||
.toggle_state(matches!(self.mode, NewSessionMode::Attach(_)))
|
||||
.style(ui::ButtonStyle::Subtle)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.mode = NewSessionMode::attach(
|
||||
this.debugger.clone(),
|
||||
this.workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
if let Some((debugger, attach)) =
|
||||
this.debugger.as_ref().zip(this.mode.as_attach())
|
||||
{
|
||||
Self::update_attach_picker(&attach, &debugger, window, cx);
|
||||
}
|
||||
this.mode.focus_handle(cx).focus(window);
|
||||
cx.notify();
|
||||
}))
|
||||
.last(),
|
||||
),
|
||||
)
|
||||
.justify_between()
|
||||
.child(self.adapter_drop_down_menu(window, cx))
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.border_b_1(),
|
||||
)
|
||||
.child(v_flex().child(self.mode.clone().render(window, cx)))
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.p_2()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.border_t_1()
|
||||
.w_full()
|
||||
.child(self.debug_config_drop_down_menu(window, cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_end()
|
||||
.when(matches!(self.mode, NewSessionMode::Launch(_)), |this| {
|
||||
let weak = cx.weak_entity();
|
||||
this.child(
|
||||
CheckboxWithLabel::new(
|
||||
"debugger-stop-on-entry",
|
||||
Label::new("Stop on Entry").size(ui::LabelSize::Small),
|
||||
self.stop_on_entry,
|
||||
move |state, _, cx| {
|
||||
weak.update(cx, |this, _| {
|
||||
this.stop_on_entry = *state;
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
)
|
||||
.checkbox_position(ui::IconPosition::End),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
Button::new("debugger-spawn", "Start")
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.start_new_session(cx).log_err();
|
||||
}))
|
||||
.disabled(self.debugger.is_none()),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for NewSessionModal {}
|
||||
impl Focusable for NewSessionModal {
|
||||
fn focus_handle(&self, cx: &ui::App) -> gpui::FocusHandle {
|
||||
self.mode.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl ModalView for NewSessionModal {}
|
||||
@@ -1,26 +1,13 @@
|
||||
mod failed;
|
||||
mod inert;
|
||||
pub mod running;
|
||||
mod starting;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use dap::client::SessionId;
|
||||
use failed::FailedState;
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
Subscription, Task, Transformation, WeakEntity, percentage,
|
||||
};
|
||||
use inert::{InertEvent, InertState};
|
||||
use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
|
||||
use project::Project;
|
||||
use project::debugger::{dap_store::DapStore, session::Session};
|
||||
use project::worktree_store::WorktreeStore;
|
||||
use rpc::proto::{self, PeerId};
|
||||
use running::RunningState;
|
||||
use starting::{StartingEvent, StartingState};
|
||||
use task::DebugTaskDefinition;
|
||||
use ui::{Indicator, prelude::*};
|
||||
use util::ResultExt;
|
||||
use ui::prelude::*;
|
||||
use workspace::{
|
||||
FollowableItem, ViewId, Workspace,
|
||||
item::{self, Item},
|
||||
@@ -29,9 +16,6 @@ use workspace::{
|
||||
use crate::debugger_panel::DebugPanel;
|
||||
|
||||
pub(crate) enum DebugSessionState {
|
||||
Inert(Entity<InertState>),
|
||||
Starting(Entity<StartingState>),
|
||||
Failed(Entity<FailedState>),
|
||||
Running(Entity<running::RunningState>),
|
||||
}
|
||||
|
||||
@@ -39,7 +23,6 @@ impl DebugSessionState {
|
||||
pub(crate) fn as_running(&self) -> Option<&Entity<running::RunningState>> {
|
||||
match &self {
|
||||
DebugSessionState::Running(entity) => Some(entity),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,9 +31,9 @@ pub struct DebugSession {
|
||||
remote_id: Option<workspace::ViewId>,
|
||||
mode: DebugSessionState,
|
||||
dap_store: WeakEntity<DapStore>,
|
||||
debug_panel: WeakEntity<DebugPanel>,
|
||||
worktree_store: WeakEntity<WorktreeStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
_debug_panel: WeakEntity<DebugPanel>,
|
||||
_worktree_store: WeakEntity<WorktreeStore>,
|
||||
_workspace: WeakEntity<Workspace>,
|
||||
_subscriptions: [Subscription; 1],
|
||||
}
|
||||
|
||||
@@ -60,59 +43,24 @@ pub enum DebugPanelItemEvent {
|
||||
Stopped { go_to_stack_frame: bool },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum ThreadItem {
|
||||
Console,
|
||||
LoadedSource,
|
||||
Modules,
|
||||
Variables,
|
||||
}
|
||||
|
||||
impl DebugSession {
|
||||
pub(super) fn inert(
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
debug_panel: WeakEntity<DebugPanel>,
|
||||
config: Option<DebugTaskDefinition>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
let default_cwd = project
|
||||
.read(cx)
|
||||
.worktrees(cx)
|
||||
.next()
|
||||
.and_then(|tree| tree.read(cx).abs_path().to_str().map(|str| str.to_string()))
|
||||
.unwrap_or_default();
|
||||
|
||||
let inert =
|
||||
cx.new(|cx| InertState::new(workspace.clone(), &default_cwd, config, window, cx));
|
||||
|
||||
let project = project.read(cx);
|
||||
let dap_store = project.dap_store().downgrade();
|
||||
let worktree_store = project.worktree_store().downgrade();
|
||||
cx.new(|cx| {
|
||||
let _subscriptions = [cx.subscribe_in(&inert, window, Self::on_inert_event)];
|
||||
Self {
|
||||
remote_id: None,
|
||||
mode: DebugSessionState::Inert(inert),
|
||||
dap_store,
|
||||
worktree_store,
|
||||
debug_panel,
|
||||
workspace,
|
||||
_subscriptions,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn running(
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
session: Entity<Session>,
|
||||
debug_panel: WeakEntity<DebugPanel>,
|
||||
_debug_panel: WeakEntity<DebugPanel>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
let mode = cx.new(|cx| RunningState::new(session.clone(), workspace.clone(), window, cx));
|
||||
let mode = cx.new(|cx| {
|
||||
RunningState::new(
|
||||
session.clone(),
|
||||
project.clone(),
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
cx.new(|cx| Self {
|
||||
_subscriptions: [cx.subscribe(&mode, |_, _, _, cx| {
|
||||
@@ -121,26 +69,21 @@ impl DebugSession {
|
||||
remote_id: None,
|
||||
mode: DebugSessionState::Running(mode),
|
||||
dap_store: project.read(cx).dap_store().downgrade(),
|
||||
debug_panel,
|
||||
worktree_store: project.read(cx).worktree_store().downgrade(),
|
||||
workspace,
|
||||
_debug_panel,
|
||||
_worktree_store: project.read(cx).worktree_store().downgrade(),
|
||||
_workspace: workspace,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn session_id(&self, cx: &App) -> Option<SessionId> {
|
||||
pub(crate) fn session_id(&self, cx: &App) -> SessionId {
|
||||
match &self.mode {
|
||||
DebugSessionState::Inert(_) => None,
|
||||
DebugSessionState::Starting(entity) => Some(entity.read(cx).session_id),
|
||||
DebugSessionState::Failed(_) => None,
|
||||
DebugSessionState::Running(entity) => Some(entity.read(cx).session_id()),
|
||||
DebugSessionState::Running(entity) => entity.read(cx).session_id(),
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(unused)]
|
||||
pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
|
||||
match &self.mode {
|
||||
DebugSessionState::Inert(_) => {}
|
||||
DebugSessionState::Starting(_entity) => {} // todo(debugger): we need to shutdown the starting process in this case (or recreate it on a breakpoint being hit)
|
||||
DebugSessionState::Failed(_) => {}
|
||||
DebugSessionState::Running(state) => state.update(cx, |state, cx| state.shutdown(cx)),
|
||||
}
|
||||
}
|
||||
@@ -149,63 +92,29 @@ impl DebugSession {
|
||||
&self.mode
|
||||
}
|
||||
|
||||
fn on_inert_event(
|
||||
&mut self,
|
||||
_: &Entity<InertState>,
|
||||
event: &InertEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let dap_store = self.dap_store.clone();
|
||||
let InertEvent::Spawned { config } = event;
|
||||
let config = config.clone();
|
||||
|
||||
self.debug_panel
|
||||
.update(cx, |this, _| this.last_inert_config = Some(config.clone()))
|
||||
.log_err();
|
||||
|
||||
let worktree = self
|
||||
.worktree_store
|
||||
.update(cx, |this, _| this.worktrees().next())
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("worktree-less project");
|
||||
let Ok((new_session_id, task)) = dap_store.update(cx, |store, cx| {
|
||||
store.new_session(config.into(), &worktree, None, cx)
|
||||
}) else {
|
||||
return;
|
||||
pub(crate) fn label(&self, cx: &App) -> String {
|
||||
let session_id = match &self.mode {
|
||||
DebugSessionState::Running(running_state) => running_state.read(cx).session_id(),
|
||||
};
|
||||
let starting = cx.new(|cx| StartingState::new(new_session_id, task, cx));
|
||||
|
||||
self._subscriptions = [cx.subscribe_in(&starting, window, Self::on_starting_event)];
|
||||
self.mode = DebugSessionState::Starting(starting);
|
||||
}
|
||||
|
||||
fn on_starting_event(
|
||||
&mut self,
|
||||
_: &Entity<StartingState>,
|
||||
event: &StartingEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let StartingEvent::Finished(session) = event {
|
||||
let mode =
|
||||
cx.new(|cx| RunningState::new(session.clone(), self.workspace.clone(), window, cx));
|
||||
self.mode = DebugSessionState::Running(mode);
|
||||
} else if let StartingEvent::Failed = event {
|
||||
self.mode = DebugSessionState::Failed(cx.new(FailedState::new));
|
||||
let Ok(Some(session)) = self
|
||||
.dap_store
|
||||
.read_with(cx, |store, _| store.session_by_id(session_id))
|
||||
else {
|
||||
return "".to_owned();
|
||||
};
|
||||
cx.notify();
|
||||
session
|
||||
.read(cx)
|
||||
.as_local()
|
||||
.expect("Remote Debug Sessions are not implemented yet")
|
||||
.label()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DebugPanelItemEvent> for DebugSession {}
|
||||
|
||||
impl Focusable for DebugSession {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
match &self.mode {
|
||||
DebugSessionState::Inert(inert_state) => inert_state.focus_handle(cx),
|
||||
DebugSessionState::Starting(starting_state) => starting_state.focus_handle(cx),
|
||||
DebugSessionState::Failed(failed_state) => failed_state.focus_handle(cx),
|
||||
DebugSessionState::Running(running_state) => running_state.focus_handle(cx),
|
||||
}
|
||||
}
|
||||
@@ -213,61 +122,6 @@ impl Focusable for DebugSession {
|
||||
|
||||
impl Item for DebugSession {
|
||||
type Event = DebugPanelItemEvent;
|
||||
fn tab_content(&self, _: item::TabContentParams, _: &Window, cx: &App) -> AnyElement {
|
||||
let (icon, label, color) = match &self.mode {
|
||||
DebugSessionState::Inert(_) => (None, "New Session", Color::Default),
|
||||
DebugSessionState::Starting(_) => (None, "Starting", Color::Default),
|
||||
DebugSessionState::Failed(_) => (
|
||||
Some(Indicator::dot().color(Color::Error)),
|
||||
"Failed",
|
||||
Color::Error,
|
||||
),
|
||||
DebugSessionState::Running(state) => {
|
||||
if state.read(cx).session().read(cx).is_terminated() {
|
||||
(
|
||||
Some(Indicator::dot().color(Color::Error)),
|
||||
"Terminated",
|
||||
Color::Error,
|
||||
)
|
||||
} else {
|
||||
match state.read(cx).thread_status(cx).unwrap_or_default() {
|
||||
project::debugger::session::ThreadStatus::Stopped => (
|
||||
Some(Indicator::dot().color(Color::Conflict)),
|
||||
state
|
||||
.read_with(cx, |state, cx| state.thread_status(cx))
|
||||
.map(|status| status.label())
|
||||
.unwrap_or("Stopped"),
|
||||
Color::Conflict,
|
||||
),
|
||||
_ => (
|
||||
Some(Indicator::dot().color(Color::Success)),
|
||||
state
|
||||
.read_with(cx, |state, cx| state.thread_status(cx))
|
||||
.map(|status| status.label())
|
||||
.unwrap_or("Running"),
|
||||
Color::Success,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let is_starting = matches!(self.mode, DebugSessionState::Starting(_));
|
||||
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.children(is_starting.then(|| {
|
||||
Icon::new(IconName::ArrowCircle).with_animation(
|
||||
"starting-debug-session",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|this, delta| this.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
}))
|
||||
.when_some(icon, |this, indicator| this.child(indicator))
|
||||
.justify_between()
|
||||
.child(Label::new(label).color(color))
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl FollowableItem for DebugSession {
|
||||
@@ -339,15 +193,6 @@ impl FollowableItem for DebugSession {
|
||||
impl Render for DebugSession {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
match &self.mode {
|
||||
DebugSessionState::Inert(inert_state) => {
|
||||
inert_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
|
||||
}
|
||||
DebugSessionState::Starting(starting_state) => {
|
||||
starting_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
|
||||
}
|
||||
DebugSessionState::Failed(failed_state) => {
|
||||
failed_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
|
||||
}
|
||||
DebugSessionState::Running(running_state) => {
|
||||
running_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
use gpui::{FocusHandle, Focusable};
|
||||
use ui::{
|
||||
Color, Context, IntoElement, Label, LabelCommon, ParentElement, Render, Styled, Window, h_flex,
|
||||
};
|
||||
|
||||
pub(crate) struct FailedState {
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
impl FailedState {
|
||||
pub(super) fn new(cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for FailedState {
|
||||
fn focus_handle(&self, _: &ui::App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
impl Render for FailedState {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.size_full()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(Label::new("Failed to spawn debugging session").color(Color::Error))
|
||||
}
|
||||
}
|
||||
@@ -1,336 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use dap::DebugRequestType;
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{App, AppContext, Entity, EventEmitter, FocusHandle, Focusable, TextStyle, WeakEntity};
|
||||
use settings::Settings as _;
|
||||
use task::{DebugTaskDefinition, LaunchConfig, TCPHost};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
ActiveTheme as _, ButtonCommon, ButtonLike, Clickable, Context, ContextMenu, Disableable,
|
||||
DropdownMenu, FluentBuilder, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label,
|
||||
LabelCommon, LabelSize, ParentElement, PopoverMenu, PopoverMenuHandle, Render, SharedString,
|
||||
SplitButton, Styled, Window, div, h_flex, relative, v_flex,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::attach_modal::AttachModal;
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
|
||||
enum SpawnMode {
|
||||
#[default]
|
||||
Launch,
|
||||
Attach,
|
||||
}
|
||||
|
||||
impl SpawnMode {
|
||||
fn label(&self) -> &'static str {
|
||||
match self {
|
||||
SpawnMode::Launch => "Launch",
|
||||
SpawnMode::Attach => "Attach",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DebugRequestType> for SpawnMode {
|
||||
fn from(request: DebugRequestType) -> Self {
|
||||
match request {
|
||||
DebugRequestType::Launch(_) => SpawnMode::Launch,
|
||||
DebugRequestType::Attach(_) => SpawnMode::Attach,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct InertState {
|
||||
focus_handle: FocusHandle,
|
||||
selected_debugger: Option<SharedString>,
|
||||
program_editor: Entity<Editor>,
|
||||
cwd_editor: Entity<Editor>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
spawn_mode: SpawnMode,
|
||||
popover_handle: PopoverMenuHandle<ContextMenu>,
|
||||
}
|
||||
|
||||
impl InertState {
|
||||
pub(super) fn new(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
default_cwd: &str,
|
||||
debug_config: Option<DebugTaskDefinition>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let selected_debugger = debug_config
|
||||
.as_ref()
|
||||
.map(|config| SharedString::from(config.adapter.clone()));
|
||||
|
||||
let spawn_mode = debug_config
|
||||
.as_ref()
|
||||
.map(|config| config.request.clone().into())
|
||||
.unwrap_or_default();
|
||||
|
||||
let program = debug_config
|
||||
.as_ref()
|
||||
.and_then(|config| match &config.request {
|
||||
DebugRequestType::Attach(_) => None,
|
||||
DebugRequestType::Launch(launch_config) => Some(launch_config.program.clone()),
|
||||
});
|
||||
|
||||
let program_editor = cx.new(|cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
if let Some(program) = program {
|
||||
editor.insert(&program, window, cx);
|
||||
} else {
|
||||
editor.set_placeholder_text("Program path", cx);
|
||||
}
|
||||
editor
|
||||
});
|
||||
|
||||
let cwd = debug_config
|
||||
.and_then(|config| match &config.request {
|
||||
DebugRequestType::Attach(_) => None,
|
||||
DebugRequestType::Launch(launch_config) => launch_config.cwd.clone(),
|
||||
})
|
||||
.unwrap_or_else(|| PathBuf::from(default_cwd));
|
||||
|
||||
let cwd_editor = cx.new(|cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
editor.insert(cwd.to_str().unwrap_or_else(|| default_cwd), window, cx);
|
||||
editor.set_placeholder_text("Working directory", cx);
|
||||
editor
|
||||
});
|
||||
|
||||
Self {
|
||||
workspace,
|
||||
cwd_editor,
|
||||
program_editor,
|
||||
selected_debugger,
|
||||
spawn_mode,
|
||||
focus_handle: cx.focus_handle(),
|
||||
popover_handle: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Focusable for InertState {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum InertEvent {
|
||||
Spawned { config: DebugTaskDefinition },
|
||||
}
|
||||
|
||||
impl EventEmitter<InertEvent> for InertState {}
|
||||
|
||||
static SELECT_DEBUGGER_LABEL: SharedString = SharedString::new_static("Select Debugger");
|
||||
|
||||
impl Render for InertState {
|
||||
fn render(
|
||||
&mut self,
|
||||
window: &mut ui::Window,
|
||||
cx: &mut ui::Context<'_, Self>,
|
||||
) -> impl ui::IntoElement {
|
||||
let weak = cx.weak_entity();
|
||||
let workspace = self.workspace.clone();
|
||||
let disable_buttons = self.selected_debugger.is_none();
|
||||
let spawn_button = ButtonLike::new_rounded_left("spawn-debug-session")
|
||||
.child(Label::new(self.spawn_mode.label()).size(LabelSize::Small))
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
if this.spawn_mode == SpawnMode::Launch {
|
||||
let program = this.program_editor.read(cx).text(cx);
|
||||
let cwd = PathBuf::from(this.cwd_editor.read(cx).text(cx));
|
||||
let kind = this
|
||||
.selected_debugger
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| {
|
||||
unimplemented!(
|
||||
"Automatic selection of a debugger based on users project"
|
||||
)
|
||||
})
|
||||
.to_string();
|
||||
|
||||
cx.emit(InertEvent::Spawned {
|
||||
config: DebugTaskDefinition {
|
||||
label: "hard coded".into(),
|
||||
adapter: kind,
|
||||
request: DebugRequestType::Launch(LaunchConfig {
|
||||
program,
|
||||
cwd: Some(cwd),
|
||||
}),
|
||||
tcp_connection: Some(TCPHost::default()),
|
||||
initialize_args: None,
|
||||
args: Default::default(),
|
||||
locator: None,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.attach(window, cx)
|
||||
}
|
||||
}))
|
||||
.disabled(disable_buttons);
|
||||
|
||||
v_flex()
|
||||
.track_focus(&self.focus_handle)
|
||||
.size_full()
|
||||
.gap_1()
|
||||
.p_2()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.child(Self::render_editor(&self.program_editor, cx))
|
||||
.child(
|
||||
h_flex().child(DropdownMenu::new(
|
||||
"dap-adapter-picker",
|
||||
self.selected_debugger
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| &SELECT_DEBUGGER_LABEL)
|
||||
.clone(),
|
||||
ContextMenu::build(window, cx, move |mut this, _, cx| {
|
||||
let setter_for_name = |name: SharedString| {
|
||||
let weak = weak.clone();
|
||||
move |_: &mut Window, cx: &mut App| {
|
||||
let name = name.clone();
|
||||
weak.update(cx, move |this, cx| {
|
||||
this.selected_debugger = Some(name.clone());
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
let available_adapters = workspace
|
||||
.update(cx, |this, cx| {
|
||||
this.project()
|
||||
.read(cx)
|
||||
.debug_adapters()
|
||||
.enumerate_adapters()
|
||||
})
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
|
||||
for adapter in available_adapters {
|
||||
this = this.entry(
|
||||
adapter.0.clone(),
|
||||
None,
|
||||
setter_for_name(adapter.0.clone()),
|
||||
);
|
||||
}
|
||||
this
|
||||
}),
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Self::render_editor(&self.cwd_editor, cx))
|
||||
.map(|this| {
|
||||
let entity = cx.weak_entity();
|
||||
this.child(SplitButton {
|
||||
left: spawn_button,
|
||||
right: PopoverMenu::new("debugger-select-spawn-mode")
|
||||
.trigger(
|
||||
ButtonLike::new_rounded_right(
|
||||
"debugger-spawn-button-mode",
|
||||
)
|
||||
.layer(ui::ElevationIndex::ModalSurface)
|
||||
.size(ui::ButtonSize::None)
|
||||
.child(
|
||||
div().px_1().child(
|
||||
Icon::new(IconName::ChevronDownSmall)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
),
|
||||
)
|
||||
.menu(move |window, cx| {
|
||||
Some(ContextMenu::build(window, cx, {
|
||||
let entity = entity.clone();
|
||||
move |this, _, _| {
|
||||
this.entry("Launch", None, {
|
||||
let entity = entity.clone();
|
||||
move |_, cx| {
|
||||
let _ =
|
||||
entity.update(cx, |this, cx| {
|
||||
this.spawn_mode =
|
||||
SpawnMode::Launch;
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
})
|
||||
.entry("Attach", None, {
|
||||
let entity = entity.clone();
|
||||
move |_, cx| {
|
||||
let _ =
|
||||
entity.update(cx, |this, cx| {
|
||||
this.spawn_mode =
|
||||
SpawnMode::Attach;
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}))
|
||||
})
|
||||
.with_handle(self.popover_handle.clone())
|
||||
.into_any_element(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl InertState {
|
||||
fn render_editor(editor: &Entity<Editor>, cx: &Context<Self>) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_features: settings.buffer_font.features.clone(),
|
||||
font_size: settings.buffer_font_size(cx).into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
EditorElement::new(
|
||||
editor,
|
||||
EditorStyle {
|
||||
background: cx.theme().colors().editor_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn attach(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let kind = self
|
||||
.selected_debugger
|
||||
.as_deref()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
unimplemented!("Automatic selection of a debugger based on users project")
|
||||
});
|
||||
|
||||
let config = DebugTaskDefinition {
|
||||
label: "hard coded attach".into(),
|
||||
adapter: kind,
|
||||
request: DebugRequestType::Attach(task::AttachConfig { process_id: None }),
|
||||
initialize_args: None,
|
||||
args: Default::default(),
|
||||
locator: None,
|
||||
tcp_connection: Some(TCPHost::default()),
|
||||
};
|
||||
|
||||
let _ = self.workspace.update(cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
AttachModal::new(project, config, window, cx)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,366 +4,253 @@ mod module_list;
|
||||
pub mod stack_frame_list;
|
||||
pub mod variable_list;
|
||||
|
||||
use super::{DebugPanelItemEvent, ThreadItem};
|
||||
use std::{any::Any, ops::ControlFlow, sync::Arc};
|
||||
|
||||
use super::DebugPanelItemEvent;
|
||||
use collections::HashMap;
|
||||
use console::Console;
|
||||
use dap::{Capabilities, Thread, client::SessionId, debugger_settings::DebuggerSettings};
|
||||
use gpui::{AppContext, Entity, EventEmitter, FocusHandle, Focusable, Subscription, WeakEntity};
|
||||
use gpui::{
|
||||
Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
|
||||
NoAction, Subscription, WeakEntity,
|
||||
};
|
||||
use loaded_source_list::LoadedSourceList;
|
||||
use module_list::ModuleList;
|
||||
use project::debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus};
|
||||
use project::{
|
||||
Project,
|
||||
debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus},
|
||||
};
|
||||
use rpc::proto::ViewId;
|
||||
use settings::Settings;
|
||||
use stack_frame_list::StackFrameList;
|
||||
use ui::{
|
||||
ActiveTheme, AnyElement, App, Button, ButtonCommon, Clickable, Context, ContextMenu,
|
||||
Disableable, Divider, DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, Indicator,
|
||||
InteractiveElement, IntoElement, Label, ParentElement, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Tooltip, Window, div, h_flex, v_flex,
|
||||
App, Context, ContextMenu, DropdownMenu, InteractiveElement, IntoElement, ParentElement,
|
||||
Render, SharedString, Styled, Window, div, h_flex, v_flex,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use variable_list::VariableList;
|
||||
use workspace::Workspace;
|
||||
use workspace::{
|
||||
ActivePaneDecorator, DraggedTab, Item, Pane, PaneGroup, Workspace, move_item, pane::Event,
|
||||
};
|
||||
|
||||
pub struct RunningState {
|
||||
session: Entity<Session>,
|
||||
thread_id: Option<ThreadId>,
|
||||
console: Entity<console::Console>,
|
||||
focus_handle: FocusHandle,
|
||||
_remote_id: Option<ViewId>,
|
||||
show_console_indicator: bool,
|
||||
module_list: Entity<module_list::ModuleList>,
|
||||
active_thread_item: ThreadItem,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
session_id: SessionId,
|
||||
variable_list: Entity<variable_list::VariableList>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
stack_frame_list: Entity<stack_frame_list::StackFrameList>,
|
||||
loaded_source_list: Entity<loaded_source_list::LoadedSourceList>,
|
||||
_module_list: Entity<module_list::ModuleList>,
|
||||
_console: Entity<Console>,
|
||||
panes: PaneGroup,
|
||||
pane_close_subscriptions: HashMap<EntityId, Subscription>,
|
||||
}
|
||||
|
||||
impl Render for RunningState {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let threads = self.session.update(cx, |this, cx| this.threads(cx));
|
||||
self.select_current_thread(&threads, cx);
|
||||
|
||||
let active = self.panes.panes().into_iter().next();
|
||||
let x = if let Some(active) = active {
|
||||
self.panes
|
||||
.render(
|
||||
None,
|
||||
&ActivePaneDecorator::new(active, &self.workspace),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
div().into_any_element()
|
||||
};
|
||||
let thread_status = self
|
||||
.thread_id
|
||||
.map(|thread_id| self.session.read(cx).thread_status(thread_id))
|
||||
.unwrap_or(ThreadStatus::Exited);
|
||||
|
||||
let selected_thread_name = threads
|
||||
.iter()
|
||||
.find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
|
||||
.map(|(thread, _)| thread.name.clone())
|
||||
.unwrap_or("Threads".to_owned());
|
||||
|
||||
self.variable_list.update(cx, |this, cx| {
|
||||
this.disabled(thread_status != ThreadStatus::Stopped, cx);
|
||||
});
|
||||
|
||||
let active_thread_item = &self.active_thread_item;
|
||||
|
||||
let has_no_threads = threads.is_empty();
|
||||
let capabilities = self.capabilities(cx);
|
||||
let state = cx.entity();
|
||||
h_flex()
|
||||
.key_context("DebugPanelItem")
|
||||
.track_focus(&self.focus_handle(cx))
|
||||
v_flex()
|
||||
.size_full()
|
||||
.items_start()
|
||||
.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.items_start()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.px_1()
|
||||
.py_0p5()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.map(|this| {
|
||||
if thread_status == ThreadStatus::Running {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
"debug-pause",
|
||||
IconName::DebugPause,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.pause_thread(cx);
|
||||
}))
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Pause program")(window, cx)
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
"debug-continue",
|
||||
IconName::DebugContinue,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.continue_thread(cx)
|
||||
}))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Continue program")(window, cx)
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
.child(
|
||||
IconButton::new("debug-restart", IconName::DebugRestart)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.restart_session(cx);
|
||||
}))
|
||||
.disabled(
|
||||
!capabilities
|
||||
.supports_restart_request
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Restart")(window, cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-stop", IconName::DebugStop)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.stop_thread(cx);
|
||||
}))
|
||||
.disabled(
|
||||
thread_status != ThreadStatus::Stopped
|
||||
&& thread_status != ThreadStatus::Running,
|
||||
)
|
||||
.tooltip({
|
||||
let label = if capabilities
|
||||
.supports_terminate_threads_request
|
||||
.unwrap_or_default()
|
||||
{
|
||||
"Terminate Thread"
|
||||
} else {
|
||||
"Terminate all Threads"
|
||||
};
|
||||
move |window, cx| Tooltip::text(label)(window, cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new(
|
||||
"debug-disconnect",
|
||||
IconName::DebugDisconnect,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.disconnect_client(cx);
|
||||
}))
|
||||
.disabled(
|
||||
thread_status == ThreadStatus::Exited
|
||||
|| thread_status == ThreadStatus::Ended,
|
||||
)
|
||||
.tooltip(Tooltip::text("Disconnect")),
|
||||
)
|
||||
.child(Divider::vertical())
|
||||
.when(
|
||||
capabilities.supports_step_back.unwrap_or(false),
|
||||
|this| {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
"debug-step-back",
|
||||
IconName::DebugStepBack,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.step_back(cx);
|
||||
}))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Step back")(window, cx)
|
||||
}),
|
||||
)
|
||||
},
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-step-over", IconName::DebugStepOver)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.step_over(cx);
|
||||
}))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Step over")(window, cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-step-in", IconName::DebugStepInto)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.step_in(cx);
|
||||
}))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Step in")(window, cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-step-out", IconName::DebugStepOut)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.step_out(cx);
|
||||
}))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::text("Step out")(window, cx)
|
||||
}),
|
||||
)
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
IconButton::new(
|
||||
"debug-ignore-breakpoints",
|
||||
if self.session.read(cx).breakpoints_enabled() {
|
||||
IconName::DebugBreakpoint
|
||||
} else {
|
||||
IconName::DebugIgnoreBreakpoints
|
||||
},
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.toggle_ignore_breakpoints(cx);
|
||||
}))
|
||||
.disabled(
|
||||
thread_status == ThreadStatus::Exited
|
||||
|| thread_status == ThreadStatus::Ended,
|
||||
)
|
||||
.tooltip(
|
||||
move |window, cx| {
|
||||
Tooltip::text("Ignore breakpoints")(window, cx)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.px_1()
|
||||
.py_0p5()
|
||||
.gap_2()
|
||||
.w_3_4()
|
||||
.justify_end()
|
||||
.child(Label::new("Thread:"))
|
||||
.child(
|
||||
DropdownMenu::new(
|
||||
("thread-list", self.session_id.0),
|
||||
selected_thread_name,
|
||||
ContextMenu::build(
|
||||
window,
|
||||
cx,
|
||||
move |mut this, _, _| {
|
||||
for (thread, _) in threads {
|
||||
let state = state.clone();
|
||||
let thread_id = thread.id;
|
||||
this = this.entry(
|
||||
thread.name,
|
||||
None,
|
||||
move |_, cx| {
|
||||
state.update(cx, |state, cx| {
|
||||
state.select_thread(
|
||||
ThreadId(thread_id),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
this
|
||||
},
|
||||
),
|
||||
)
|
||||
.disabled(
|
||||
has_no_threads
|
||||
|| thread_status != ThreadStatus::Stopped,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.size_full()
|
||||
.items_start()
|
||||
.p_1()
|
||||
.gap_4()
|
||||
.child(self.stack_frame_list.clone()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.size_full()
|
||||
.items_start()
|
||||
.child(
|
||||
h_flex()
|
||||
.border_b_1()
|
||||
.w_full()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(self.render_entry_button(
|
||||
&SharedString::from("Variables"),
|
||||
ThreadItem::Variables,
|
||||
cx,
|
||||
))
|
||||
.when(
|
||||
capabilities.supports_modules_request.unwrap_or_default(),
|
||||
|this| {
|
||||
this.child(self.render_entry_button(
|
||||
&SharedString::from("Modules"),
|
||||
ThreadItem::Modules,
|
||||
cx,
|
||||
))
|
||||
},
|
||||
)
|
||||
.when(
|
||||
capabilities
|
||||
.supports_loaded_sources_request
|
||||
.unwrap_or_default(),
|
||||
|this| {
|
||||
this.child(self.render_entry_button(
|
||||
&SharedString::from("Loaded Sources"),
|
||||
ThreadItem::LoadedSource,
|
||||
cx,
|
||||
))
|
||||
},
|
||||
)
|
||||
.child(self.render_entry_button(
|
||||
&SharedString::from("Console"),
|
||||
ThreadItem::Console,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.when(*active_thread_item == ThreadItem::Variables, |this| {
|
||||
this.child(self.variable_list.clone())
|
||||
})
|
||||
.when(*active_thread_item == ThreadItem::Modules, |this| {
|
||||
this.size_full().child(self.module_list.clone())
|
||||
})
|
||||
.when(*active_thread_item == ThreadItem::LoadedSource, |this| {
|
||||
this.size_full().child(self.loaded_source_list.clone())
|
||||
})
|
||||
.when(*active_thread_item == ThreadItem::Console, |this| {
|
||||
this.child(self.console.clone())
|
||||
}),
|
||||
)
|
||||
.key_context("DebugSessionItem")
|
||||
.track_focus(&self.focus_handle(cx))
|
||||
.child(h_flex().flex_1().child(x))
|
||||
}
|
||||
}
|
||||
|
||||
struct SubView {
|
||||
inner: AnyView,
|
||||
pane_focus_handle: FocusHandle,
|
||||
tab_name: SharedString,
|
||||
}
|
||||
|
||||
impl SubView {
|
||||
fn new(
|
||||
pane_focus_handle: FocusHandle,
|
||||
view: AnyView,
|
||||
tab_name: SharedString,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
cx.new(|_| Self {
|
||||
tab_name,
|
||||
inner: view,
|
||||
pane_focus_handle,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Focusable for SubView {
|
||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||
self.pane_focus_handle.clone()
|
||||
}
|
||||
}
|
||||
impl EventEmitter<()> for SubView {}
|
||||
impl Item for SubView {
|
||||
type Event = ();
|
||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
||||
Some(self.tab_name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for SubView {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex().size_full().child(self.inner.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn new_debugger_pane(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<RunningState>,
|
||||
) -> Entity<Pane> {
|
||||
let weak_running = cx.weak_entity();
|
||||
let custom_drop_handle = {
|
||||
let workspace = workspace.clone();
|
||||
let project = project.downgrade();
|
||||
let weak_running = weak_running.clone();
|
||||
move |pane: &mut Pane, any: &dyn Any, window: &mut Window, cx: &mut Context<Pane>| {
|
||||
let Some(tab) = any.downcast_ref::<DraggedTab>() else {
|
||||
return ControlFlow::Break(());
|
||||
};
|
||||
let Some(project) = project.upgrade() else {
|
||||
return ControlFlow::Break(());
|
||||
};
|
||||
let this_pane = cx.entity().clone();
|
||||
let item = if tab.pane == this_pane {
|
||||
pane.item_for_index(tab.ix)
|
||||
} else {
|
||||
tab.pane.read(cx).item_for_index(tab.ix)
|
||||
};
|
||||
let Some(item) = item.filter(|item| item.downcast::<SubView>().is_some()) else {
|
||||
return ControlFlow::Break(());
|
||||
};
|
||||
|
||||
let source = tab.pane.clone();
|
||||
let item_id_to_move = item.item_id();
|
||||
|
||||
let Ok(new_split_pane) = pane
|
||||
.drag_split_direction()
|
||||
.map(|split_direction| {
|
||||
weak_running.update(cx, |running, cx| {
|
||||
let new_pane =
|
||||
new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
let _previous_subscription = running.pane_close_subscriptions.insert(
|
||||
new_pane.entity_id(),
|
||||
cx.subscribe(&new_pane, RunningState::handle_pane_event),
|
||||
);
|
||||
debug_assert!(_previous_subscription.is_none());
|
||||
running
|
||||
.panes
|
||||
.split(&this_pane, &new_pane, split_direction)?;
|
||||
anyhow::Ok(new_pane)
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
else {
|
||||
return ControlFlow::Break(());
|
||||
};
|
||||
|
||||
match new_split_pane.transpose() {
|
||||
// Source pane may be the one currently updated, so defer the move.
|
||||
Ok(Some(new_pane)) => cx
|
||||
.spawn_in(window, async move |_, cx| {
|
||||
cx.update(|window, cx| {
|
||||
move_item(
|
||||
&source,
|
||||
&new_pane,
|
||||
item_id_to_move,
|
||||
new_pane.read(cx).active_item_index(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach(),
|
||||
// If we drop into existing pane or current pane,
|
||||
// regular pane drop handler will take care of it,
|
||||
// using the right tab index for the operation.
|
||||
Ok(None) => return ControlFlow::Continue(()),
|
||||
err @ Err(_) => {
|
||||
err.log_err();
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
};
|
||||
|
||||
ControlFlow::Break(())
|
||||
}
|
||||
};
|
||||
|
||||
let ret = cx.new(move |cx| {
|
||||
let mut pane = Pane::new(
|
||||
workspace.clone(),
|
||||
project.clone(),
|
||||
Default::default(),
|
||||
None,
|
||||
NoAction.boxed_clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
pane.set_can_split(Some(Arc::new(move |pane, dragged_item, _window, cx| {
|
||||
if let Some(tab) = dragged_item.downcast_ref::<DraggedTab>() {
|
||||
let is_current_pane = tab.pane == cx.entity();
|
||||
let Some(can_drag_away) = weak_running
|
||||
.update(cx, |running_state, _| {
|
||||
let current_panes = running_state.panes.panes();
|
||||
!current_panes.contains(&&tab.pane)
|
||||
|| current_panes.len() > 1
|
||||
|| (!is_current_pane || pane.items_len() > 1)
|
||||
})
|
||||
.ok()
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if can_drag_away {
|
||||
let item = if is_current_pane {
|
||||
pane.item_for_index(tab.ix)
|
||||
} else {
|
||||
tab.pane.read(cx).item_for_index(tab.ix)
|
||||
};
|
||||
if let Some(item) = item {
|
||||
return item.downcast::<SubView>().is_some();
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
})));
|
||||
pane.display_nav_history_buttons(None);
|
||||
pane.set_custom_drop_handle(cx, custom_drop_handle);
|
||||
pane
|
||||
});
|
||||
|
||||
ret
|
||||
}
|
||||
impl RunningState {
|
||||
pub fn new(
|
||||
session: Entity<Session>,
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -380,6 +267,7 @@ impl RunningState {
|
||||
|
||||
let module_list = cx.new(|cx| ModuleList::new(session.clone(), workspace.clone(), cx));
|
||||
|
||||
#[expect(unused)]
|
||||
let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), cx));
|
||||
|
||||
let console = cx.new(|cx| {
|
||||
@@ -417,24 +305,116 @@ impl RunningState {
|
||||
}),
|
||||
];
|
||||
|
||||
let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
leftmost_pane.update(cx, |this, cx| {
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
this.focus_handle(cx),
|
||||
stack_frame_list.clone().into(),
|
||||
SharedString::new_static("Frames"),
|
||||
cx,
|
||||
)),
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
center_pane.update(cx, |this, cx| {
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
variable_list.focus_handle(cx),
|
||||
variable_list.clone().into(),
|
||||
SharedString::new_static("Variables"),
|
||||
cx,
|
||||
)),
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
this.focus_handle(cx),
|
||||
module_list.clone().into(),
|
||||
SharedString::new_static("Modules"),
|
||||
cx,
|
||||
)),
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
rightmost_pane.update(cx, |this, cx| {
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
this.focus_handle(cx),
|
||||
console.clone().into(),
|
||||
SharedString::new_static("Console"),
|
||||
cx,
|
||||
)),
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let pane_close_subscriptions = HashMap::from_iter(
|
||||
[&leftmost_pane, ¢er_pane, &rightmost_pane]
|
||||
.into_iter()
|
||||
.map(|entity| {
|
||||
(
|
||||
entity.entity_id(),
|
||||
cx.subscribe(entity, Self::handle_pane_event),
|
||||
)
|
||||
}),
|
||||
);
|
||||
let group_root = workspace::PaneAxis::new(
|
||||
gpui::Axis::Horizontal,
|
||||
[leftmost_pane, center_pane, rightmost_pane]
|
||||
.into_iter()
|
||||
.map(workspace::Member::Pane)
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let panes = PaneGroup::with_root(workspace::Member::Axis(group_root));
|
||||
|
||||
Self {
|
||||
session,
|
||||
console,
|
||||
workspace,
|
||||
module_list,
|
||||
focus_handle,
|
||||
variable_list,
|
||||
_subscriptions,
|
||||
thread_id: None,
|
||||
_remote_id: None,
|
||||
stack_frame_list,
|
||||
loaded_source_list,
|
||||
session_id,
|
||||
show_console_indicator: false,
|
||||
active_thread_item: ThreadItem::Variables,
|
||||
panes,
|
||||
_module_list: module_list,
|
||||
_console: console,
|
||||
pane_close_subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_pane_event(
|
||||
this: &mut RunningState,
|
||||
source_pane: Entity<Pane>,
|
||||
event: &Event,
|
||||
cx: &mut Context<RunningState>,
|
||||
) {
|
||||
if let Event::Remove { .. } = event {
|
||||
let _did_find_pane = this.panes.remove(&source_pane).is_ok();
|
||||
debug_assert!(_did_find_pane);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) {
|
||||
if self.thread_id.is_some() {
|
||||
self.stack_frame_list
|
||||
@@ -450,37 +430,43 @@ impl RunningState {
|
||||
self.session_id
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn set_thread_item(&mut self, thread_item: ThreadItem, cx: &mut Context<Self>) {
|
||||
self.active_thread_item = thread_item;
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[cfg(test)]
|
||||
pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
|
||||
&self.stack_frame_list
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[cfg(test)]
|
||||
pub fn console(&self) -> &Entity<Console> {
|
||||
&self.console
|
||||
&self._console
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn module_list(&self) -> &Entity<ModuleList> {
|
||||
&self.module_list
|
||||
#[cfg(test)]
|
||||
pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
|
||||
&self._module_list
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn variable_list(&self) -> &Entity<VariableList> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn activate_variable_list(&self, window: &mut Window, cx: &mut App) {
|
||||
let (variable_list_position, pane) = self
|
||||
.panes
|
||||
.panes()
|
||||
.into_iter()
|
||||
.find_map(|pane| {
|
||||
pane.read(cx)
|
||||
.items_of_type::<SubView>()
|
||||
.position(|view| view.read(cx).tab_name == *"Variables")
|
||||
.map(|view| (view, pane))
|
||||
})
|
||||
.unwrap();
|
||||
pane.update(cx, |this, cx| {
|
||||
this.activate_item(variable_list_position, true, true, window, cx);
|
||||
})
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
|
||||
&self.variable_list
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn are_breakpoints_ignored(&self, cx: &App) -> bool {
|
||||
self.session.read(cx).ignore_breakpoints()
|
||||
}
|
||||
|
||||
pub fn capabilities(&self, cx: &App) -> Capabilities {
|
||||
self.session().read(cx).capabilities().clone()
|
||||
}
|
||||
@@ -504,8 +490,8 @@ impl RunningState {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn selected_thread_id(&self) -> Option<ThreadId> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
|
||||
self.thread_id
|
||||
}
|
||||
|
||||
@@ -526,41 +512,6 @@ impl RunningState {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_entry_button(
|
||||
&self,
|
||||
label: &SharedString,
|
||||
thread_item: ThreadItem,
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
let has_indicator =
|
||||
matches!(thread_item, ThreadItem::Console) && self.show_console_indicator;
|
||||
|
||||
div()
|
||||
.id(label.clone())
|
||||
.px_2()
|
||||
.py_1()
|
||||
.cursor_pointer()
|
||||
.border_b_2()
|
||||
.when(self.active_thread_item == thread_item, |this| {
|
||||
this.border_color(cx.theme().colors().border)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.child(Button::new(label.clone(), label.clone()))
|
||||
.when(has_indicator, |this| this.child(Indicator::dot())),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, _window, cx| {
|
||||
this.active_thread_item = thread_item;
|
||||
|
||||
if matches!(this.active_thread_item, ThreadItem::Console) {
|
||||
this.show_console_indicator = false;
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(thread_id) = self.thread_id else {
|
||||
return;
|
||||
@@ -583,7 +534,7 @@ impl RunningState {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn step_in(&mut self, cx: &mut Context<Self>) {
|
||||
pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(thread_id) = self.thread_id else {
|
||||
return;
|
||||
};
|
||||
@@ -595,7 +546,7 @@ impl RunningState {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn step_out(&mut self, cx: &mut Context<Self>) {
|
||||
pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(thread_id) = self.thread_id else {
|
||||
return;
|
||||
};
|
||||
@@ -607,7 +558,7 @@ impl RunningState {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn step_back(&mut self, cx: &mut Context<Self>) {
|
||||
pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(thread_id) = self.thread_id else {
|
||||
return;
|
||||
};
|
||||
@@ -675,6 +626,10 @@ impl RunningState {
|
||||
});
|
||||
}
|
||||
|
||||
#[expect(
|
||||
unused,
|
||||
reason = "Support for disconnecting a client is not wired through yet"
|
||||
)]
|
||||
pub fn disconnect_client(&self, cx: &mut Context<Self>) {
|
||||
self.session().update(cx, |state, cx| {
|
||||
state.disconnect_client(cx);
|
||||
@@ -686,6 +641,36 @@ impl RunningState {
|
||||
session.toggle_ignore_breakpoints(cx).detach();
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn thread_dropdown(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, RunningState>,
|
||||
) -> DropdownMenu {
|
||||
let state = cx.entity();
|
||||
let threads = self.session.update(cx, |this, cx| this.threads(cx));
|
||||
let selected_thread_name = threads
|
||||
.iter()
|
||||
.find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
|
||||
.map(|(thread, _)| thread.name.clone())
|
||||
.unwrap_or("Threads".to_owned());
|
||||
DropdownMenu::new(
|
||||
("thread-list", self.session_id.0),
|
||||
selected_thread_name,
|
||||
ContextMenu::build(window, cx, move |mut this, _, _| {
|
||||
for (thread, _) in threads {
|
||||
let state = state.clone();
|
||||
let thread_id = thread.id;
|
||||
this = this.entry(thread.name, None, move |_, cx| {
|
||||
state.update(cx, |state, cx| {
|
||||
state.select_thread(ThreadId(thread_id), cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
this
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DebugPanelItemEvent> for RunningState {}
|
||||
|
||||
@@ -85,16 +85,11 @@ impl Console {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn editor(&self) -> &Entity<Editor> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn editor(&self) -> &Entity<Editor> {
|
||||
&self.console
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn query_bar(&self) -> &Entity<Editor> {
|
||||
&self.query_bar
|
||||
}
|
||||
|
||||
fn is_local(&self, cx: &Context<Self>) -> bool {
|
||||
self.session.read(cx).is_local()
|
||||
}
|
||||
@@ -372,6 +367,7 @@ impl ConsoleQueryBarCompletionProvider {
|
||||
documentation: None,
|
||||
confirm: None,
|
||||
source: project::CompletionSource::Custom,
|
||||
insert_text_mode: None,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
@@ -414,6 +410,7 @@ impl ConsoleQueryBarCompletionProvider {
|
||||
documentation: None,
|
||||
confirm: None,
|
||||
source: project::CompletionSource::Custom,
|
||||
insert_text_mode: None,
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
|
||||
@@ -147,11 +147,9 @@ impl ModuleList {
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl ModuleList {
|
||||
pub fn modules(&self, cx: &mut Context<Self>) -> Vec<dap::Module> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn modules(&self, cx: &mut Context<Self>) -> Vec<dap::Module> {
|
||||
self.session
|
||||
.update(cx, |session, cx| session.modules(cx).to_vec())
|
||||
}
|
||||
|
||||
@@ -87,13 +87,13 @@ impl StackFrameList {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn entries(&self) -> &Vec<StackFrameEntry> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn entries(&self) -> &Vec<StackFrameEntry> {
|
||||
&self.entries
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn flatten_entries(&self) -> Vec<dap::StackFrame> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn flatten_entries(&self) -> Vec<dap::StackFrame> {
|
||||
self.entries
|
||||
.iter()
|
||||
.flat_map(|frame| match frame {
|
||||
@@ -115,8 +115,8 @@ impl StackFrameList {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn dap_stack_frames(&self, cx: &mut App) -> Vec<dap::StackFrame> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn dap_stack_frames(&self, cx: &mut App) -> Vec<dap::StackFrame> {
|
||||
self.stack_frames(cx)
|
||||
.into_iter()
|
||||
.map(|stack_frame| stack_frame.dap.clone())
|
||||
|
||||
@@ -540,8 +540,8 @@ impl VariableList {
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn assert_visual_entries(&self, expected: Vec<&str>) {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn assert_visual_entries(&self, expected: Vec<&str>) {
|
||||
const INDENT: &'static str = " ";
|
||||
|
||||
let entries = &self.entries;
|
||||
@@ -569,8 +569,8 @@ impl VariableList {
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn scopes(&self) -> Vec<dap::Scope> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn scopes(&self) -> Vec<dap::Scope> {
|
||||
self.entries
|
||||
.iter()
|
||||
.filter_map(|entry| match &entry.dap_kind {
|
||||
@@ -582,8 +582,8 @@ impl VariableList {
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn variables_per_scope(&self) -> Vec<(dap::Scope, Vec<dap::Variable>)> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn variables_per_scope(&self) -> Vec<(dap::Scope, Vec<dap::Variable>)> {
|
||||
let mut scopes: Vec<(dap::Scope, Vec<_>)> = Vec::new();
|
||||
let mut idx = 0;
|
||||
|
||||
@@ -604,8 +604,8 @@ impl VariableList {
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn variables(&self) -> Vec<dap::Variable> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn variables(&self) -> Vec<dap::Variable> {
|
||||
self.entries
|
||||
.iter()
|
||||
.filter_map(|entry| match &entry.dap_kind {
|
||||
@@ -687,6 +687,7 @@ impl VariableList {
|
||||
.child(
|
||||
ListItem::new(SharedString::from(format!("scope-{}", var_ref)))
|
||||
.selectable(false)
|
||||
.disabled(self.disabled)
|
||||
.indent_level(state.depth + 1)
|
||||
.indent_step_size(px(20.))
|
||||
.always_show_disclosure_icon(true)
|
||||
@@ -695,7 +696,15 @@ impl VariableList {
|
||||
let var_path = entry.path.clone();
|
||||
cx.listener(move |this, _, _, cx| this.toggle_entry(&var_path, cx))
|
||||
})
|
||||
.child(div().text_ui(cx).w_full().child(scope.name.clone())),
|
||||
.child(
|
||||
div()
|
||||
.text_ui(cx)
|
||||
.w_full()
|
||||
.when(self.disabled, |this| {
|
||||
this.text_color(Color::Disabled.color(cx))
|
||||
})
|
||||
.child(scope.name.clone()),
|
||||
),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
@@ -716,20 +725,27 @@ impl VariableList {
|
||||
};
|
||||
|
||||
let syntax_color_for = |name| cx.theme().syntax().get(name).color;
|
||||
let variable_name_color = match &dap
|
||||
.presentation_hint
|
||||
.as_ref()
|
||||
.and_then(|hint| hint.kind.as_ref())
|
||||
.unwrap_or(&VariablePresentationHintKind::Unknown)
|
||||
{
|
||||
VariablePresentationHintKind::Class
|
||||
| VariablePresentationHintKind::BaseClass
|
||||
| VariablePresentationHintKind::InnerClass
|
||||
| VariablePresentationHintKind::MostDerivedClass => syntax_color_for("type"),
|
||||
VariablePresentationHintKind::Data => syntax_color_for("variable"),
|
||||
VariablePresentationHintKind::Unknown | _ => syntax_color_for("variable"),
|
||||
let variable_name_color = if self.disabled {
|
||||
Some(Color::Disabled.color(cx))
|
||||
} else {
|
||||
match &dap
|
||||
.presentation_hint
|
||||
.as_ref()
|
||||
.and_then(|hint| hint.kind.as_ref())
|
||||
.unwrap_or(&VariablePresentationHintKind::Unknown)
|
||||
{
|
||||
VariablePresentationHintKind::Class
|
||||
| VariablePresentationHintKind::BaseClass
|
||||
| VariablePresentationHintKind::InnerClass
|
||||
| VariablePresentationHintKind::MostDerivedClass => syntax_color_for("type"),
|
||||
VariablePresentationHintKind::Data => syntax_color_for("variable"),
|
||||
VariablePresentationHintKind::Unknown | _ => syntax_color_for("variable"),
|
||||
}
|
||||
};
|
||||
let variable_color = syntax_color_for("variable.special");
|
||||
let variable_color = self
|
||||
.disabled
|
||||
.then(|| Color::Disabled.color(cx))
|
||||
.or_else(|| syntax_color_for("variable.special"));
|
||||
|
||||
let var_ref = dap.variables_reference;
|
||||
let colors = get_entry_color(cx);
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use dap::client::SessionId;
|
||||
use gpui::{
|
||||
Animation, AnimationExt, Entity, EventEmitter, FocusHandle, Focusable, Task, Transformation,
|
||||
percentage,
|
||||
};
|
||||
use project::debugger::session::Session;
|
||||
use ui::{Color, Context, Icon, IconName, IntoElement, ParentElement, Render, Styled, v_flex};
|
||||
|
||||
pub(crate) struct StartingState {
|
||||
focus_handle: FocusHandle,
|
||||
pub(super) session_id: SessionId,
|
||||
_notify_parent: Task<()>,
|
||||
}
|
||||
|
||||
pub(crate) enum StartingEvent {
|
||||
Failed,
|
||||
Finished(Entity<Session>),
|
||||
}
|
||||
|
||||
impl EventEmitter<StartingEvent> for StartingState {}
|
||||
|
||||
impl StartingState {
|
||||
pub(crate) fn new(
|
||||
session_id: SessionId,
|
||||
task: Task<Result<Entity<Session>>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let _notify_parent = cx.spawn(async move |this, cx| {
|
||||
let entity = task.await;
|
||||
|
||||
this.update(cx, |_, cx| {
|
||||
if let Ok(entity) = entity {
|
||||
cx.emit(StartingEvent::Finished(entity))
|
||||
} else {
|
||||
cx.emit(StartingEvent::Failed)
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
Self {
|
||||
session_id,
|
||||
focus_handle: cx.focus_handle(),
|
||||
_notify_parent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for StartingState {
|
||||
fn focus_handle(&self, _: &ui::App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for StartingState {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut ui::Window,
|
||||
_cx: &mut ui::Context<'_, Self>,
|
||||
) -> impl ui::IntoElement {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.child("Starting a debug adapter")
|
||||
.child(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ pub fn active_debug_session_panel(
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
debug_panel
|
||||
.update(cx, |this, cx| this.active_session(cx))
|
||||
.update(cx, |this, _| this.active_session())
|
||||
.unwrap()
|
||||
})
|
||||
.unwrap()
|
||||
|
||||
@@ -90,7 +90,7 @@ async fn test_show_attach_modal_and_select_process(
|
||||
initialize_args: None,
|
||||
tcp_connection: Some(TCPHost::default()),
|
||||
locator: None,
|
||||
args: Default::default(),
|
||||
stop_on_entry: None,
|
||||
},
|
||||
vec![
|
||||
Candidate {
|
||||
@@ -109,6 +109,7 @@ async fn test_show_attach_modal_and_select_process(
|
||||
command: vec![],
|
||||
},
|
||||
],
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -101,10 +101,6 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
|
||||
.clone()
|
||||
});
|
||||
|
||||
running_state.update(cx, |state, cx| {
|
||||
state.set_thread_item(session::ThreadItem::Console, cx);
|
||||
cx.refresh_windows();
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
// assert we have output from before the thread stopped
|
||||
@@ -112,7 +108,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
let active_debug_session_panel = debug_panel
|
||||
.update(cx, |this, cx| this.active_session(cx))
|
||||
.update(cx, |this, _| this.active_session())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
@@ -151,8 +147,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
running_state.update(cx, |state, cx| {
|
||||
state.set_thread_item(session::ThreadItem::Console, cx);
|
||||
running_state.update(cx, |_, cx| {
|
||||
cx.refresh_windows();
|
||||
});
|
||||
cx.run_until_parked();
|
||||
@@ -162,7 +157,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
let active_session_panel = debug_panel
|
||||
.update(cx, |this, cx| this.active_session(cx))
|
||||
.update(cx, |this, _| this.active_session())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -85,9 +85,8 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
||||
workspace
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
let active_session = debug_panel.update(cx, |debug_panel, cx| {
|
||||
debug_panel.active_session(cx).unwrap()
|
||||
});
|
||||
let active_session =
|
||||
debug_panel.update(cx, |debug_panel, _| debug_panel.active_session().unwrap());
|
||||
|
||||
let running_state = active_session.update(cx, |active_session, _| {
|
||||
active_session
|
||||
@@ -98,9 +97,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
||||
});
|
||||
|
||||
debug_panel.update(cx, |this, cx| {
|
||||
assert!(this.active_session(cx).is_some());
|
||||
// we have one active session and one inert item
|
||||
assert_eq!(2, this.pane().unwrap().read(cx).items_len());
|
||||
assert!(this.active_session().is_some());
|
||||
assert!(running_state.read(cx).selected_thread_id().is_none());
|
||||
});
|
||||
})
|
||||
@@ -124,7 +121,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
let active_session = debug_panel
|
||||
.update(cx, |this, cx| this.active_session(cx))
|
||||
.update(cx, |this, _| this.active_session())
|
||||
.unwrap();
|
||||
|
||||
let running_state = active_session.update(cx, |active_session, _| {
|
||||
@@ -135,11 +132,6 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
||||
.clone()
|
||||
});
|
||||
|
||||
// we have one active session and one inert item
|
||||
assert_eq!(
|
||||
2,
|
||||
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
|
||||
);
|
||||
assert_eq!(client.id(), running_state.read(cx).session_id());
|
||||
assert_eq!(
|
||||
ThreadId(1),
|
||||
@@ -162,7 +154,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
|
||||
let active_session = debug_panel
|
||||
.update(cx, |this, cx| this.active_session(cx))
|
||||
.update(cx, |this, _| this.active_session())
|
||||
.unwrap();
|
||||
|
||||
let running_state = active_session.update(cx, |active_session, _| {
|
||||
@@ -174,8 +166,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
||||
});
|
||||
|
||||
debug_panel.update(cx, |this, cx| {
|
||||
assert!(this.active_session(cx).is_some());
|
||||
assert_eq!(2, this.pane().unwrap().read(cx).items_len());
|
||||
assert!(this.active_session().is_some());
|
||||
assert_eq!(
|
||||
ThreadId(1),
|
||||
running_state.read(cx).selected_thread_id().unwrap()
|
||||
@@ -243,10 +234,8 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
|
||||
debug_panel.update(cx, |this, cx| {
|
||||
assert!(this.active_session(cx).is_some());
|
||||
// we have one active session and one inert item
|
||||
assert_eq!(2, this.pane().unwrap().read(cx).items_len());
|
||||
debug_panel.update(cx, |this, _| {
|
||||
assert!(this.active_session().is_some());
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
@@ -270,7 +259,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
let active_session = debug_panel
|
||||
.update(cx, |this, cx| this.active_session(cx))
|
||||
.update(cx, |this, _| this.active_session())
|
||||
.unwrap();
|
||||
|
||||
let running_state = active_session.update(cx, |active_session, _| {
|
||||
@@ -281,12 +270,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||
.clone()
|
||||
});
|
||||
|
||||
// we have one active session and one inert item
|
||||
assert_eq!(
|
||||
2,
|
||||
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
|
||||
);
|
||||
assert_eq!(client.id(), active_session.read(cx).session_id(cx).unwrap());
|
||||
assert_eq!(client.id(), active_session.read(cx).session_id(cx));
|
||||
assert_eq!(
|
||||
ThreadId(1),
|
||||
running_state.read(cx).selected_thread_id().unwrap()
|
||||
@@ -312,7 +296,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
let active_session = debug_panel
|
||||
.update(cx, |this, cx| this.active_session(cx))
|
||||
.update(cx, |this, _| this.active_session())
|
||||
.unwrap();
|
||||
|
||||
let running_state = active_session.update(cx, |active_session, _| {
|
||||
@@ -323,12 +307,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||
.clone()
|
||||
});
|
||||
|
||||
// we have one active session and one inert item
|
||||
assert_eq!(
|
||||
2,
|
||||
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
|
||||
);
|
||||
assert_eq!(client.id(), active_session.read(cx).session_id(cx).unwrap());
|
||||
assert_eq!(client.id(), active_session.read(cx).session_id(cx));
|
||||
assert_eq!(
|
||||
ThreadId(1),
|
||||
running_state.read(cx).selected_thread_id().unwrap()
|
||||
@@ -349,7 +328,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
let active_session = debug_panel
|
||||
.update(cx, |this, cx| this.active_session(cx))
|
||||
.update(cx, |this, _| this.active_session())
|
||||
.unwrap();
|
||||
|
||||
let running_state = active_session.update(cx, |active_session, _| {
|
||||
@@ -361,8 +340,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||
});
|
||||
|
||||
debug_panel.update(cx, |this, cx| {
|
||||
assert!(this.active_session(cx).is_some());
|
||||
assert_eq!(2, this.pane().unwrap().read(cx).items_len());
|
||||
assert!(this.active_session().is_some());
|
||||
assert_eq!(
|
||||
ThreadId(1),
|
||||
running_state.read(cx).selected_thread_id().unwrap()
|
||||
@@ -1447,7 +1425,7 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
|
||||
})
|
||||
.await;
|
||||
|
||||
cx.dispatch_action(workspace::ClearAllBreakpoints);
|
||||
cx.dispatch_action(crate::ClearAllBreakpoints);
|
||||
cx.run_until_parked();
|
||||
|
||||
let shutdown_session = project.update(cx, |project, cx| {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::{
|
||||
debugger_panel::DebugPanel,
|
||||
session::ThreadItem,
|
||||
tests::{active_debug_session_panel, init_test, init_test_workspace},
|
||||
};
|
||||
use dap::{
|
||||
@@ -139,13 +138,7 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||
.clone()
|
||||
});
|
||||
|
||||
assert!(
|
||||
!called_modules.load(std::sync::atomic::Ordering::SeqCst),
|
||||
"Request Modules shouldn't be called before it's needed"
|
||||
);
|
||||
|
||||
running_state.update(cx, |state, cx| {
|
||||
state.set_thread_item(ThreadItem::Modules, cx);
|
||||
running_state.update(cx, |_, cx| {
|
||||
cx.refresh_windows();
|
||||
});
|
||||
|
||||
@@ -157,9 +150,6 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||
);
|
||||
|
||||
active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
|
||||
running_state.update(cx, |state, cx| {
|
||||
state.set_thread_item(ThreadItem::Modules, cx)
|
||||
});
|
||||
let actual_modules = running_state.update(cx, |state, cx| {
|
||||
state.module_list().update(cx, |list, cx| list.modules(cx))
|
||||
});
|
||||
|
||||
@@ -410,7 +410,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
let active_debug_panel_item = debug_panel
|
||||
.update(cx, |this, cx| this.active_session(cx))
|
||||
.update(cx, |this, _| this.active_session())
|
||||
.unwrap();
|
||||
|
||||
active_debug_panel_item
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user