Compare commits
1 Commits
fix-launch
...
arm_github
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f855eb7470 |
9
.github/ISSUE_TEMPLATE/01_bug_ai.yml
vendored
9
.github/ISSUE_TEMPLATE/01_bug_ai.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Bug Report (AI)
|
||||
name: Bug Report (AI Related)
|
||||
description: Zed Agent Panel Bugs
|
||||
type: "Bug"
|
||||
labels: ["ai"]
|
||||
@@ -19,14 +19,15 @@ body:
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected Behavior**:
|
||||
**Actual Behavior**:
|
||||
Actual Behavior:
|
||||
Expected Behavior:
|
||||
|
||||
### Model Provider Details
|
||||
- Provider: (Anthropic via ZedPro, Anthropic via API key, Copilot Chat, Mistral, OpenAI, etc)
|
||||
- Model Name:
|
||||
- Mode: (Agent Panel, Inline Assistant, Terminal Assistant or Text Threads)
|
||||
- Other Details (MCPs, other settings, etc):
|
||||
- MCP Servers in-use:
|
||||
- Other Details:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
36
.github/ISSUE_TEMPLATE/02_bug_edit_predictions.yml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/02_bug_edit_predictions.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Bug Report (Edit Predictions)
|
||||
description: Zed Edit Predictions bugs
|
||||
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 -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
### Description
|
||||
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
|
||||
<!-- Please include the LLM provider and model name you are using -->
|
||||
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
|
||||
35
.github/ISSUE_TEMPLATE/03_bug_git.yml
vendored
Normal file
35
.github/ISSUE_TEMPLATE/03_bug_git.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Bug Report (Git)
|
||||
description: Zed Git-Related Bugs
|
||||
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 -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
### Description
|
||||
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
|
||||
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
|
||||
4
.github/ISSUE_TEMPLATE/04_bug_debugger.yml
vendored
4
.github/ISSUE_TEMPLATE/04_bug_debugger.yml
vendored
@@ -19,8 +19,8 @@ body:
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected Behavior**:
|
||||
**Actual Behavior**:
|
||||
Actual Behavior:
|
||||
Expected Behavior:
|
||||
|
||||
validations:
|
||||
required: true
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
@@ -18,16 +18,14 @@ body:
|
||||
- Issues with insufficient detail may be summarily closed.
|
||||
-->
|
||||
|
||||
DESCRIPTION_HERE
|
||||
|
||||
Steps to reproduce:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
**Expected Behavior**:
|
||||
**Actual Behavior**:
|
||||
Expected Behavior:
|
||||
Actual Behavior:
|
||||
|
||||
<!-- Before Submitting, did you:
|
||||
1. Include settings.json, keymap.json, .editorconfig if relevant?
|
||||
|
||||
2
.github/actions/build_docs/action.yml
vendored
2
.github/actions/build_docs/action.yml
vendored
@@ -22,7 +22,7 @@ runs:
|
||||
- name: Check for broken links
|
||||
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
|
||||
with:
|
||||
args: --no-progress --exclude '^http' './docs/src/**/*'
|
||||
args: --no-progress './docs/src/**/*'
|
||||
fail: true
|
||||
|
||||
- name: Build book
|
||||
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -183,9 +183,6 @@ jobs:
|
||||
- name: Check for todo! and FIXME comments
|
||||
run: script/check-todos
|
||||
|
||||
- name: Check modifier use in keymaps
|
||||
run: script/check-keymaps
|
||||
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
@@ -686,7 +683,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
name: Linux arm64 release bundle
|
||||
runs-on:
|
||||
- buildjet-16vcpu-ubuntu-2204-arm
|
||||
- hosted-linux-arm-1
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
@@ -801,7 +798,6 @@ jobs:
|
||||
name: Build with Nix
|
||||
uses: ./.github/workflows/nix.yml
|
||||
if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix')
|
||||
secrets: inherit
|
||||
with:
|
||||
flake-output: debug
|
||||
# excludes the final package to only cache dependencies
|
||||
|
||||
1
.github/workflows/eval.yml
vendored
1
.github/workflows/eval.yml
vendored
@@ -30,7 +30,6 @@ jobs:
|
||||
noop:
|
||||
name: No-op
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
steps:
|
||||
- name: No-op
|
||||
run: echo "Nothing to do"
|
||||
|
||||
1
.github/workflows/release_nightly.yml
vendored
1
.github/workflows/release_nightly.yml
vendored
@@ -214,7 +214,6 @@ jobs:
|
||||
bundle-nix:
|
||||
name: Build and cache Nix package
|
||||
needs: tests
|
||||
secrets: inherit
|
||||
uses: ./.github/workflows/nix.yml
|
||||
|
||||
update-nightly-tag:
|
||||
|
||||
3
.github/workflows/unit_evals.yml
vendored
3
.github/workflows/unit_evals.yml
vendored
@@ -19,7 +19,6 @@ env:
|
||||
|
||||
jobs:
|
||||
unit_evals:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
timeout-minutes: 60
|
||||
name: Run unit evals
|
||||
runs-on:
|
||||
@@ -63,7 +62,7 @@ jobs:
|
||||
|
||||
- name: Run unit evals
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: cargo nextest run --workspace --no-fail-fast --features eval --no-capture -E 'test(::eval_)'
|
||||
run: cargo nextest run --workspace --no-fail-fast --features eval --no-capture -E 'test(::eval_)' --test-threads 1
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
|
||||
|
||||
32
Cargo.lock
generated
32
Cargo.lock
generated
@@ -99,7 +99,6 @@ dependencies = [
|
||||
"paths",
|
||||
"picker",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"prompt_store",
|
||||
"proto",
|
||||
@@ -705,7 +704,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"streaming_diff",
|
||||
"strsim",
|
||||
"task",
|
||||
@@ -3161,16 +3159,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "command-fds"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ec1052629a80c28594777d1252efc8a6b005d13f9edfd8c3fc0f44d5b32489a"
|
||||
dependencies = [
|
||||
"nix 0.30.1",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "command_palette"
|
||||
version = "0.1.0"
|
||||
@@ -4027,7 +4015,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"language",
|
||||
"libc",
|
||||
"log",
|
||||
"node_runtime",
|
||||
"parking_lot",
|
||||
@@ -4051,7 +4038,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dap-types"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/zed-industries/dap-types?rev=b40956a7f4d1939da67429d941389ee306a3a308#b40956a7f4d1939da67429d941389ee306a3a308"
|
||||
source = "git+https://github.com/zed-industries/dap-types?rev=68516de327fa1be15214133a0a2e52a12982ce75#68516de327fa1be15214133a0a2e52a12982ce75"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -4064,7 +4051,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"collections",
|
||||
"dap",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
@@ -4696,6 +4682,7 @@ dependencies = [
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
"convert_case 0.8.0",
|
||||
"ctor",
|
||||
"dap",
|
||||
@@ -9003,6 +8990,7 @@ dependencies = [
|
||||
"tree-sitter-yaml",
|
||||
"unindent",
|
||||
"util",
|
||||
"which 6.0.3",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
]
|
||||
@@ -10141,18 +10129,6 @@ dependencies = [
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.2.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "node_runtime"
|
||||
version = "0.1.0"
|
||||
@@ -12133,6 +12109,7 @@ dependencies = [
|
||||
"unindent",
|
||||
"url",
|
||||
"util",
|
||||
"uuid",
|
||||
"which 6.0.3",
|
||||
"workspace-hack",
|
||||
"worktree",
|
||||
@@ -17144,7 +17121,6 @@ dependencies = [
|
||||
"async-fs",
|
||||
"async_zip",
|
||||
"collections",
|
||||
"command-fds",
|
||||
"dirs 4.0.0",
|
||||
"dunce",
|
||||
"futures 0.3.31",
|
||||
|
||||
@@ -435,7 +435,7 @@ core-foundation-sys = "0.8.6"
|
||||
core-video = { version = "0.4.3", features = ["metal"] }
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
ctor = "0.4.0"
|
||||
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "b40956a7f4d1939da67429d941389ee306a3a308" }
|
||||
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "68516de327fa1be15214133a0a2e52a12982ce75" }
|
||||
dashmap = "6.0"
|
||||
derive_more = "0.99.17"
|
||||
dirs = "4.0"
|
||||
@@ -698,8 +698,6 @@ codegen-units = 16
|
||||
[profile.dev.package]
|
||||
taffy = { opt-level = 3 }
|
||||
cranelift-codegen = { opt-level = 3 }
|
||||
cranelift-codegen-meta = { opt-level = 3 }
|
||||
cranelift-codegen-shared = { opt-level = 3 }
|
||||
resvg = { opt-level = 3 }
|
||||
rustybuzz = { opt-level = 3 }
|
||||
ttf-parser = { opt-level = 3 }
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<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-help-icon lucide-circle-help"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>
|
||||
|
Before Width: | Height: | Size: 348 B |
@@ -35,7 +35,7 @@
|
||||
"ctrl-shift-f5": "debugger::Restart",
|
||||
"f6": "debugger::Pause",
|
||||
"f7": "debugger::StepOver",
|
||||
"ctrl-f11": "debugger::StepInto",
|
||||
"cmd-f11": "debugger::StepInto",
|
||||
"shift-f11": "debugger::StepOut",
|
||||
"f11": "zed::ToggleFullScreen",
|
||||
"ctrl-alt-z": "edit_prediction::RateCompletions",
|
||||
@@ -59,6 +59,7 @@
|
||||
"tab": "editor::Tab",
|
||||
"shift-tab": "editor::Backtab",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
// "ctrl-t": "editor::Transpose",
|
||||
"ctrl-k ctrl-q": "editor::Rewrap",
|
||||
"ctrl-k q": "editor::Rewrap",
|
||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||
@@ -99,16 +100,21 @@
|
||||
"shift-down": "editor::SelectDown",
|
||||
"shift-left": "editor::SelectLeft",
|
||||
"shift-right": "editor::SelectRight",
|
||||
"ctrl-shift-left": "editor::SelectToPreviousWordStart",
|
||||
"ctrl-shift-right": "editor::SelectToNextWordEnd",
|
||||
"ctrl-shift-left": "editor::SelectToPreviousWordStart", // cursorWordLeftSelect
|
||||
"ctrl-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
|
||||
"ctrl-shift-home": "editor::SelectToBeginning",
|
||||
"ctrl-shift-end": "editor::SelectToEnd",
|
||||
"ctrl-a": "editor::SelectAll",
|
||||
"ctrl-l": "editor::SelectLine",
|
||||
"ctrl-shift-i": "editor::Format",
|
||||
"alt-shift-o": "editor::OrganizeImports",
|
||||
// "cmd-shift-left": ["editor::SelectToBeginningOfLine", {"stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
// "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
// "cmd-shift-right": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
// "ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
// "alt-v": ["editor::MovePageUp", { "center_cursor": true }],
|
||||
"ctrl-alt-space": "editor::ShowCharacterPalette",
|
||||
"ctrl-;": "editor::ToggleLineNumbers",
|
||||
"ctrl-'": "editor::ToggleSelectedDiffHunks",
|
||||
@@ -134,6 +140,7 @@
|
||||
"find": "buffer_search::Deploy",
|
||||
"ctrl-f": "buffer_search::Deploy",
|
||||
"ctrl-h": "buffer_search::DeployReplace",
|
||||
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
|
||||
"ctrl->": "assistant::QuoteSelection",
|
||||
"ctrl-<": "assistant::InsertIntoEditor",
|
||||
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
|
||||
@@ -260,8 +267,8 @@
|
||||
{
|
||||
"context": "AgentPanel && prompt_editor",
|
||||
"bindings": {
|
||||
"ctrl-n": "agent::NewTextThread",
|
||||
"ctrl-alt-t": "agent::NewThread"
|
||||
"cmd-n": "agent::NewTextThread",
|
||||
"cmd-alt-t": "agent::NewThread"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"ctrl-shift-d": "editor::DuplicateSelection",
|
||||
"alt-f3": "editor::SelectAllMatches", // find_all_under
|
||||
// "ctrl-f3": "", // find_under (cancels any selections)
|
||||
// "ctrl-alt-shift-g": "" // find_under_prev (cancels any selections)
|
||||
// "cmd-alt-shift-g": "" // find_under_prev (cancels any selections)
|
||||
"f9": "editor::SortLinesCaseSensitive",
|
||||
"ctrl-f9": "editor::SortLinesCaseInsensitive",
|
||||
"f12": "editor::GoToDefinition",
|
||||
|
||||
@@ -28,8 +28,7 @@
|
||||
"context": "InlineAssistEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-backspace": "editor::Cancel",
|
||||
"cmd-enter": "menu::Confirm"
|
||||
"cmd-shift-backspace": "editor::Cancel"
|
||||
// "alt-enter": // Quick Question
|
||||
// "cmd-shift-enter": // Full File Context
|
||||
// "cmd-shift-k": // Toggle input focus (editor <> inline assist)
|
||||
|
||||
@@ -395,8 +395,6 @@
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePreviousItem",
|
||||
"insert": "vim::InsertBefore",
|
||||
".": "vim::Repeat",
|
||||
"alt-.": "vim::RepeatFind",
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode",
|
||||
@@ -423,7 +421,6 @@
|
||||
|
||||
"x": "editor::SelectLine",
|
||||
"shift-x": "editor::SelectLine",
|
||||
"%": "editor::SelectAll",
|
||||
// Window mode
|
||||
"space w h": "workspace::ActivatePaneLeft",
|
||||
"space w l": "workspace::ActivatePaneRight",
|
||||
@@ -453,8 +450,7 @@
|
||||
"ctrl-c": "editor::ToggleComments",
|
||||
"d": "vim::HelixDelete",
|
||||
"c": "vim::Substitute",
|
||||
"shift-c": "editor::AddSelectionBelow",
|
||||
"alt-shift-c": "editor::AddSelectionAbove"
|
||||
"shift-c": "editor::AddSelectionBelow"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -101,12 +101,9 @@
|
||||
// The second option is decimal.
|
||||
"unit": "binary"
|
||||
},
|
||||
// Determines the modifier to be used to add multiple cursors with the mouse. The open hover link mouse gestures will adapt such that it do not conflict with the multicursor modifier.
|
||||
//
|
||||
// 1. Maps to `Alt` on Linux and Windows and to `Option` on MacOS:
|
||||
// "alt"
|
||||
// 2. Maps `Control` on Linux and Windows and to `Command` on MacOS:
|
||||
// "cmd_or_ctrl" (alias: "cmd", "ctrl")
|
||||
// The key to use for adding multiple cursors
|
||||
// Currently "alt" or "cmd_or_ctrl" (also aliased as
|
||||
// "cmd" and "ctrl") are supported.
|
||||
"multi_cursor_modifier": "alt",
|
||||
// Whether to enable vim modes and key bindings.
|
||||
"vim_mode": false,
|
||||
@@ -217,8 +214,6 @@
|
||||
"show_signature_help_after_edits": false,
|
||||
// Whether to show code action button at start of buffer line.
|
||||
"inline_code_actions": true,
|
||||
// Whether to allow drag and drop text selection in buffer.
|
||||
"drag_and_drop_selection": true,
|
||||
// What to do when go to definition yields no results.
|
||||
//
|
||||
// 1. Do nothing: `none`
|
||||
@@ -445,9 +440,7 @@
|
||||
// Whether to show breakpoints in the gutter.
|
||||
"breakpoints": true,
|
||||
// Whether to show fold buttons in the gutter.
|
||||
"folds": true,
|
||||
// Minimum number of characters to reserve space for in the gutter.
|
||||
"min_line_number_digits": 4
|
||||
"folds": true
|
||||
},
|
||||
"indent_guides": {
|
||||
// Whether to show indent guides in the editor.
|
||||
@@ -606,9 +599,7 @@
|
||||
// 2. Never show indent guides:
|
||||
// "never"
|
||||
"show": "always"
|
||||
},
|
||||
// Whether to hide the root entry when only one folder is open in the window.
|
||||
"hide_root": false
|
||||
}
|
||||
},
|
||||
"outline_panel": {
|
||||
// Whether to show the outline panel button in the status bar
|
||||
@@ -1480,8 +1471,7 @@
|
||||
"Go": {
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true
|
||||
},
|
||||
"debuggers": ["Delve"]
|
||||
}
|
||||
},
|
||||
"GraphQL": {
|
||||
"prettier": {
|
||||
@@ -1546,15 +1536,9 @@
|
||||
"Plain Text": {
|
||||
"allow_rewrap": "anywhere"
|
||||
},
|
||||
"Python": {
|
||||
"debuggers": ["Debugpy"]
|
||||
},
|
||||
"Ruby": {
|
||||
"language_servers": ["solargraph", "!ruby-lsp", "!rubocop", "!sorbet", "!steep", "..."]
|
||||
},
|
||||
"Rust": {
|
||||
"debuggers": ["CodeLLDB"]
|
||||
},
|
||||
"SCSS": {
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Project tasks configuration. See https://zed.dev/docs/tasks for documentation.
|
||||
// Static tasks configuration.
|
||||
//
|
||||
// Example:
|
||||
[
|
||||
|
||||
@@ -99,8 +99,6 @@
|
||||
"version_control.added": "#27a657ff",
|
||||
"version_control.modified": "#d3b020ff",
|
||||
"version_control.deleted": "#e06c76ff",
|
||||
"version_control.conflict_marker.ours": "#a1c1811a",
|
||||
"version_control.conflict_marker.theirs": "#74ade81a",
|
||||
"conflict": "#dec184ff",
|
||||
"conflict.background": "#dec1841a",
|
||||
"conflict.border": "#5d4c2fff",
|
||||
|
||||
@@ -109,6 +109,5 @@ gpui = { workspace = true, "features" = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, "features" = ["test-support"] }
|
||||
language_model = { workspace = true, "features" = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
|
||||
@@ -1605,7 +1605,6 @@ impl ActiveThread {
|
||||
|
||||
this.thread.update(cx, |thread, cx| {
|
||||
thread.advance_prompt_id();
|
||||
thread.cancel_last_completion(Some(window.window_handle()), cx);
|
||||
thread.send_to_model(
|
||||
model.model,
|
||||
CompletionIntent::UserPrompt,
|
||||
@@ -1789,31 +1788,12 @@ impl ActiveThread {
|
||||
|
||||
fn render_message(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
let message_id = self.messages[ix];
|
||||
let workspace = self.workspace.clone();
|
||||
let thread = self.thread.read(cx);
|
||||
|
||||
let is_first_message = ix == 0;
|
||||
let is_last_message = ix == self.messages.len() - 1;
|
||||
|
||||
let Some(message) = thread.message(message_id) else {
|
||||
let Some(message) = self.thread.read(cx).message(message_id) else {
|
||||
return Empty.into_any();
|
||||
};
|
||||
|
||||
let is_generating = thread.is_generating();
|
||||
let is_generating_stale = thread.is_generation_stale().unwrap_or(false);
|
||||
|
||||
let loading_dots = (is_generating && is_last_message).then(|| {
|
||||
h_flex()
|
||||
.h_8()
|
||||
.my_3()
|
||||
.mx_5()
|
||||
.when(is_generating_stale || message.is_hidden, |this| {
|
||||
this.child(AnimatedLabel::new("").size(LabelSize::Small))
|
||||
})
|
||||
});
|
||||
|
||||
if message.is_hidden {
|
||||
return div().children(loading_dots).into_any();
|
||||
return Empty.into_any();
|
||||
}
|
||||
|
||||
let message_creases = message.creases.clone();
|
||||
@@ -1822,6 +1802,9 @@ impl ActiveThread {
|
||||
return Empty.into_any();
|
||||
};
|
||||
|
||||
let workspace = self.workspace.clone();
|
||||
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 configured_model = thread.configured_model().map(|m| m.model);
|
||||
@@ -1832,6 +1815,14 @@ impl ActiveThread {
|
||||
|
||||
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_generating_stale = thread.is_generation_stale().unwrap_or(false);
|
||||
|
||||
let is_first_message = ix == 0;
|
||||
let is_last_message = ix == self.messages.len() - 1;
|
||||
|
||||
let loading_dots = (is_generating_stale && is_last_message)
|
||||
.then(|| AnimatedLabel::new("").size(LabelSize::Small));
|
||||
|
||||
let editing_message_state = self
|
||||
.editing_message
|
||||
@@ -2247,7 +2238,17 @@ impl ActiveThread {
|
||||
parent.child(self.render_rules_item(cx))
|
||||
})
|
||||
.child(styled_message)
|
||||
.children(loading_dots)
|
||||
.when(is_generating && is_last_message, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.h_8()
|
||||
.mt_2()
|
||||
.mb_4()
|
||||
.ml_4()
|
||||
.py_1p5()
|
||||
.when_some(loading_dots, |this, loading_dots| this.child(loading_dots)),
|
||||
)
|
||||
})
|
||||
.when(show_feedback, move |parent| {
|
||||
parent.child(feedback_items).when_some(
|
||||
self.open_feedback_editors.get(&message_id),
|
||||
@@ -3707,7 +3708,7 @@ mod tests {
|
||||
use util::path;
|
||||
use workspace::CollaboratorId;
|
||||
|
||||
use crate::{ContextLoadResult, thread::MessageSegment, thread_store};
|
||||
use crate::{ContextLoadResult, thread_store};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -3841,114 +3842,6 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_editing_message_cancels_previous_completion(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
|
||||
let (cx, active_thread, _, thread, model) =
|
||||
setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
cx.update(|_, cx| {
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry.set_default_model(
|
||||
Some(ConfiguredModel {
|
||||
provider: Arc::new(FakeLanguageModelProvider),
|
||||
model: model.clone(),
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Track thread events to verify cancellation
|
||||
let cancellation_events = Arc::new(std::sync::Mutex::new(Vec::new()));
|
||||
let new_request_events = Arc::new(std::sync::Mutex::new(Vec::new()));
|
||||
|
||||
let _subscription = cx.update(|_, cx| {
|
||||
let cancellation_events = cancellation_events.clone();
|
||||
let new_request_events = new_request_events.clone();
|
||||
cx.subscribe(
|
||||
&thread,
|
||||
move |_thread, event: &ThreadEvent, _cx| match event {
|
||||
ThreadEvent::CompletionCanceled => {
|
||||
cancellation_events.lock().unwrap().push(());
|
||||
}
|
||||
ThreadEvent::NewRequest => {
|
||||
new_request_events.lock().unwrap().push(());
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
// Insert a user message and start streaming a response
|
||||
let message = thread.update(cx, |thread, cx| {
|
||||
let message_id = thread.insert_user_message(
|
||||
"Hello, how are you?",
|
||||
ContextLoadResult::default(),
|
||||
None,
|
||||
vec![],
|
||||
cx,
|
||||
);
|
||||
thread.advance_prompt_id();
|
||||
thread.send_to_model(
|
||||
model.clone(),
|
||||
CompletionIntent::UserPrompt,
|
||||
cx.active_window(),
|
||||
cx,
|
||||
);
|
||||
thread.message(message_id).cloned().unwrap()
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
// Verify that a completion is in progress
|
||||
assert!(cx.read(|cx| thread.read(cx).is_generating()));
|
||||
assert_eq!(new_request_events.lock().unwrap().len(), 1);
|
||||
|
||||
// Edit the message while the completion is still running
|
||||
active_thread.update_in(cx, |active_thread, window, cx| {
|
||||
active_thread.start_editing_message(
|
||||
message.id,
|
||||
message.segments.as_slice(),
|
||||
message.creases.as_slice(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let editor = active_thread
|
||||
.editing_message
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.1
|
||||
.editor
|
||||
.clone();
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.set_text("What is the weather like?", window, cx);
|
||||
});
|
||||
active_thread.confirm_editing_message(&Default::default(), window, cx);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
// Verify that the previous completion was cancelled
|
||||
assert_eq!(cancellation_events.lock().unwrap().len(), 1);
|
||||
|
||||
// Verify that a new request was started after cancellation
|
||||
assert_eq!(new_request_events.lock().unwrap().len(), 2);
|
||||
|
||||
// Verify that the edited message contains the new text
|
||||
let edited_message =
|
||||
thread.update(cx, |thread, _| thread.message(message.id).cloned().unwrap());
|
||||
match &edited_message.segments[0] {
|
||||
MessageSegment::Text(text) => {
|
||||
assert_eq!(text, "What is the weather like?");
|
||||
}
|
||||
_ => panic!("Expected text segment"),
|
||||
}
|
||||
}
|
||||
|
||||
fn init_test_settings(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
|
||||
@@ -12,7 +12,7 @@ use context_server::ContextServerId;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Action, Animation, AnimationExt as _, AnyView, App, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, ScrollHandle, Subscription, Transformation, percentage,
|
||||
Focusable, ScrollHandle, Subscription, pulsating_between,
|
||||
};
|
||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||
use project::context_server_store::{ContextServerStatus, ContextServerStore};
|
||||
@@ -475,6 +475,7 @@ impl AgentConfiguration {
|
||||
.get(&context_server_id)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
|
||||
let tools = tools_by_source
|
||||
.get(&ToolSource::ContextServer {
|
||||
id: context_server_id.0.clone().into(),
|
||||
@@ -483,23 +484,25 @@ impl AgentConfiguration {
|
||||
let tool_count = tools.len();
|
||||
|
||||
let border_color = cx.theme().colors().border.opacity(0.6);
|
||||
let success_color = Color::Success.color(cx);
|
||||
|
||||
let (status_indicator, tooltip_text) = match server_status {
|
||||
ContextServerStatus::Starting => (
|
||||
Icon::new(IconName::LoadCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Accent)
|
||||
Indicator::dot()
|
||||
.color(Color::Success)
|
||||
.with_animation(
|
||||
SharedString::from(format!("{}-starting", context_server_id.0.clone(),)),
|
||||
Animation::new(Duration::from_secs(3)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.4, 1.)),
|
||||
move |this, delta| this.color(success_color.alpha(delta).into()),
|
||||
)
|
||||
.into_any_element(),
|
||||
"Server is starting.",
|
||||
),
|
||||
ContextServerStatus::Running => (
|
||||
Indicator::dot().color(Color::Success).into_any_element(),
|
||||
"Server is active.",
|
||||
"Server is running.",
|
||||
),
|
||||
ContextServerStatus::Error(_) => (
|
||||
Indicator::dot().color(Color::Error).into_any_element(),
|
||||
@@ -523,11 +526,12 @@ impl AgentConfiguration {
|
||||
.p_1()
|
||||
.justify_between()
|
||||
.when(
|
||||
error.is_some() || are_tools_expanded && tool_count >= 1,
|
||||
error.is_some() || are_tools_expanded && tool_count > 1,
|
||||
|element| element.border_b_1().border_color(border_color),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Disclosure::new(
|
||||
"tool-list-disclosure",
|
||||
@@ -547,16 +551,12 @@ impl AgentConfiguration {
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.id(SharedString::from(format!("tooltip-{}", item_id)))
|
||||
.h_full()
|
||||
.w_3()
|
||||
.mx_1()
|
||||
.justify_center()
|
||||
div()
|
||||
.id(item_id.clone())
|
||||
.tooltip(Tooltip::text(tooltip_text))
|
||||
.child(status_indicator),
|
||||
)
|
||||
.child(Label::new(item_id).ml_0p5().mr_1p5())
|
||||
.child(Label::new(context_server_id.0.clone()).ml_0p5())
|
||||
.when(is_running, |this| {
|
||||
this.child(
|
||||
Label::new(if tool_count == 1 {
|
||||
|
||||
@@ -91,13 +91,12 @@ impl AgentModelSelector {
|
||||
|
||||
impl Render for AgentModelSelector {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
let model = self.selector.read(cx).delegate.active_model(cx);
|
||||
let model_name = model
|
||||
.map(|model| model.model.name().0)
|
||||
.unwrap_or_else(|| SharedString::from("No model selected"));
|
||||
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
PickerPopoverMenu::new(
|
||||
self.selector.clone(),
|
||||
Button::new("active-model", model_name)
|
||||
|
||||
@@ -57,7 +57,7 @@ use zed_llm_client::{CompletionIntent, UsageLimit};
|
||||
use crate::active_thread::{self, ActiveThread, ActiveThreadEvent};
|
||||
use crate::agent_configuration::{AgentConfiguration, AssistantConfigurationEvent};
|
||||
use crate::agent_diff::AgentDiff;
|
||||
use crate::history_store::{HistoryEntryId, HistoryStore};
|
||||
use crate::history_store::{HistoryStore, RecentEntry};
|
||||
use crate::message_editor::{MessageEditor, MessageEditorEvent};
|
||||
use crate::thread::{Thread, ThreadError, ThreadId, ThreadSummary, TokenUsageRatio};
|
||||
use crate::thread_history::{HistoryEntryElement, ThreadHistory};
|
||||
@@ -257,7 +257,6 @@ impl ActiveView {
|
||||
|
||||
pub fn prompt_editor(
|
||||
context_editor: Entity<ContextEditor>,
|
||||
history_store: Entity<HistoryStore>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
@@ -323,19 +322,6 @@ impl ActiveView {
|
||||
editor.set_text(summary, window, cx);
|
||||
})
|
||||
}
|
||||
ContextEvent::PathChanged { old_path, new_path } => {
|
||||
history_store.update(cx, |history_store, cx| {
|
||||
if let Some(old_path) = old_path {
|
||||
history_store
|
||||
.replace_recently_opened_text_thread(old_path, new_path, cx);
|
||||
} else {
|
||||
history_store.push_recently_opened_entry(
|
||||
HistoryEntryId::Context(new_path.clone()),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}),
|
||||
@@ -530,7 +516,8 @@ impl AgentPanel {
|
||||
HistoryStore::new(
|
||||
thread_store.clone(),
|
||||
context_store.clone(),
|
||||
[HistoryEntryId::Thread(thread_id)],
|
||||
[RecentEntry::Thread(thread_id, thread.clone())],
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -557,13 +544,7 @@ impl AgentPanel {
|
||||
editor.insert_default_prompt(window, cx);
|
||||
editor
|
||||
});
|
||||
ActiveView::prompt_editor(
|
||||
context_editor,
|
||||
history_store.clone(),
|
||||
language_registry.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
ActiveView::prompt_editor(context_editor, language_registry.clone(), window, cx)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -600,9 +581,86 @@ impl AgentPanel {
|
||||
let panel = weak_panel.clone();
|
||||
let assistant_navigation_menu =
|
||||
ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
|
||||
if let Some(panel) = panel.upgrade() {
|
||||
menu = Self::populate_recently_opened_menu_section(menu, panel, cx);
|
||||
let recently_opened = panel
|
||||
.update(cx, |this, cx| {
|
||||
this.history_store.update(cx, |history_store, cx| {
|
||||
history_store.recently_opened_entries(cx)
|
||||
})
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if !recently_opened.is_empty() {
|
||||
menu = menu.header("Recently Opened");
|
||||
|
||||
for entry in recently_opened.iter() {
|
||||
if let RecentEntry::Context(context) = entry {
|
||||
if context.read(cx).path().is_none() {
|
||||
log::error!(
|
||||
"bug: text thread in recent history list was never saved"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let summary = entry.summary(cx);
|
||||
|
||||
menu = menu.entry_with_end_slot_on_hover(
|
||||
summary,
|
||||
None,
|
||||
{
|
||||
let panel = panel.clone();
|
||||
let entry = entry.clone();
|
||||
move |window, cx| {
|
||||
panel
|
||||
.update(cx, {
|
||||
let entry = entry.clone();
|
||||
move |this, cx| match entry {
|
||||
RecentEntry::Thread(_, thread) => {
|
||||
this.open_thread(thread, window, cx)
|
||||
}
|
||||
RecentEntry::Context(context) => {
|
||||
let Some(path) = context.read(cx).path()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
this.open_saved_prompt_editor(
|
||||
path.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx)
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
},
|
||||
IconName::Close,
|
||||
"Close Entry".into(),
|
||||
{
|
||||
let panel = panel.clone();
|
||||
let entry = entry.clone();
|
||||
move |_window, cx| {
|
||||
panel
|
||||
.update(cx, |this, cx| {
|
||||
this.history_store.update(
|
||||
cx,
|
||||
|history_store, cx| {
|
||||
history_store.remove_recently_opened_entry(
|
||||
&entry, cx,
|
||||
);
|
||||
},
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
menu = menu.separator();
|
||||
}
|
||||
|
||||
menu.action("View All", Box::new(OpenHistory))
|
||||
.end_slot_action(DeleteRecentlyOpenThread.boxed_clone())
|
||||
.fixed_width(px(320.).into())
|
||||
@@ -840,7 +898,6 @@ impl AgentPanel {
|
||||
self.set_active_view(
|
||||
ActiveView::prompt_editor(
|
||||
context_editor.clone(),
|
||||
self.history_store.clone(),
|
||||
self.language_registry.clone(),
|
||||
window,
|
||||
cx,
|
||||
@@ -927,13 +984,7 @@ impl AgentPanel {
|
||||
)
|
||||
});
|
||||
self.set_active_view(
|
||||
ActiveView::prompt_editor(
|
||||
editor.clone(),
|
||||
self.history_store.clone(),
|
||||
self.language_registry.clone(),
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
ActiveView::prompt_editor(editor.clone(), self.language_registry.clone(), window, cx),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -1332,6 +1383,16 @@ impl AgentPanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
ActiveView::TextThread { context_editor, .. } => {
|
||||
let context = context_editor.read(cx).context();
|
||||
// When switching away from an unsaved text thread, delete its entry.
|
||||
if context.read(cx).path().is_none() {
|
||||
let context = context.clone();
|
||||
self.history_store.update(cx, |store, cx| {
|
||||
store.remove_recently_opened_entry(&RecentEntry::Context(context), cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1339,14 +1400,13 @@ impl AgentPanel {
|
||||
ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
|
||||
if let Some(thread) = thread.upgrade() {
|
||||
let id = thread.read(cx).id().clone();
|
||||
store.push_recently_opened_entry(HistoryEntryId::Thread(id), cx);
|
||||
store.push_recently_opened_entry(RecentEntry::Thread(id, thread), cx);
|
||||
}
|
||||
}),
|
||||
ActiveView::TextThread { context_editor, .. } => {
|
||||
self.history_store.update(cx, |store, cx| {
|
||||
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
|
||||
store.push_recently_opened_entry(HistoryEntryId::Context(path.clone()), cx)
|
||||
}
|
||||
let context = context_editor.read(cx).context().clone();
|
||||
store.push_recently_opened_entry(RecentEntry::Context(context), cx)
|
||||
})
|
||||
}
|
||||
_ => {}
|
||||
@@ -1365,70 +1425,6 @@ impl AgentPanel {
|
||||
|
||||
self.focus_handle(cx).focus(window);
|
||||
}
|
||||
|
||||
fn populate_recently_opened_menu_section(
|
||||
mut menu: ContextMenu,
|
||||
panel: Entity<Self>,
|
||||
cx: &mut Context<ContextMenu>,
|
||||
) -> ContextMenu {
|
||||
let entries = panel
|
||||
.read(cx)
|
||||
.history_store
|
||||
.read(cx)
|
||||
.recently_opened_entries(cx);
|
||||
|
||||
if entries.is_empty() {
|
||||
return menu;
|
||||
}
|
||||
|
||||
menu = menu.header("Recently Opened");
|
||||
|
||||
for entry in entries {
|
||||
let title = entry.title().clone();
|
||||
let id = entry.id();
|
||||
|
||||
menu = menu.entry_with_end_slot_on_hover(
|
||||
title,
|
||||
None,
|
||||
{
|
||||
let panel = panel.downgrade();
|
||||
let id = id.clone();
|
||||
move |window, cx| {
|
||||
let id = id.clone();
|
||||
panel
|
||||
.update(cx, move |this, cx| match id {
|
||||
HistoryEntryId::Thread(id) => this
|
||||
.open_thread_by_id(&id, window, cx)
|
||||
.detach_and_log_err(cx),
|
||||
HistoryEntryId::Context(path) => this
|
||||
.open_saved_prompt_editor(path.clone(), window, cx)
|
||||
.detach_and_log_err(cx),
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
},
|
||||
IconName::Close,
|
||||
"Close Entry".into(),
|
||||
{
|
||||
let panel = panel.downgrade();
|
||||
let id = id.clone();
|
||||
move |_window, cx| {
|
||||
panel
|
||||
.update(cx, |this, cx| {
|
||||
this.history_store.update(cx, |history_store, cx| {
|
||||
history_store.remove_recently_opened_entry(&id, cx);
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
menu = menu.separator();
|
||||
|
||||
menu
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for AgentPanel {
|
||||
|
||||
@@ -386,10 +386,8 @@ impl CodegenAlternative {
|
||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||
} else {
|
||||
let request = self.build_request(&model, user_prompt, cx)?;
|
||||
cx.spawn(async move |_, cx| {
|
||||
Ok(model.stream_completion_text(request.await, &cx).await?)
|
||||
})
|
||||
.boxed_local()
|
||||
cx.spawn(async move |_, cx| model.stream_completion_text(request.await, &cx).await)
|
||||
.boxed_local()
|
||||
};
|
||||
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
||||
Ok(())
|
||||
|
||||
@@ -282,18 +282,15 @@ pub fn unordered_thread_entries(
|
||||
text_thread_store: Entity<TextThreadStore>,
|
||||
cx: &App,
|
||||
) -> impl Iterator<Item = (DateTime<Utc>, ThreadContextEntry)> {
|
||||
let threads = thread_store
|
||||
.read(cx)
|
||||
.reverse_chronological_threads()
|
||||
.map(|thread| {
|
||||
(
|
||||
thread.updated_at,
|
||||
ThreadContextEntry::Thread {
|
||||
id: thread.id.clone(),
|
||||
title: thread.summary.clone(),
|
||||
},
|
||||
)
|
||||
});
|
||||
let threads = thread_store.read(cx).unordered_threads().map(|thread| {
|
||||
(
|
||||
thread.updated_at,
|
||||
ThreadContextEntry::Thread {
|
||||
id: thread.id.clone(),
|
||||
title: thread.summary.clone(),
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
let text_threads = text_thread_store
|
||||
.read(cx)
|
||||
@@ -303,7 +300,7 @@ pub fn unordered_thread_entries(
|
||||
context.mtime.to_utc(),
|
||||
ThreadContextEntry::Context {
|
||||
path: context.path.clone(),
|
||||
title: context.title.clone(),
|
||||
title: context.title.clone().into(),
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
@@ -71,10 +71,6 @@ fn show_configure_mcp_modal(
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, Workspace>,
|
||||
) {
|
||||
if !window.is_window_active() {
|
||||
return;
|
||||
}
|
||||
|
||||
let context_server_store = workspace.project().read(cx).context_server_store();
|
||||
let repository: Option<SharedString> = manifest.repository.as_ref().map(|s| s.clone().into());
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ impl Tool for ContextServerTool {
|
||||
arguments
|
||||
);
|
||||
let response = protocol
|
||||
.request::<context_server::types::requests::CallTool>(
|
||||
.request::<context_server::types::request::CallTool>(
|
||||
context_server::types::CallToolParams {
|
||||
name: tool_name,
|
||||
arguments,
|
||||
@@ -123,9 +123,6 @@ impl Tool for ContextServerTool {
|
||||
types::ToolResponseContent::Image { .. } => {
|
||||
log::warn!("Ignoring image content from tool response");
|
||||
}
|
||||
types::ToolResponseContent::Audio { .. } => {
|
||||
log::warn!("Ignoring audio content from tool response");
|
||||
}
|
||||
types::ToolResponseContent::Resource { .. } => {
|
||||
log::warn!("Ignoring resource content from tool response");
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
use std::{collections::VecDeque, path::Path, sync::Arc};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_context_editor::SavedContextMetadata;
|
||||
use anyhow::Context as _;
|
||||
use assistant_context_editor::{AssistantContext, SavedContextMetadata};
|
||||
use chrono::{DateTime, Utc};
|
||||
use gpui::{AsyncApp, Entity, SharedString, Task, prelude::*};
|
||||
use itertools::Itertools;
|
||||
use paths::contexts_dir;
|
||||
use futures::future::{TryFutureExt as _, join_all};
|
||||
use gpui::{Entity, Task, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smol::future::FutureExt;
|
||||
use std::time::Duration;
|
||||
use ui::App;
|
||||
use ui::{App, SharedString, Window};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::{
|
||||
Thread,
|
||||
thread::ThreadId,
|
||||
thread_store::{SerializedThreadMetadata, ThreadStore},
|
||||
};
|
||||
@@ -40,34 +41,52 @@ impl HistoryEntry {
|
||||
HistoryEntry::Context(context) => HistoryEntryId::Context(context.path.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title(&self) -> &SharedString {
|
||||
match self {
|
||||
HistoryEntry::Thread(thread) => &thread.summary,
|
||||
HistoryEntry::Context(context) => &context.title,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic identifier for a history entry.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum HistoryEntryId {
|
||||
Thread(ThreadId),
|
||||
Context(Arc<Path>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum RecentEntry {
|
||||
Thread(ThreadId, Entity<Thread>),
|
||||
Context(Entity<AssistantContext>),
|
||||
}
|
||||
|
||||
impl PartialEq for RecentEntry {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Thread(l0, _), Self::Thread(r0, _)) => l0 == r0,
|
||||
(Self::Context(l0), Self::Context(r0)) => l0 == r0,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for RecentEntry {}
|
||||
|
||||
impl RecentEntry {
|
||||
pub(crate) fn summary(&self, cx: &App) -> SharedString {
|
||||
match self {
|
||||
RecentEntry::Thread(_, thread) => thread.read(cx).summary().or_default(),
|
||||
RecentEntry::Context(context) => context.read(cx).summary().or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
enum SerializedRecentOpen {
|
||||
enum SerializedRecentEntry {
|
||||
Thread(String),
|
||||
ContextName(String),
|
||||
/// Old format which stores the full path
|
||||
Context(String),
|
||||
}
|
||||
|
||||
pub struct HistoryStore {
|
||||
thread_store: Entity<ThreadStore>,
|
||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||
recently_opened_entries: VecDeque<HistoryEntryId>,
|
||||
recently_opened_entries: VecDeque<RecentEntry>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
_save_recently_opened_entries_task: Task<()>,
|
||||
}
|
||||
@@ -76,7 +95,8 @@ impl HistoryStore {
|
||||
pub fn new(
|
||||
thread_store: Entity<ThreadStore>,
|
||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||
initial_recent_entries: impl IntoIterator<Item = HistoryEntryId>,
|
||||
initial_recent_entries: impl IntoIterator<Item = RecentEntry>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let subscriptions = vec![
|
||||
@@ -84,20 +104,68 @@ impl HistoryStore {
|
||||
cx.observe(&context_store, |_, _, cx| cx.notify()),
|
||||
];
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let entries = Self::load_recently_opened_entries(cx).await.log_err()?;
|
||||
this.update(cx, |this, _| {
|
||||
this.recently_opened_entries
|
||||
.extend(
|
||||
entries.into_iter().take(
|
||||
MAX_RECENTLY_OPENED_ENTRIES
|
||||
.saturating_sub(this.recently_opened_entries.len()),
|
||||
),
|
||||
);
|
||||
window
|
||||
.spawn(cx, {
|
||||
let thread_store = thread_store.downgrade();
|
||||
let context_store = context_store.downgrade();
|
||||
let this = cx.weak_entity();
|
||||
async move |cx| {
|
||||
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
|
||||
let contents = cx
|
||||
.background_spawn(async move { std::fs::read_to_string(path) })
|
||||
.await
|
||||
.ok()?;
|
||||
let entries = serde_json::from_str::<Vec<SerializedRecentEntry>>(&contents)
|
||||
.context("deserializing persisted agent panel navigation history")
|
||||
.log_err()?
|
||||
.into_iter()
|
||||
.take(MAX_RECENTLY_OPENED_ENTRIES)
|
||||
.map(|serialized| match serialized {
|
||||
SerializedRecentEntry::Thread(id) => thread_store
|
||||
.update_in(cx, |thread_store, window, cx| {
|
||||
let thread_id = ThreadId::from(id.as_str());
|
||||
thread_store
|
||||
.open_thread(&thread_id, window, cx)
|
||||
.map_ok(|thread| RecentEntry::Thread(thread_id, thread))
|
||||
.boxed()
|
||||
})
|
||||
.unwrap_or_else(|_| {
|
||||
async {
|
||||
anyhow::bail!("no thread store");
|
||||
}
|
||||
.boxed()
|
||||
}),
|
||||
SerializedRecentEntry::Context(id) => context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
context_store
|
||||
.open_local_context(Path::new(&id).into(), cx)
|
||||
.map_ok(RecentEntry::Context)
|
||||
.boxed()
|
||||
})
|
||||
.unwrap_or_else(|_| {
|
||||
async {
|
||||
anyhow::bail!("no context store");
|
||||
}
|
||||
.boxed()
|
||||
}),
|
||||
});
|
||||
let entries = join_all(entries)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|result| result.log_with_level(log::Level::Debug))
|
||||
.collect::<VecDeque<_>>();
|
||||
|
||||
this.update(cx, |this, _| {
|
||||
this.recently_opened_entries.extend(entries);
|
||||
this.recently_opened_entries
|
||||
.truncate(MAX_RECENTLY_OPENED_ENTRIES);
|
||||
})
|
||||
.ok();
|
||||
|
||||
Some(())
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.detach();
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
thread_store,
|
||||
@@ -116,20 +184,19 @@ impl HistoryStore {
|
||||
return history_entries;
|
||||
}
|
||||
|
||||
history_entries.extend(
|
||||
self.thread_store
|
||||
.read(cx)
|
||||
.reverse_chronological_threads()
|
||||
.cloned()
|
||||
.map(HistoryEntry::Thread),
|
||||
);
|
||||
history_entries.extend(
|
||||
self.context_store
|
||||
.read(cx)
|
||||
.unordered_contexts()
|
||||
.cloned()
|
||||
.map(HistoryEntry::Context),
|
||||
);
|
||||
for thread in self
|
||||
.thread_store
|
||||
.update(cx, |this, _cx| this.reverse_chronological_threads())
|
||||
{
|
||||
history_entries.push(HistoryEntry::Thread(thread));
|
||||
}
|
||||
|
||||
for context in self
|
||||
.context_store
|
||||
.update(cx, |this, _cx| this.reverse_chronological_contexts())
|
||||
{
|
||||
history_entries.push(HistoryEntry::Context(context));
|
||||
}
|
||||
|
||||
history_entries.sort_unstable_by_key(|entry| std::cmp::Reverse(entry.updated_at()));
|
||||
history_entries
|
||||
@@ -139,62 +206,15 @@ impl HistoryStore {
|
||||
self.entries(cx).into_iter().take(limit).collect()
|
||||
}
|
||||
|
||||
pub fn recently_opened_entries(&self, cx: &App) -> Vec<HistoryEntry> {
|
||||
#[cfg(debug_assertions)]
|
||||
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let thread_entries = self
|
||||
.thread_store
|
||||
.read(cx)
|
||||
.reverse_chronological_threads()
|
||||
.flat_map(|thread| {
|
||||
self.recently_opened_entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(index, entry)| match entry {
|
||||
HistoryEntryId::Thread(id) if &thread.id == id => {
|
||||
Some((index, HistoryEntry::Thread(thread.clone())))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
|
||||
let context_entries =
|
||||
self.context_store
|
||||
.read(cx)
|
||||
.unordered_contexts()
|
||||
.flat_map(|context| {
|
||||
self.recently_opened_entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(index, entry)| match entry {
|
||||
HistoryEntryId::Context(path) if &context.path == path => {
|
||||
Some((index, HistoryEntry::Context(context.clone())))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
|
||||
thread_entries
|
||||
.chain(context_entries)
|
||||
// optimization to halt iteration early
|
||||
.take(self.recently_opened_entries.len())
|
||||
.sorted_unstable_by_key(|(index, _)| *index)
|
||||
.map(|(_, entry)| entry)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn save_recently_opened_entries(&mut self, cx: &mut Context<Self>) {
|
||||
let serialized_entries = self
|
||||
.recently_opened_entries
|
||||
.iter()
|
||||
.filter_map(|entry| match entry {
|
||||
HistoryEntryId::Context(path) => path.file_name().map(|file| {
|
||||
SerializedRecentOpen::ContextName(file.to_string_lossy().to_string())
|
||||
}),
|
||||
HistoryEntryId::Thread(id) => Some(SerializedRecentOpen::Thread(id.to_string())),
|
||||
RecentEntry::Context(context) => Some(SerializedRecentEntry::Context(
|
||||
context.read(cx).path()?.to_str()?.to_owned(),
|
||||
)),
|
||||
RecentEntry::Thread(id, _) => Some(SerializedRecentEntry::Thread(id.to_string())),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -213,33 +233,7 @@ impl HistoryStore {
|
||||
});
|
||||
}
|
||||
|
||||
fn load_recently_opened_entries(cx: &AsyncApp) -> Task<Result<Vec<HistoryEntryId>>> {
|
||||
cx.background_spawn(async move {
|
||||
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
|
||||
let contents = smol::fs::read_to_string(path).await?;
|
||||
let entries = serde_json::from_str::<Vec<SerializedRecentOpen>>(&contents)
|
||||
.context("deserializing persisted agent panel navigation history")?
|
||||
.into_iter()
|
||||
.take(MAX_RECENTLY_OPENED_ENTRIES)
|
||||
.flat_map(|entry| match entry {
|
||||
SerializedRecentOpen::Thread(id) => {
|
||||
Some(HistoryEntryId::Thread(id.as_str().into()))
|
||||
}
|
||||
SerializedRecentOpen::ContextName(file_name) => Some(HistoryEntryId::Context(
|
||||
contexts_dir().join(file_name).into(),
|
||||
)),
|
||||
SerializedRecentOpen::Context(path) => {
|
||||
Path::new(&path).file_name().map(|file_name| {
|
||||
HistoryEntryId::Context(contexts_dir().join(file_name).into())
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(entries)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn push_recently_opened_entry(&mut self, entry: HistoryEntryId, cx: &mut Context<Self>) {
|
||||
pub fn push_recently_opened_entry(&mut self, entry: RecentEntry, cx: &mut Context<Self>) {
|
||||
self.recently_opened_entries
|
||||
.retain(|old_entry| old_entry != &entry);
|
||||
self.recently_opened_entries.push_front(entry);
|
||||
@@ -250,33 +244,24 @@ impl HistoryStore {
|
||||
|
||||
pub fn remove_recently_opened_thread(&mut self, id: ThreadId, cx: &mut Context<Self>) {
|
||||
self.recently_opened_entries.retain(|entry| match entry {
|
||||
HistoryEntryId::Thread(thread_id) if thread_id == &id => false,
|
||||
RecentEntry::Thread(thread_id, _) if thread_id == &id => false,
|
||||
_ => true,
|
||||
});
|
||||
self.save_recently_opened_entries(cx);
|
||||
}
|
||||
|
||||
pub fn replace_recently_opened_text_thread(
|
||||
&mut self,
|
||||
old_path: &Path,
|
||||
new_path: &Arc<Path>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
for entry in &mut self.recently_opened_entries {
|
||||
match entry {
|
||||
HistoryEntryId::Context(path) if path.as_ref() == old_path => {
|
||||
*entry = HistoryEntryId::Context(new_path.clone());
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self.save_recently_opened_entries(cx);
|
||||
}
|
||||
|
||||
pub fn remove_recently_opened_entry(&mut self, entry: &HistoryEntryId, cx: &mut Context<Self>) {
|
||||
pub fn remove_recently_opened_entry(&mut self, entry: &RecentEntry, cx: &mut Context<Self>) {
|
||||
self.recently_opened_entries
|
||||
.retain(|old_entry| old_entry != entry);
|
||||
self.save_recently_opened_entries(cx);
|
||||
}
|
||||
|
||||
pub fn recently_opened_entries(&self, _cx: &mut Context<Self>) -> VecDeque<RecentEntry> {
|
||||
#[cfg(debug_assertions)]
|
||||
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
|
||||
return VecDeque::new();
|
||||
}
|
||||
|
||||
self.recently_opened_entries.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
use ui::prelude::*;
|
||||
use util::{RangeExt, ResultExt, maybe};
|
||||
use util::RangeExt;
|
||||
use util::ResultExt;
|
||||
use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};
|
||||
use zed_actions::agent::OpenConfiguration;
|
||||
|
||||
@@ -1170,31 +1171,27 @@ impl InlineAssistant {
|
||||
selections.select_anchor_ranges([position..position])
|
||||
});
|
||||
|
||||
let mut scroll_target_range = None;
|
||||
let mut scroll_target_top;
|
||||
let mut scroll_target_bottom;
|
||||
if let Some(decorations) = assist.decorations.as_ref() {
|
||||
scroll_target_range = maybe!({
|
||||
let top = editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32;
|
||||
let bottom = editor.row_for_block(decorations.end_block_id, cx)?.0 as f32;
|
||||
Some((top, bottom))
|
||||
});
|
||||
if scroll_target_range.is_none() {
|
||||
log::error!("bug: failed to find blocks for scrolling to inline assist");
|
||||
}
|
||||
}
|
||||
let scroll_target_range = scroll_target_range.unwrap_or_else(|| {
|
||||
scroll_target_top = editor
|
||||
.row_for_block(decorations.prompt_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
scroll_target_bottom = editor
|
||||
.row_for_block(decorations.end_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
} else {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let start_row = assist
|
||||
.range
|
||||
.start
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.row();
|
||||
let top = start_row.0 as f32;
|
||||
let bottom = top + 1.0;
|
||||
(top, bottom)
|
||||
});
|
||||
let mut scroll_target_top = scroll_target_range.0;
|
||||
let mut scroll_target_bottom = scroll_target_range.1;
|
||||
|
||||
scroll_target_top = start_row.0 as f32;
|
||||
scroll_target_bottom = scroll_target_top + 1.;
|
||||
}
|
||||
scroll_target_top -= editor.vertical_scroll_margin() as f32;
|
||||
scroll_target_bottom += editor.vertical_scroll_margin() as f32;
|
||||
|
||||
@@ -1334,7 +1331,7 @@ impl InlineAssistant {
|
||||
editor.clear_gutter_highlights::<GutterPendingRange>(cx);
|
||||
} else {
|
||||
editor.highlight_gutter::<GutterPendingRange>(
|
||||
gutter_pending_ranges,
|
||||
&gutter_pending_ranges,
|
||||
|cx| cx.theme().status().info_background,
|
||||
cx,
|
||||
)
|
||||
@@ -1345,7 +1342,7 @@ impl InlineAssistant {
|
||||
editor.clear_gutter_highlights::<GutterTransformedRange>(cx);
|
||||
} else {
|
||||
editor.highlight_gutter::<GutterTransformedRange>(
|
||||
gutter_transformed_ranges,
|
||||
&gutter_transformed_ranges,
|
||||
|cx| cx.theme().status().info,
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -722,7 +722,6 @@ impl MessageEditor {
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_none()
|
||||
.flex_wrap()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -732,7 +731,6 @@ impl MessageEditor {
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.flex_wrap()
|
||||
.when(!incompatible_tools.is_empty(), |this| {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
|
||||
@@ -195,20 +195,20 @@ impl MessageSegment {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProjectSnapshot {
|
||||
pub worktree_snapshots: Vec<WorktreeSnapshot>,
|
||||
pub unsaved_buffer_paths: Vec<String>,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct WorktreeSnapshot {
|
||||
pub worktree_path: String,
|
||||
pub git_state: Option<GitState>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GitState {
|
||||
pub remote_url: Option<String>,
|
||||
pub head_sha: Option<String>,
|
||||
@@ -247,7 +247,7 @@ impl LastRestoreCheckpoint {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub enum DetailedSummaryState {
|
||||
#[default]
|
||||
NotGenerated,
|
||||
@@ -391,7 +391,7 @@ impl ThreadSummary {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ExceededWindowError {
|
||||
/// Model used when last message exceeded context window
|
||||
model_id: LanguageModelId,
|
||||
@@ -1563,9 +1563,6 @@ impl Thread {
|
||||
Err(LanguageModelCompletionError::Other(error)) => {
|
||||
return Err(error);
|
||||
}
|
||||
Err(err @ LanguageModelCompletionError::RateLimit(..)) => {
|
||||
return Err(err.into());
|
||||
}
|
||||
};
|
||||
|
||||
match event {
|
||||
|
||||
@@ -594,11 +594,10 @@ impl Render for ThreadHistory {
|
||||
view.pr_5()
|
||||
.child(
|
||||
uniform_list(
|
||||
cx.entity().clone(),
|
||||
"thread-history",
|
||||
self.list_item_count(),
|
||||
cx.processor(|this, range: Range<usize>, window, cx| {
|
||||
this.list_items(range, window, cx)
|
||||
}),
|
||||
Self::list_items,
|
||||
)
|
||||
.p_1()
|
||||
.track_scroll(self.scroll_handle.clone())
|
||||
@@ -672,7 +671,7 @@ impl RenderOnce for HistoryEntryElement {
|
||||
),
|
||||
HistoryEntry::Context(context) => (
|
||||
context.path.to_string_lossy().to_string(),
|
||||
context.title.clone(),
|
||||
context.title.clone().into(),
|
||||
context.mtime.timestamp(),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -89,7 +89,7 @@ pub fn init(cx: &mut App) {
|
||||
pub struct SharedProjectContext(Rc<RefCell<Option<ProjectContext>>>);
|
||||
|
||||
impl SharedProjectContext {
|
||||
pub fn borrow(&self) -> Ref<'_, Option<ProjectContext>> {
|
||||
pub fn borrow(&self) -> Ref<Option<ProjectContext>> {
|
||||
self.0.borrow()
|
||||
}
|
||||
}
|
||||
@@ -393,11 +393,16 @@ impl ThreadStore {
|
||||
self.threads.len()
|
||||
}
|
||||
|
||||
pub fn reverse_chronological_threads(&self) -> impl Iterator<Item = &SerializedThreadMetadata> {
|
||||
// ordering is from "ORDER BY" in `list_threads`
|
||||
pub fn unordered_threads(&self) -> impl Iterator<Item = &SerializedThreadMetadata> {
|
||||
self.threads.iter()
|
||||
}
|
||||
|
||||
pub fn reverse_chronological_threads(&self) -> Vec<SerializedThreadMetadata> {
|
||||
let mut threads = self.threads.iter().cloned().collect::<Vec<_>>();
|
||||
threads.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.updated_at));
|
||||
threads
|
||||
}
|
||||
|
||||
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
|
||||
cx.new(|cx| {
|
||||
Thread::new(
|
||||
@@ -562,7 +567,7 @@ impl ThreadStore {
|
||||
|
||||
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
|
||||
if let Some(response) = protocol
|
||||
.request::<context_server::types::requests::ListTools>(())
|
||||
.request::<context_server::types::request::ListTools>(())
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
@@ -603,7 +608,7 @@ pub struct SerializedThreadMetadata {
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SerializedThread {
|
||||
pub version: String,
|
||||
pub summary: SharedString,
|
||||
@@ -629,7 +634,7 @@ pub struct SerializedThread {
|
||||
pub profile: Option<AgentProfileId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SerializedLanguageModel {
|
||||
pub provider: String,
|
||||
pub model: String,
|
||||
@@ -690,15 +695,11 @@ impl SerializedThreadV0_1_0 {
|
||||
messages.push(message);
|
||||
}
|
||||
|
||||
SerializedThread {
|
||||
messages,
|
||||
version: SerializedThread::VERSION.to_string(),
|
||||
..self.0
|
||||
}
|
||||
SerializedThread { messages, ..self.0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SerializedMessage {
|
||||
pub id: MessageId,
|
||||
pub role: Role,
|
||||
@@ -716,7 +717,7 @@ pub struct SerializedMessage {
|
||||
pub is_hidden: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum SerializedMessageSegment {
|
||||
#[serde(rename = "text")]
|
||||
@@ -734,14 +735,14 @@ pub enum SerializedMessageSegment {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SerializedToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
pub name: SharedString,
|
||||
pub input: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SerializedToolResult {
|
||||
pub tool_use_id: LanguageModelToolUseId,
|
||||
pub is_error: bool,
|
||||
@@ -804,7 +805,7 @@ impl LegacySerializedMessage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SerializedCrease {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
@@ -923,7 +924,7 @@ impl ThreadsDatabase {
|
||||
|
||||
fn bytes_encode(
|
||||
item: &Self::EItem,
|
||||
) -> Result<std::borrow::Cow<'_, [u8]>, heed::BoxedError> {
|
||||
) -> Result<std::borrow::Cow<[u8]>, heed::BoxedError> {
|
||||
serde_json::to_vec(&item.0)
|
||||
.map(std::borrow::Cow::Owned)
|
||||
.map_err(Into::into)
|
||||
@@ -1061,181 +1062,3 @@ impl ThreadsDatabase {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::thread::{DetailedSummaryState, MessageId};
|
||||
use chrono::Utc;
|
||||
use language_model::{Role, TokenUsage};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_legacy_serialized_thread_upgrade() {
|
||||
let updated_at = Utc::now();
|
||||
let legacy_thread = LegacySerializedThread {
|
||||
summary: "Test conversation".into(),
|
||||
updated_at,
|
||||
messages: vec![LegacySerializedMessage {
|
||||
id: MessageId(1),
|
||||
role: Role::User,
|
||||
text: "Hello, world!".to_string(),
|
||||
tool_uses: vec![],
|
||||
tool_results: vec![],
|
||||
}],
|
||||
initial_project_snapshot: None,
|
||||
};
|
||||
|
||||
let upgraded = legacy_thread.upgrade();
|
||||
|
||||
assert_eq!(
|
||||
upgraded,
|
||||
SerializedThread {
|
||||
summary: "Test conversation".into(),
|
||||
updated_at,
|
||||
messages: vec![SerializedMessage {
|
||||
id: MessageId(1),
|
||||
role: Role::User,
|
||||
segments: vec![SerializedMessageSegment::Text {
|
||||
text: "Hello, world!".to_string()
|
||||
}],
|
||||
tool_uses: vec![],
|
||||
tool_results: vec![],
|
||||
context: "".to_string(),
|
||||
creases: vec![],
|
||||
is_hidden: false
|
||||
}],
|
||||
version: SerializedThread::VERSION.to_string(),
|
||||
initial_project_snapshot: None,
|
||||
cumulative_token_usage: TokenUsage::default(),
|
||||
request_token_usage: vec![],
|
||||
detailed_summary_state: DetailedSummaryState::default(),
|
||||
exceeded_window_error: None,
|
||||
model: None,
|
||||
completion_mode: None,
|
||||
tool_use_limit_reached: false,
|
||||
profile: None
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialized_threadv0_1_0_upgrade() {
|
||||
let updated_at = Utc::now();
|
||||
let thread_v0_1_0 = SerializedThreadV0_1_0(SerializedThread {
|
||||
summary: "Test conversation".into(),
|
||||
updated_at,
|
||||
messages: vec![
|
||||
SerializedMessage {
|
||||
id: MessageId(1),
|
||||
role: Role::User,
|
||||
segments: vec![SerializedMessageSegment::Text {
|
||||
text: "Use tool_1".to_string(),
|
||||
}],
|
||||
tool_uses: vec![],
|
||||
tool_results: vec![],
|
||||
context: "".to_string(),
|
||||
creases: vec![],
|
||||
is_hidden: false,
|
||||
},
|
||||
SerializedMessage {
|
||||
id: MessageId(2),
|
||||
role: Role::Assistant,
|
||||
segments: vec![SerializedMessageSegment::Text {
|
||||
text: "I want to use a tool".to_string(),
|
||||
}],
|
||||
tool_uses: vec![SerializedToolUse {
|
||||
id: "abc".into(),
|
||||
name: "tool_1".into(),
|
||||
input: serde_json::Value::Null,
|
||||
}],
|
||||
tool_results: vec![],
|
||||
context: "".to_string(),
|
||||
creases: vec![],
|
||||
is_hidden: false,
|
||||
},
|
||||
SerializedMessage {
|
||||
id: MessageId(1),
|
||||
role: Role::User,
|
||||
segments: vec![SerializedMessageSegment::Text {
|
||||
text: "Here is the tool result".to_string(),
|
||||
}],
|
||||
tool_uses: vec![],
|
||||
tool_results: vec![SerializedToolResult {
|
||||
tool_use_id: "abc".into(),
|
||||
is_error: false,
|
||||
content: LanguageModelToolResultContent::Text("abcdef".into()),
|
||||
output: Some(serde_json::Value::Null),
|
||||
}],
|
||||
context: "".to_string(),
|
||||
creases: vec![],
|
||||
is_hidden: false,
|
||||
},
|
||||
],
|
||||
version: SerializedThreadV0_1_0::VERSION.to_string(),
|
||||
initial_project_snapshot: None,
|
||||
cumulative_token_usage: TokenUsage::default(),
|
||||
request_token_usage: vec![],
|
||||
detailed_summary_state: DetailedSummaryState::default(),
|
||||
exceeded_window_error: None,
|
||||
model: None,
|
||||
completion_mode: None,
|
||||
tool_use_limit_reached: false,
|
||||
profile: None,
|
||||
});
|
||||
let upgraded = thread_v0_1_0.upgrade();
|
||||
|
||||
assert_eq!(
|
||||
upgraded,
|
||||
SerializedThread {
|
||||
summary: "Test conversation".into(),
|
||||
updated_at,
|
||||
messages: vec![
|
||||
SerializedMessage {
|
||||
id: MessageId(1),
|
||||
role: Role::User,
|
||||
segments: vec![SerializedMessageSegment::Text {
|
||||
text: "Use tool_1".to_string()
|
||||
}],
|
||||
tool_uses: vec![],
|
||||
tool_results: vec![],
|
||||
context: "".to_string(),
|
||||
creases: vec![],
|
||||
is_hidden: false
|
||||
},
|
||||
SerializedMessage {
|
||||
id: MessageId(2),
|
||||
role: Role::Assistant,
|
||||
segments: vec![SerializedMessageSegment::Text {
|
||||
text: "I want to use a tool".to_string(),
|
||||
}],
|
||||
tool_uses: vec![SerializedToolUse {
|
||||
id: "abc".into(),
|
||||
name: "tool_1".into(),
|
||||
input: serde_json::Value::Null,
|
||||
}],
|
||||
tool_results: vec![SerializedToolResult {
|
||||
tool_use_id: "abc".into(),
|
||||
is_error: false,
|
||||
content: LanguageModelToolResultContent::Text("abcdef".into()),
|
||||
output: Some(serde_json::Value::Null),
|
||||
}],
|
||||
context: "".to_string(),
|
||||
creases: vec![],
|
||||
is_hidden: false,
|
||||
},
|
||||
],
|
||||
version: SerializedThread::VERSION.to_string(),
|
||||
initial_project_snapshot: None,
|
||||
cumulative_token_usage: TokenUsage::default(),
|
||||
request_token_usage: vec![],
|
||||
detailed_summary_state: DetailedSummaryState::default(),
|
||||
exceeded_window_error: None,
|
||||
model: None,
|
||||
completion_mode: None,
|
||||
tool_use_limit_reached: false,
|
||||
profile: None
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -407,7 +406,6 @@ impl RateLimit {
|
||||
/// <https://docs.anthropic.com/en/api/rate-limits#response-headers>
|
||||
#[derive(Debug)]
|
||||
pub struct RateLimitInfo {
|
||||
pub retry_after: Option<Duration>,
|
||||
pub requests: Option<RateLimit>,
|
||||
pub tokens: Option<RateLimit>,
|
||||
pub input_tokens: Option<RateLimit>,
|
||||
@@ -419,11 +417,10 @@ impl RateLimitInfo {
|
||||
// Check if any rate limit headers exist
|
||||
let has_rate_limit_headers = headers
|
||||
.keys()
|
||||
.any(|k| k == "retry-after" || k.as_str().starts_with("anthropic-ratelimit-"));
|
||||
.any(|k| k.as_str().starts_with("anthropic-ratelimit-"));
|
||||
|
||||
if !has_rate_limit_headers {
|
||||
return Self {
|
||||
retry_after: None,
|
||||
requests: None,
|
||||
tokens: None,
|
||||
input_tokens: None,
|
||||
@@ -432,11 +429,6 @@ impl RateLimitInfo {
|
||||
}
|
||||
|
||||
Self {
|
||||
retry_after: headers
|
||||
.get("retry-after")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| v.parse::<u64>().ok())
|
||||
.map(Duration::from_secs),
|
||||
requests: RateLimit::from_headers("requests", headers).ok(),
|
||||
tokens: RateLimit::from_headers("tokens", headers).ok(),
|
||||
input_tokens: RateLimit::from_headers("input-tokens", headers).ok(),
|
||||
@@ -489,8 +481,8 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
.send(request)
|
||||
.await
|
||||
.context("failed to send request to Anthropic")?;
|
||||
let rate_limits = RateLimitInfo::from_headers(response.headers());
|
||||
if response.status().is_success() {
|
||||
let rate_limits = RateLimitInfo::from_headers(response.headers());
|
||||
let reader = BufReader::new(response.into_body());
|
||||
let stream = reader
|
||||
.lines()
|
||||
@@ -508,8 +500,6 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
})
|
||||
.boxed();
|
||||
Ok((stream, Some(rate_limits)))
|
||||
} else if let Some(retry_after) = rate_limits.retry_after {
|
||||
Err(AnthropicError::RateLimit(retry_after))
|
||||
} else {
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
@@ -779,8 +769,6 @@ pub struct MessageDelta {
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AnthropicError {
|
||||
#[error("rate limit exceeded, retry after {0:?}")]
|
||||
RateLimit(Duration),
|
||||
#[error("an error occurred while interacting with the Anthropic API: {error_type}: {message}", error_type = .0.error_type, message = .0.message)]
|
||||
ApiError(ApiError),
|
||||
#[error("{0}")]
|
||||
|
||||
@@ -11,7 +11,7 @@ use assistant_slash_commands::FileCommandMetadata;
|
||||
use client::{self, proto, telemetry::Telemetry};
|
||||
use clock::ReplicaId;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::{Fs, RenameOptions};
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::{FutureExt, StreamExt, future::Shared};
|
||||
use gpui::{
|
||||
App, AppContext as _, Context, Entity, EventEmitter, RenderImage, SharedString, Subscription,
|
||||
@@ -452,10 +452,6 @@ pub enum ContextEvent {
|
||||
MessagesEdited,
|
||||
SummaryChanged,
|
||||
SummaryGenerated,
|
||||
PathChanged {
|
||||
old_path: Option<Arc<Path>>,
|
||||
new_path: Arc<Path>,
|
||||
},
|
||||
StreamedCompletion,
|
||||
StartedThoughtProcess(Range<language::Anchor>),
|
||||
EndedThoughtProcess(language::Anchor),
|
||||
@@ -2898,34 +2894,22 @@ impl AssistantContext {
|
||||
}
|
||||
|
||||
fs.create_dir(contexts_dir().as_ref()).await?;
|
||||
|
||||
// rename before write ensures that only one file exists
|
||||
if let Some(old_path) = old_path.as_ref() {
|
||||
fs.atomic_write(new_path.clone(), serde_json::to_string(&context).unwrap())
|
||||
.await?;
|
||||
if let Some(old_path) = old_path {
|
||||
if new_path.as_path() != old_path.as_ref() {
|
||||
fs.rename(
|
||||
fs.remove_file(
|
||||
&old_path,
|
||||
&new_path,
|
||||
RenameOptions {
|
||||
overwrite: true,
|
||||
ignore_if_exists: true,
|
||||
RemoveOptions {
|
||||
recursive: false,
|
||||
ignore_if_not_exists: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
// update path before write in case it fails
|
||||
this.update(cx, {
|
||||
let new_path: Arc<Path> = new_path.clone().into();
|
||||
move |this, cx| {
|
||||
this.path = Some(new_path.clone());
|
||||
cx.emit(ContextEvent::PathChanged { old_path, new_path });
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
fs.atomic_write(new_path, serde_json::to_string(&context).unwrap())
|
||||
.await?;
|
||||
this.update(cx, |this, _| this.path = Some(new_path.into()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -3293,7 +3277,7 @@ impl SavedContextV0_1_0 {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SavedContextMetadata {
|
||||
pub title: SharedString,
|
||||
pub title: String,
|
||||
pub path: Arc<Path>,
|
||||
pub mtime: chrono::DateTime<chrono::Local>,
|
||||
}
|
||||
|
||||
@@ -580,7 +580,6 @@ impl ContextEditor {
|
||||
});
|
||||
}
|
||||
ContextEvent::SummaryGenerated => {}
|
||||
ContextEvent::PathChanged { .. } => {}
|
||||
ContextEvent::StartedThoughtProcess(range) => {
|
||||
let creases = self.insert_thought_process_output_sections(
|
||||
[(
|
||||
|
||||
@@ -347,6 +347,12 @@ impl ContextStore {
|
||||
self.contexts_metadata.iter()
|
||||
}
|
||||
|
||||
pub fn reverse_chronological_contexts(&self) -> Vec<SavedContextMetadata> {
|
||||
let mut contexts = self.contexts_metadata.iter().cloned().collect::<Vec<_>>();
|
||||
contexts.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.mtime));
|
||||
contexts
|
||||
}
|
||||
|
||||
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
|
||||
let context = cx.new(|cx| {
|
||||
AssistantContext::local(
|
||||
@@ -612,16 +618,6 @@ impl ContextStore {
|
||||
ContextEvent::SummaryChanged => {
|
||||
self.advertise_contexts(cx);
|
||||
}
|
||||
ContextEvent::PathChanged { old_path, new_path } => {
|
||||
if let Some(old_path) = old_path.as_ref() {
|
||||
for metadata in &mut self.contexts_metadata {
|
||||
if &metadata.path == old_path {
|
||||
metadata.path = new_path.clone();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextEvent::Operation(operation) => {
|
||||
let context_id = context.read(cx).id().to_proto();
|
||||
let operation = operation.to_proto();
|
||||
@@ -796,7 +792,7 @@ impl ContextStore {
|
||||
.next()
|
||||
{
|
||||
contexts.push(SavedContextMetadata {
|
||||
title: title.to_string().into(),
|
||||
title: title.to_string(),
|
||||
path: path.into(),
|
||||
mtime: metadata.mtime.timestamp_for_user().into(),
|
||||
});
|
||||
@@ -869,7 +865,7 @@ impl ContextStore {
|
||||
|
||||
if protocol.capable(context_server::protocol::ServerCapability::Prompts) {
|
||||
if let Some(response) = protocol
|
||||
.request::<context_server::types::requests::PromptsList>(())
|
||||
.request::<context_server::types::request::PromptsList>(())
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
|
||||
@@ -682,12 +682,11 @@ mod tests {
|
||||
_: &AsyncApp,
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<
|
||||
http_client::Result<
|
||||
BoxStream<
|
||||
'static,
|
||||
Result<LanguageModelCompletionEvent, LanguageModelCompletionError>,
|
||||
http_client::Result<LanguageModelCompletionEvent, LanguageModelCompletionError>,
|
||||
>,
|
||||
LanguageModelCompletionError,
|
||||
>,
|
||||
> {
|
||||
unimplemented!()
|
||||
|
||||
@@ -87,7 +87,7 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
let protocol = server.client().context("Context server not initialized")?;
|
||||
|
||||
let response = protocol
|
||||
.request::<context_server::types::requests::CompletionComplete>(
|
||||
.request::<context_server::types::request::CompletionComplete>(
|
||||
context_server::types::CompletionCompleteParams {
|
||||
reference: context_server::types::CompletionReference::Prompt(
|
||||
context_server::types::PromptReference {
|
||||
@@ -145,7 +145,7 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let protocol = server.client().context("Context server not initialized")?;
|
||||
let response = protocol
|
||||
.request::<context_server::types::requests::PromptsGet>(
|
||||
.request::<context_server::types::request::PromptsGet>(
|
||||
context_server::types::PromptsGetParams {
|
||||
name: prompt_name.clone(),
|
||||
arguments: Some(prompt_args),
|
||||
|
||||
@@ -46,19 +46,15 @@ fn adapt_to_json_schema_subset(json: &mut Value) -> Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
const KEYS_TO_REMOVE: [(&str, fn(&Value) -> bool); 5] = [
|
||||
("format", |value| value.is_string()),
|
||||
("additionalProperties", |value| value.is_boolean()),
|
||||
("exclusiveMinimum", |value| value.is_number()),
|
||||
("exclusiveMaximum", |value| value.is_number()),
|
||||
("optional", |value| value.is_boolean()),
|
||||
const KEYS_TO_REMOVE: [&str; 5] = [
|
||||
"format",
|
||||
"additionalProperties",
|
||||
"exclusiveMinimum",
|
||||
"exclusiveMaximum",
|
||||
"optional",
|
||||
];
|
||||
for (key, predicate) in KEYS_TO_REMOVE {
|
||||
if let Some(value) = obj.get(key) {
|
||||
if predicate(value) {
|
||||
obj.remove(key);
|
||||
}
|
||||
}
|
||||
for key in KEYS_TO_REMOVE {
|
||||
obj.remove(key);
|
||||
}
|
||||
|
||||
// If a type is not specified for an input parameter, add a default type
|
||||
@@ -157,24 +153,6 @@ mod tests {
|
||||
"type": "integer"
|
||||
})
|
||||
);
|
||||
|
||||
// Ensure that we do not remove keys that are actually supported (e.g. "format" can just be used as another property)
|
||||
let mut json = json!({
|
||||
"description": "A test field",
|
||||
"type": "integer",
|
||||
"format": {},
|
||||
});
|
||||
|
||||
adapt_to_json_schema_subset(&mut json).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
json,
|
||||
json!({
|
||||
"description": "A test field",
|
||||
"type": "integer",
|
||||
"format": {},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -80,7 +80,6 @@ rand.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
reqwest_client.workspace = true
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
smol.workspace = true
|
||||
task = { workspace = true, features = ["test-support"]}
|
||||
tempfile.workspace = true
|
||||
theme.workspace = true
|
||||
|
||||
@@ -11,7 +11,7 @@ use client::{Client, UserStore};
|
||||
use collections::HashMap;
|
||||
use fs::FakeFs;
|
||||
use futures::{FutureExt, future::LocalBoxFuture};
|
||||
use gpui::{AppContext, TestAppContext, Timer};
|
||||
use gpui::{AppContext, TestAppContext};
|
||||
use indoc::{formatdoc, indoc};
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequestTool, LanguageModelToolResult,
|
||||
@@ -1255,12 +1255,9 @@ impl EvalAssertion {
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
let mut response = retry_on_rate_limit(async || {
|
||||
Ok(judge
|
||||
.stream_completion_text(request.clone(), &cx.to_async())
|
||||
.await?)
|
||||
})
|
||||
.await?;
|
||||
let mut response = judge
|
||||
.stream_completion_text(request, &cx.to_async())
|
||||
.await?;
|
||||
let mut output = String::new();
|
||||
while let Some(chunk) = response.stream.next().await {
|
||||
let chunk = chunk?;
|
||||
@@ -1311,17 +1308,10 @@ fn eval(
|
||||
run_eval(eval.clone(), tx.clone());
|
||||
|
||||
let executor = gpui::background_executor();
|
||||
let semaphore = Arc::new(smol::lock::Semaphore::new(32));
|
||||
for _ in 1..iterations {
|
||||
let eval = eval.clone();
|
||||
let tx = tx.clone();
|
||||
let semaphore = semaphore.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let _guard = semaphore.acquire().await;
|
||||
run_eval(eval, tx)
|
||||
})
|
||||
.detach();
|
||||
executor.spawn(async move { run_eval(eval, tx) }).detach();
|
||||
}
|
||||
drop(tx);
|
||||
|
||||
@@ -1587,31 +1577,21 @@ impl EditAgentTest {
|
||||
if let Some(input_content) = eval.input_content.as_deref() {
|
||||
buffer.update(cx, |buffer, cx| buffer.set_text(input_content, cx));
|
||||
}
|
||||
retry_on_rate_limit(async || {
|
||||
self.agent
|
||||
.edit(
|
||||
buffer.clone(),
|
||||
eval.edit_file_input.display_description.clone(),
|
||||
&conversation,
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.0
|
||||
.await
|
||||
})
|
||||
.await?
|
||||
let (edit_output, _) = self.agent.edit(
|
||||
buffer.clone(),
|
||||
eval.edit_file_input.display_description,
|
||||
&conversation,
|
||||
&mut cx.to_async(),
|
||||
);
|
||||
edit_output.await?
|
||||
} else {
|
||||
retry_on_rate_limit(async || {
|
||||
self.agent
|
||||
.overwrite(
|
||||
buffer.clone(),
|
||||
eval.edit_file_input.display_description.clone(),
|
||||
&conversation,
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.0
|
||||
.await
|
||||
})
|
||||
.await?
|
||||
let (edit_output, _) = self.agent.overwrite(
|
||||
buffer.clone(),
|
||||
eval.edit_file_input.display_description,
|
||||
&conversation,
|
||||
&mut cx.to_async(),
|
||||
);
|
||||
edit_output.await?
|
||||
};
|
||||
|
||||
let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text());
|
||||
@@ -1633,26 +1613,6 @@ impl EditAgentTest {
|
||||
}
|
||||
}
|
||||
|
||||
async fn retry_on_rate_limit<R>(mut request: impl AsyncFnMut() -> Result<R>) -> Result<R> {
|
||||
loop {
|
||||
match request().await {
|
||||
Ok(result) => return Ok(result),
|
||||
Err(err) => match err.downcast::<LanguageModelCompletionError>() {
|
||||
Ok(err) => match err {
|
||||
LanguageModelCompletionError::RateLimit(duration) => {
|
||||
// Wait until after we are allowed to try again
|
||||
eprintln!("Rate limit exceeded. Waiting for {duration:?}...",);
|
||||
Timer::after(duration).await;
|
||||
continue;
|
||||
}
|
||||
_ => return Err(err.into()),
|
||||
},
|
||||
Err(err) => return Err(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
struct EvalAssertionOutcome {
|
||||
score: usize,
|
||||
|
||||
@@ -638,36 +638,29 @@ impl ToolCard for TerminalToolCard {
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.rounded_b_md()
|
||||
.text_ui_sm(cx)
|
||||
.child({
|
||||
let content_mode = terminal.read(cx).content_mode(window, cx);
|
||||
|
||||
if content_mode.is_scrollable() {
|
||||
div().h_72().child(terminal.clone()).into_any_element()
|
||||
} else {
|
||||
ToolOutputPreview::new(
|
||||
terminal.clone().into_any_element(),
|
||||
terminal.entity_id(),
|
||||
)
|
||||
.with_total_lines(self.content_line_count)
|
||||
.toggle_state(!content_mode.is_limited())
|
||||
.on_toggle({
|
||||
let terminal = terminal.clone();
|
||||
move |is_expanded, _, cx| {
|
||||
terminal.update(cx, |terminal, cx| {
|
||||
terminal.set_embedded_mode(
|
||||
if is_expanded {
|
||||
None
|
||||
} else {
|
||||
Some(COLLAPSED_LINES)
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
}),
|
||||
.child(
|
||||
ToolOutputPreview::new(
|
||||
terminal.clone().into_any_element(),
|
||||
terminal.entity_id(),
|
||||
)
|
||||
.with_total_lines(self.content_line_count)
|
||||
.toggle_state(!terminal.read(cx).is_content_limited(window))
|
||||
.on_toggle({
|
||||
let terminal = terminal.clone();
|
||||
move |is_expanded, _, cx| {
|
||||
terminal.update(cx, |terminal, cx| {
|
||||
terminal.set_embedded_mode(
|
||||
if is_expanded {
|
||||
None
|
||||
} else {
|
||||
Some(COLLAPSED_LINES)
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -452,10 +452,6 @@ impl Model {
|
||||
| Model::Claude3_5SonnetV2
|
||||
| Model::Claude3_7Sonnet
|
||||
| Model::Claude3_7SonnetThinking
|
||||
| Model::ClaudeSonnet4
|
||||
| Model::ClaudeSonnet4Thinking
|
||||
| Model::ClaudeOpus4
|
||||
| Model::ClaudeOpus4Thinking
|
||||
| Model::Claude3Haiku
|
||||
| Model::Claude3Opus
|
||||
| Model::Claude3Sonnet
|
||||
|
||||
@@ -111,7 +111,7 @@ pub struct ChannelMembership {
|
||||
pub role: proto::ChannelRole,
|
||||
}
|
||||
impl ChannelMembership {
|
||||
pub fn sort_key(&self) -> MembershipSortKey<'_> {
|
||||
pub fn sort_key(&self) -> MembershipSortKey {
|
||||
MembershipSortKey {
|
||||
role_order: match self.role {
|
||||
proto::ChannelRole::Admin => 0,
|
||||
|
||||
@@ -32,7 +32,7 @@ impl ChannelIndex {
|
||||
.retain(|channel_id| !channels.contains(channel_id));
|
||||
}
|
||||
|
||||
pub fn bulk_insert(&mut self) -> ChannelPathsInsertGuard<'_> {
|
||||
pub fn bulk_insert(&mut self) -> ChannelPathsInsertGuard {
|
||||
ChannelPathsInsertGuard {
|
||||
channels_ordered: &mut self.channels_ordered,
|
||||
channels_by_id: &mut self.channels_by_id,
|
||||
|
||||
@@ -39,7 +39,7 @@ enum ProxyType<'t> {
|
||||
HttpProxy(HttpProxyType<'t>),
|
||||
}
|
||||
|
||||
fn parse_proxy_type(proxy: &Url) -> Option<((String, u16), ProxyType<'_>)> {
|
||||
fn parse_proxy_type(proxy: &Url) -> Option<((String, u16), ProxyType)> {
|
||||
let scheme = proxy.scheme();
|
||||
let host = proxy.host()?.to_string();
|
||||
let port = proxy.port_or_known_default()?;
|
||||
|
||||
@@ -501,10 +501,8 @@ impl Database {
|
||||
|
||||
/// Returns all channels for the user with the given ID.
|
||||
pub async fn get_channels_for_user(&self, user_id: UserId) -> Result<ChannelsForUser> {
|
||||
self.weak_transaction(
|
||||
|tx| async move { self.get_user_channels(user_id, None, true, &tx).await },
|
||||
)
|
||||
.await
|
||||
self.transaction(|tx| async move { self.get_user_channels(user_id, None, true, &tx).await })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns all channels for the user with the given ID that are descendants
|
||||
|
||||
@@ -15,7 +15,7 @@ impl Database {
|
||||
user_b_busy: bool,
|
||||
}
|
||||
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let user_a_participant = Alias::new("user_a_participant");
|
||||
let user_b_participant = Alias::new("user_b_participant");
|
||||
let mut db_contacts = contact::Entity::find()
|
||||
@@ -91,7 +91,7 @@ impl Database {
|
||||
|
||||
/// Returns whether the given user is a busy (on a call).
|
||||
pub async fn is_user_busy(&self, user_id: UserId) -> Result<bool> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let participant = room_participant::Entity::find()
|
||||
.filter(room_participant::Column::UserId.eq(user_id))
|
||||
.one(&*tx)
|
||||
|
||||
@@ -80,7 +80,7 @@ impl Database {
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<Option<proto::IncomingCall>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
self.transaction(|tx| async move {
|
||||
let pending_participant = room_participant::Entity::find()
|
||||
.filter(
|
||||
room_participant::Column::UserId
|
||||
|
||||
@@ -7,12 +7,6 @@ pub use token::*;
|
||||
|
||||
pub const AGENT_EXTENDED_TRIAL_FEATURE_FLAG: &str = "agent-extended-trial";
|
||||
|
||||
/// The name of the feature flag that bypasses the account age check.
|
||||
pub const BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG: &str = "bypass-account-age-check";
|
||||
|
||||
/// The minimum account age an account must have in order to use the LLM service.
|
||||
pub const MIN_ACCOUNT_AGE_FOR_LLM_USE: chrono::Duration = chrono::Duration::days(30);
|
||||
|
||||
/// The default value to use for maximum spend per month if the user did not
|
||||
/// explicitly set a maximum spend.
|
||||
///
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::db::billing_subscription::SubscriptionKind;
|
||||
use crate::db::{billing_customer, billing_subscription, user};
|
||||
use crate::llm::{AGENT_EXTENDED_TRIAL_FEATURE_FLAG, BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG};
|
||||
use crate::llm::AGENT_EXTENDED_TRIAL_FEATURE_FLAG;
|
||||
use crate::{Config, db::billing_preference};
|
||||
use anyhow::{Context as _, Result};
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
@@ -84,7 +84,7 @@ impl LlmTokenClaims {
|
||||
.any(|flag| flag == "llm-closed-beta"),
|
||||
bypass_account_age_check: feature_flags
|
||||
.iter()
|
||||
.any(|flag| flag == BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG),
|
||||
.any(|flag| flag == "bypass-account-age-check"),
|
||||
can_use_web_search_tool: true,
|
||||
use_llm_request_queue: feature_flags.iter().any(|flag| flag == "llm-request-queue"),
|
||||
plan,
|
||||
|
||||
@@ -4,10 +4,7 @@ use crate::api::billing::find_or_create_billing_customer;
|
||||
use crate::api::{CloudflareIpCountryHeader, SystemIdHeader};
|
||||
use crate::db::billing_subscription::SubscriptionKind;
|
||||
use crate::llm::db::LlmDatabase;
|
||||
use crate::llm::{
|
||||
AGENT_EXTENDED_TRIAL_FEATURE_FLAG, BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG, LlmTokenClaims,
|
||||
MIN_ACCOUNT_AGE_FOR_LLM_USE,
|
||||
};
|
||||
use crate::llm::{AGENT_EXTENDED_TRIAL_FEATURE_FLAG, LlmTokenClaims};
|
||||
use crate::stripe_client::StripeCustomerId;
|
||||
use crate::{
|
||||
AppState, Error, Result, auth,
|
||||
@@ -68,7 +65,7 @@ use std::{
|
||||
rc::Rc,
|
||||
sync::{
|
||||
Arc, OnceLock,
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@@ -89,36 +86,10 @@ pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(15);
|
||||
const MESSAGE_COUNT_PER_PAGE: usize = 100;
|
||||
const MAX_MESSAGE_LEN: usize = 1024;
|
||||
const NOTIFICATION_COUNT_PER_PAGE: usize = 50;
|
||||
const MAX_CONCURRENT_CONNECTIONS: usize = 512;
|
||||
|
||||
static CONCURRENT_CONNECTIONS: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
type MessageHandler =
|
||||
Box<dyn Send + Sync + Fn(Box<dyn AnyTypedEnvelope>, Session) -> BoxFuture<'static, ()>>;
|
||||
|
||||
pub struct ConnectionGuard;
|
||||
|
||||
impl ConnectionGuard {
|
||||
pub fn try_acquire() -> Result<Self, ()> {
|
||||
let current_connections = CONCURRENT_CONNECTIONS.fetch_add(1, SeqCst);
|
||||
if current_connections >= MAX_CONCURRENT_CONNECTIONS {
|
||||
CONCURRENT_CONNECTIONS.fetch_sub(1, SeqCst);
|
||||
tracing::error!(
|
||||
"too many concurrent connections: {}",
|
||||
current_connections + 1
|
||||
);
|
||||
return Err(());
|
||||
}
|
||||
Ok(ConnectionGuard)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ConnectionGuard {
|
||||
fn drop(&mut self) {
|
||||
CONCURRENT_CONNECTIONS.fetch_sub(1, SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
struct Response<R> {
|
||||
peer: Arc<Peer>,
|
||||
receipt: Receipt<R>,
|
||||
@@ -751,7 +722,6 @@ impl Server {
|
||||
system_id: Option<String>,
|
||||
send_connection_id: Option<oneshot::Sender<ConnectionId>>,
|
||||
executor: Executor,
|
||||
connection_guard: Option<ConnectionGuard>,
|
||||
) -> impl Future<Output = ()> + use<> {
|
||||
let this = self.clone();
|
||||
let span = info_span!("handle connection", %address,
|
||||
@@ -772,7 +742,6 @@ impl Server {
|
||||
tracing::error!("server is tearing down");
|
||||
return
|
||||
}
|
||||
|
||||
let (connection_id, handle_io, mut incoming_rx) = this
|
||||
.peer
|
||||
.add_connection(connection, {
|
||||
@@ -814,7 +783,6 @@ impl Server {
|
||||
tracing::error!(?error, "failed to send initial client update");
|
||||
return;
|
||||
}
|
||||
drop(connection_guard);
|
||||
|
||||
let handle_io = handle_io.fuse();
|
||||
futures::pin_mut!(handle_io);
|
||||
@@ -1186,19 +1154,6 @@ pub async fn handle_websocket_request(
|
||||
}
|
||||
|
||||
let socket_address = socket_address.to_string();
|
||||
|
||||
// Acquire connection guard before WebSocket upgrade
|
||||
let connection_guard = match ConnectionGuard::try_acquire() {
|
||||
Ok(guard) => guard,
|
||||
Err(()) => {
|
||||
return (
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
"Too many concurrent connections",
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
|
||||
ws.on_upgrade(move |socket| {
|
||||
let socket = socket
|
||||
.map_ok(to_tungstenite_message)
|
||||
@@ -1216,7 +1171,6 @@ pub async fn handle_websocket_request(
|
||||
system_id_header.map(|header| header.to_string()),
|
||||
None,
|
||||
Executor::Production,
|
||||
Some(connection_guard),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -2819,12 +2773,8 @@ async fn make_update_user_plan_message(
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let bypass_account_age_check = feature_flags
|
||||
.iter()
|
||||
.any(|flag| flag == BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG);
|
||||
let account_too_young = !matches!(plan, proto::Plan::ZedPro)
|
||||
&& !bypass_account_age_check
|
||||
&& user.account_age() < MIN_ACCOUNT_AGE_FOR_LLM_USE;
|
||||
let account_too_young =
|
||||
!matches!(plan, proto::Plan::ZedPro) && user.account_age() < MIN_ACCOUNT_AGE_FOR_LLM_USE;
|
||||
|
||||
Ok(proto::UpdateUserPlan {
|
||||
plan: plan.into(),
|
||||
@@ -4125,6 +4075,9 @@ async fn accept_terms_of_service(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The minimum account age an account must have in order to use the LLM service.
|
||||
pub const MIN_ACCOUNT_AGE_FOR_LLM_USE: chrono::Duration = chrono::Duration::days(30);
|
||||
|
||||
async fn get_llm_api_token(
|
||||
_request: proto::GetLlmToken,
|
||||
response: Response<proto::GetLlmToken>,
|
||||
|
||||
@@ -671,7 +671,7 @@ async fn test_remote_server_debugger(
|
||||
});
|
||||
|
||||
session.update(cx_a, |session, _| {
|
||||
assert_eq!(session.binary().command.as_deref(), Some("ssh"));
|
||||
assert_eq!(session.binary().command, "ssh");
|
||||
});
|
||||
|
||||
let shutdown_session = workspace.update(cx_a, |workspace, cx| {
|
||||
|
||||
@@ -258,7 +258,6 @@ impl TestServer {
|
||||
None,
|
||||
Some(connection_id_tx),
|
||||
Executor::Deterministic(cx.background_executor().clone()),
|
||||
None,
|
||||
))
|
||||
.detach();
|
||||
let connection_id = connection_id_rx.await.map_err(|e| {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::client::Client;
|
||||
use crate::types::{self, Notification, Request};
|
||||
use crate::types::{self, Request};
|
||||
|
||||
pub struct ModelContextProtocol {
|
||||
inner: Client,
|
||||
@@ -20,10 +20,9 @@ impl ModelContextProtocol {
|
||||
}
|
||||
|
||||
fn supported_protocols() -> Vec<types::ProtocolVersion> {
|
||||
vec![
|
||||
types::ProtocolVersion(types::LATEST_PROTOCOL_VERSION.to_string()),
|
||||
types::ProtocolVersion(types::VERSION_2024_11_05.to_string()),
|
||||
]
|
||||
vec![types::ProtocolVersion(
|
||||
types::LATEST_PROTOCOL_VERSION.to_string(),
|
||||
)]
|
||||
}
|
||||
|
||||
pub async fn initialize(
|
||||
@@ -43,7 +42,7 @@ impl ModelContextProtocol {
|
||||
|
||||
let response: types::InitializeResponse = self
|
||||
.inner
|
||||
.request(types::requests::Initialize::METHOD, params)
|
||||
.request(types::request::Initialize::METHOD, params)
|
||||
.await?;
|
||||
|
||||
anyhow::ensure!(
|
||||
@@ -54,13 +53,16 @@ impl ModelContextProtocol {
|
||||
|
||||
log::trace!("mcp server info {:?}", response.server_info);
|
||||
|
||||
self.inner.notify(
|
||||
types::NotificationType::Initialized.as_str(),
|
||||
serde_json::json!({}),
|
||||
)?;
|
||||
|
||||
let initialized_protocol = InitializedContextServerProtocol {
|
||||
inner: self.inner,
|
||||
initialize: response,
|
||||
};
|
||||
|
||||
initialized_protocol.notify::<types::notifications::Initialized>(())?;
|
||||
|
||||
Ok(initialized_protocol)
|
||||
}
|
||||
}
|
||||
@@ -94,8 +96,4 @@ impl InitializedContextServerProtocol {
|
||||
pub async fn request<T: Request>(&self, params: T::Params) -> Result<T::Response> {
|
||||
self.inner.request(T::METHOD, params).await
|
||||
}
|
||||
|
||||
pub fn notify<T: Notification>(&self, params: T::Params) -> Result<()> {
|
||||
self.inner.notify(T::METHOD, params)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ pub fn create_fake_transport(
|
||||
executor: BackgroundExecutor,
|
||||
) -> FakeTransport {
|
||||
let name = name.into();
|
||||
FakeTransport::new(executor).on_request::<crate::types::requests::Initialize>(move |_params| {
|
||||
FakeTransport::new(executor).on_request::<crate::types::request::Initialize>(move |_params| {
|
||||
create_initialize_response(name.clone())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@ use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
pub const LATEST_PROTOCOL_VERSION: &str = "2025-03-26";
|
||||
pub const VERSION_2024_11_05: &str = "2024-11-05";
|
||||
pub const LATEST_PROTOCOL_VERSION: &str = "2024-11-05";
|
||||
|
||||
pub mod requests {
|
||||
pub mod request {
|
||||
use super::*;
|
||||
|
||||
macro_rules! request {
|
||||
@@ -83,57 +82,6 @@ pub trait Request {
|
||||
const METHOD: &'static str;
|
||||
}
|
||||
|
||||
pub mod notifications {
|
||||
use super::*;
|
||||
|
||||
macro_rules! notification {
|
||||
($method:expr, $name:ident, $params:ty) => {
|
||||
pub struct $name;
|
||||
|
||||
impl Notification for $name {
|
||||
type Params = $params;
|
||||
const METHOD: &'static str = $method;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
notification!("notifications/initialized", Initialized, ());
|
||||
notification!("notifications/progress", Progress, ProgressParams);
|
||||
notification!("notifications/message", Message, MessageParams);
|
||||
notification!(
|
||||
"notifications/resources/updated",
|
||||
ResourcesUpdated,
|
||||
ResourcesUpdatedParams
|
||||
);
|
||||
notification!(
|
||||
"notifications/resources/list_changed",
|
||||
ResourcesListChanged,
|
||||
()
|
||||
);
|
||||
notification!("notifications/tools/list_changed", ToolsListChanged, ());
|
||||
notification!("notifications/prompts/list_changed", PromptsListChanged, ());
|
||||
notification!("notifications/roots/list_changed", RootsListChanged, ());
|
||||
}
|
||||
|
||||
pub trait Notification {
|
||||
type Params: DeserializeOwned + Serialize + Send + Sync + 'static;
|
||||
const METHOD: &'static str;
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageParams {
|
||||
pub level: LoggingLevel,
|
||||
pub logger: Option<String>,
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResourcesUpdatedParams {
|
||||
pub uri: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProtocolVersion(pub String);
|
||||
@@ -343,20 +291,13 @@ pub enum MessageContent {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
annotations: Option<MessageAnnotations>,
|
||||
},
|
||||
#[serde(rename = "image", rename_all = "camelCase")]
|
||||
#[serde(rename = "image")]
|
||||
Image {
|
||||
data: String,
|
||||
mime_type: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
annotations: Option<MessageAnnotations>,
|
||||
},
|
||||
#[serde(rename = "audio", rename_all = "camelCase")]
|
||||
Audio {
|
||||
data: String,
|
||||
mime_type: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
annotations: Option<MessageAnnotations>,
|
||||
},
|
||||
#[serde(rename = "resource")]
|
||||
Resource {
|
||||
resource: ResourceContents,
|
||||
@@ -453,8 +394,6 @@ pub struct ServerCapabilities {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub logging: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub completions: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub prompts: Option<PromptsCapabilities>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resources: Option<ResourcesCapabilities>,
|
||||
@@ -499,28 +438,6 @@ pub struct Tool {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
pub input_schema: serde_json::Value,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub annotations: Option<ToolAnnotations>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ToolAnnotations {
|
||||
/// A human-readable title for the tool.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
/// If true, the tool does not modify its environment.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub read_only_hint: Option<bool>,
|
||||
/// If true, the tool may perform destructive updates to its environment.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub destructive_hint: Option<bool>,
|
||||
/// If true, calling the tool repeatedly with the same arguments will have no additional effect on its environment.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub idempotent_hint: Option<bool>,
|
||||
/// If true, this tool may interact with an "open world" of external entities.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub open_world_hint: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -611,6 +528,34 @@ pub struct ModelHint {
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum NotificationType {
|
||||
Initialized,
|
||||
Progress,
|
||||
Message,
|
||||
ResourcesUpdated,
|
||||
ResourcesListChanged,
|
||||
ToolsListChanged,
|
||||
PromptsListChanged,
|
||||
RootsListChanged,
|
||||
}
|
||||
|
||||
impl NotificationType {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
NotificationType::Initialized => "notifications/initialized",
|
||||
NotificationType::Progress => "notifications/progress",
|
||||
NotificationType::Message => "notifications/message",
|
||||
NotificationType::ResourcesUpdated => "notifications/resources/updated",
|
||||
NotificationType::ResourcesListChanged => "notifications/resources/list_changed",
|
||||
NotificationType::ToolsListChanged => "notifications/tools/list_changed",
|
||||
NotificationType::PromptsListChanged => "notifications/prompts/list_changed",
|
||||
NotificationType::RootsListChanged => "notifications/roots/list_changed",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ClientNotification {
|
||||
@@ -631,14 +576,12 @@ pub enum ProgressToken {
|
||||
Number(f64),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProgressParams {
|
||||
pub progress_token: ProgressToken,
|
||||
pub progress: f64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub total: Option<f64>,
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<HashMap<String, serde_json::Value>>,
|
||||
@@ -682,8 +625,6 @@ pub enum ToolResponseContent {
|
||||
Text { text: String },
|
||||
#[serde(rename = "image", rename_all = "camelCase")]
|
||||
Image { data: String, mime_type: String },
|
||||
#[serde(rename = "audio", rename_all = "camelCase")]
|
||||
Audio { data: String, mime_type: String },
|
||||
#[serde(rename = "resource")]
|
||||
Resource { resource: ResourceContents },
|
||||
}
|
||||
|
||||
@@ -408,30 +408,24 @@ impl Copilot {
|
||||
let proxy_url = copilot_settings.proxy.clone()?;
|
||||
let no_verify = copilot_settings.proxy_no_verify;
|
||||
let http_or_https_proxy = if proxy_url.starts_with("http:") {
|
||||
Some("HTTP_PROXY")
|
||||
"HTTP_PROXY"
|
||||
} else if proxy_url.starts_with("https:") {
|
||||
Some("HTTPS_PROXY")
|
||||
"HTTPS_PROXY"
|
||||
} else {
|
||||
log::error!(
|
||||
"Unsupported protocol scheme for language server proxy (must be http or https)"
|
||||
);
|
||||
None
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut env = HashMap::default();
|
||||
env.insert(http_or_https_proxy.to_string(), proxy_url);
|
||||
|
||||
if let Some(proxy_type) = http_or_https_proxy {
|
||||
env.insert(proxy_type.to_string(), proxy_url);
|
||||
if let Some(true) = no_verify {
|
||||
env.insert("NODE_TLS_REJECT_UNAUTHORIZED".to_string(), "0".to_string());
|
||||
};
|
||||
}
|
||||
if let Some(true) = no_verify {
|
||||
env.insert("NODE_TLS_REJECT_UNAUTHORIZED".to_string(), "0".to_string());
|
||||
};
|
||||
|
||||
if let Ok(oauth_token) = env::var(copilot_chat::COPILOT_OAUTH_ENV_VAR) {
|
||||
env.insert(copilot_chat::COPILOT_OAUTH_ENV_VAR.to_string(), oauth_token);
|
||||
}
|
||||
|
||||
if env.is_empty() { None } else { Some(env) }
|
||||
Some(env)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
||||
@@ -16,8 +16,6 @@ use paths::home_dir;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::watch_config_dir;
|
||||
|
||||
pub const COPILOT_OAUTH_ENV_VAR: &str = "GH_COPILOT_TOKEN";
|
||||
|
||||
#[derive(Default, Clone, Debug, PartialEq)]
|
||||
pub struct CopilotChatSettings {
|
||||
pub api_url: Arc<str>,
|
||||
@@ -407,19 +405,13 @@ impl CopilotChat {
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
let this = Self {
|
||||
oauth_token: std::env::var(COPILOT_OAUTH_ENV_VAR).ok(),
|
||||
Self {
|
||||
oauth_token: None,
|
||||
api_token: None,
|
||||
models: None,
|
||||
settings,
|
||||
client,
|
||||
};
|
||||
if this.oauth_token.is_some() {
|
||||
cx.spawn(async move |this, mut cx| Self::update_models(&this, &mut cx).await)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
async fn update_models(this: &WeakEntity<Self>, cx: &mut AsyncApp) -> Result<()> {
|
||||
|
||||
@@ -51,9 +51,6 @@ telemetry.workspace = true
|
||||
util.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
libc.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
async-pipe.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -181,7 +181,7 @@ impl DebugTaskDefinition {
|
||||
/// Created from a [DebugTaskDefinition], this struct describes how to spawn the debugger to create a previously-configured debug session.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DebugAdapterBinary {
|
||||
pub command: Option<String>,
|
||||
pub command: String,
|
||||
pub arguments: Vec<String>,
|
||||
pub envs: HashMap<String, String>,
|
||||
pub cwd: Option<PathBuf>,
|
||||
@@ -369,10 +369,6 @@ pub trait DebugAdapter: 'static + Send + Sync {
|
||||
}
|
||||
|
||||
async fn dap_schema(&self) -> serde_json::Value;
|
||||
|
||||
fn label_for_child_session(&self, _args: &StartDebuggingRequestArguments) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -437,7 +433,7 @@ impl DebugAdapter for FakeAdapter {
|
||||
_: &mut AsyncApp,
|
||||
) -> Result<DebugAdapterBinary> {
|
||||
Ok(DebugAdapterBinary {
|
||||
command: Some("command".into()),
|
||||
command: "command".into(),
|
||||
arguments: vec![],
|
||||
connection: None,
|
||||
envs: HashMap::default(),
|
||||
|
||||
@@ -297,7 +297,7 @@ mod tests {
|
||||
let client = DebugAdapterClient::start(
|
||||
crate::client::SessionId(1),
|
||||
DebugAdapterBinary {
|
||||
command: Some("command".into()),
|
||||
command: "command".into(),
|
||||
arguments: Default::default(),
|
||||
envs: Default::default(),
|
||||
connection: None,
|
||||
@@ -367,7 +367,7 @@ mod tests {
|
||||
let client = DebugAdapterClient::start(
|
||||
crate::client::SessionId(1),
|
||||
DebugAdapterBinary {
|
||||
command: Some("command".into()),
|
||||
command: "command".into(),
|
||||
arguments: Default::default(),
|
||||
envs: Default::default(),
|
||||
connection: None,
|
||||
@@ -420,7 +420,7 @@ mod tests {
|
||||
let client = DebugAdapterClient::start(
|
||||
crate::client::SessionId(1),
|
||||
DebugAdapterBinary {
|
||||
command: Some("command".into()),
|
||||
command: "command".into(),
|
||||
arguments: Default::default(),
|
||||
envs: Default::default(),
|
||||
connection: None,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use dap_types::{
|
||||
ErrorResponse,
|
||||
messages::{Message, Response},
|
||||
@@ -12,6 +12,7 @@ use smol::{
|
||||
io::{AsyncBufReadExt as _, AsyncWriteExt, BufReader},
|
||||
lock::Mutex,
|
||||
net::{TcpListener, TcpStream},
|
||||
process::Child,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@@ -21,7 +22,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use task::TcpArgumentsTemplate;
|
||||
use util::ConnectionResult;
|
||||
use util::{ConnectionResult, ResultExt as _};
|
||||
|
||||
use crate::{adapters::DebugAdapterBinary, debugger_settings::DebuggerSettings};
|
||||
|
||||
@@ -85,12 +86,10 @@ impl Transport {
|
||||
TcpTransport::start(binary, cx)
|
||||
.await
|
||||
.map(|(transports, tcp)| (transports, Self::Tcp(tcp)))
|
||||
.context("Tried to connect to a debug adapter via TCP transport layer")
|
||||
} else {
|
||||
StdioTransport::start(binary, cx)
|
||||
.await
|
||||
.map(|(transports, stdio)| (transports, Self::Stdio(stdio)))
|
||||
.context("Tried to connect to a debug adapter via stdin/stdout transport layer")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +102,7 @@ impl Transport {
|
||||
}
|
||||
}
|
||||
|
||||
async fn kill(&self) {
|
||||
async fn kill(&self) -> Result<()> {
|
||||
match self {
|
||||
Transport::Stdio(stdio_transport) => stdio_transport.kill().await,
|
||||
Transport::Tcp(tcp_transport) => tcp_transport.kill().await,
|
||||
@@ -192,7 +191,7 @@ impl TransportDelegate {
|
||||
match Self::handle_output(
|
||||
params.output,
|
||||
client_tx,
|
||||
pending_requests.clone(),
|
||||
pending_requests,
|
||||
output_log_handler,
|
||||
)
|
||||
.await
|
||||
@@ -200,12 +199,6 @@ impl TransportDelegate {
|
||||
Ok(()) => {}
|
||||
Err(e) => log::error!("Error handling debugger output: {e}"),
|
||||
}
|
||||
let mut pending_requests = pending_requests.lock().await;
|
||||
pending_requests.drain().for_each(|(_, request)| {
|
||||
request
|
||||
.send(Err(anyhow!("debugger shutdown unexpectedly")))
|
||||
.ok();
|
||||
});
|
||||
}));
|
||||
|
||||
if let Some(stderr) = params.stderr.take() {
|
||||
@@ -538,7 +531,7 @@ impl TransportDelegate {
|
||||
current_requests.clear();
|
||||
pending_requests.clear();
|
||||
|
||||
self.transport.kill().await;
|
||||
let _ = self.transport.kill().await.log_err();
|
||||
|
||||
drop(current_requests);
|
||||
drop(pending_requests);
|
||||
@@ -569,7 +562,7 @@ pub struct TcpTransport {
|
||||
pub port: u16,
|
||||
pub host: Ipv4Addr,
|
||||
pub timeout: u64,
|
||||
process: Option<Mutex<Child>>,
|
||||
process: Mutex<Child>,
|
||||
}
|
||||
|
||||
impl TcpTransport {
|
||||
@@ -598,23 +591,26 @@ impl TcpTransport {
|
||||
let host = connection_args.host;
|
||||
let port = connection_args.port;
|
||||
|
||||
let mut process = if let Some(command) = &binary.command {
|
||||
let mut command = util::command::new_std_command(&command);
|
||||
let mut command = util::command::new_std_command(&binary.command);
|
||||
util::set_pre_exec_to_start_new_session(&mut command);
|
||||
let mut command = smol::process::Command::from(command);
|
||||
|
||||
if let Some(cwd) = &binary.cwd {
|
||||
command.current_dir(cwd);
|
||||
}
|
||||
if let Some(cwd) = &binary.cwd {
|
||||
command.current_dir(cwd);
|
||||
}
|
||||
|
||||
command.args(&binary.arguments);
|
||||
command.envs(&binary.envs);
|
||||
command.args(&binary.arguments);
|
||||
command.envs(&binary.envs);
|
||||
|
||||
Some(
|
||||
Child::spawn(command, Stdio::null())
|
||||
.with_context(|| "failed to start debug adapter.")?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
command
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.kill_on_drop(true);
|
||||
|
||||
let mut process = command
|
||||
.spawn()
|
||||
.with_context(|| "failed to start debug adapter.")?;
|
||||
|
||||
let address = SocketAddrV4::new(host, port);
|
||||
|
||||
@@ -632,18 +628,15 @@ impl TcpTransport {
|
||||
match TcpStream::connect(address).await {
|
||||
Ok(stream) => return Ok((process, stream.split())),
|
||||
Err(_) => {
|
||||
if let Some(p) = &mut process {
|
||||
if let Ok(Some(_)) = p.try_status() {
|
||||
let output = process.take().unwrap().into_inner().output().await?;
|
||||
let output = if output.stderr.is_empty() {
|
||||
String::from_utf8_lossy(&output.stdout).to_string()
|
||||
} else {
|
||||
String::from_utf8_lossy(&output.stderr).to_string()
|
||||
};
|
||||
anyhow::bail!("{output}\nerror: process exited before debugger attached.");
|
||||
}
|
||||
if let Ok(Some(_)) = process.try_status() {
|
||||
let output = process.output().await?;
|
||||
let output = if output.stderr.is_empty() {
|
||||
String::from_utf8_lossy(&output.stdout).to_string()
|
||||
} else {
|
||||
String::from_utf8_lossy(&output.stderr).to_string()
|
||||
};
|
||||
anyhow::bail!("{output}\nerror: process exited before debugger attached.");
|
||||
}
|
||||
|
||||
cx.background_executor().timer(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
@@ -656,13 +649,13 @@ impl TcpTransport {
|
||||
host,
|
||||
port
|
||||
);
|
||||
let stdout = process.as_mut().and_then(|p| p.stdout.take());
|
||||
let stderr = process.as_mut().and_then(|p| p.stderr.take());
|
||||
let stdout = process.stdout.take();
|
||||
let stderr = process.stderr.take();
|
||||
|
||||
let this = Self {
|
||||
port,
|
||||
host,
|
||||
process: process.map(Mutex::new),
|
||||
process: Mutex::new(process),
|
||||
timeout,
|
||||
};
|
||||
|
||||
@@ -680,19 +673,10 @@ impl TcpTransport {
|
||||
true
|
||||
}
|
||||
|
||||
async fn kill(&self) {
|
||||
if let Some(process) = &self.process {
|
||||
let mut process = process.lock().await;
|
||||
Child::kill(&mut process);
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn kill(&self) -> Result<()> {
|
||||
self.process.lock().await.kill()?;
|
||||
|
||||
impl Drop for TcpTransport {
|
||||
fn drop(&mut self) {
|
||||
if let Some(mut p) = self.process.take() {
|
||||
p.get_mut().kill();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,12 +687,9 @@ pub struct StdioTransport {
|
||||
impl StdioTransport {
|
||||
#[allow(dead_code, reason = "This is used in non test builds of Zed")]
|
||||
async fn start(binary: &DebugAdapterBinary, _: AsyncApp) -> Result<(TransportPipe, Self)> {
|
||||
let Some(binary_command) = &binary.command else {
|
||||
bail!(
|
||||
"When using the `stdio` transport, the path to a debug adapter binary must be set by Zed."
|
||||
);
|
||||
};
|
||||
let mut command = util::command::new_std_command(&binary_command);
|
||||
let mut command = util::command::new_std_command(&binary.command);
|
||||
util::set_pre_exec_to_start_new_session(&mut command);
|
||||
let mut command = smol::process::Command::from(command);
|
||||
|
||||
if let Some(cwd) = &binary.cwd {
|
||||
command.current_dir(cwd);
|
||||
@@ -717,10 +698,16 @@ impl StdioTransport {
|
||||
command.args(&binary.arguments);
|
||||
command.envs(&binary.envs);
|
||||
|
||||
let mut process = Child::spawn(command, Stdio::piped()).with_context(|| {
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.kill_on_drop(true);
|
||||
|
||||
let mut process = command.spawn().with_context(|| {
|
||||
format!(
|
||||
"failed to spawn command `{} {}`.",
|
||||
binary_command,
|
||||
binary.command,
|
||||
binary.arguments.join(" ")
|
||||
)
|
||||
})?;
|
||||
@@ -735,7 +722,7 @@ impl StdioTransport {
|
||||
if stderr.is_none() {
|
||||
bail!(
|
||||
"Failed to connect to stderr for debug adapter command {}",
|
||||
&binary_command
|
||||
&binary.command
|
||||
);
|
||||
}
|
||||
|
||||
@@ -758,15 +745,9 @@ impl StdioTransport {
|
||||
false
|
||||
}
|
||||
|
||||
async fn kill(&self) {
|
||||
let mut process = self.process.lock().await;
|
||||
Child::kill(&mut process);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for StdioTransport {
|
||||
fn drop(&mut self) {
|
||||
self.process.get_mut().kill();
|
||||
async fn kill(&self) -> Result<()> {
|
||||
self.process.lock().await.kill()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -940,66 +921,7 @@ impl FakeTransport {
|
||||
false
|
||||
}
|
||||
|
||||
async fn kill(&self) {}
|
||||
}
|
||||
|
||||
struct Child {
|
||||
process: smol::process::Child,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Child {
|
||||
type Target = smol::process::Child;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.process
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for Child {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.process
|
||||
}
|
||||
}
|
||||
|
||||
impl Child {
|
||||
fn into_inner(self) -> smol::process::Child {
|
||||
self.process
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn spawn(mut command: std::process::Command, stdin: Stdio) -> Result<Self> {
|
||||
util::set_pre_exec_to_start_new_session(&mut command);
|
||||
let process = smol::process::Command::from(command)
|
||||
.stdin(stdin)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
Ok(Self { process })
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn spawn(command: std::process::Command, stdin: Stdio) -> Result<Self> {
|
||||
// TODO(windows): create a job object and add the child process handle to it,
|
||||
// see https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects
|
||||
let process = smol::process::Command::from(command)
|
||||
.stdin(stdin)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
Ok(Self { process })
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn kill(&mut self) {
|
||||
let pid = self.process.id();
|
||||
unsafe {
|
||||
libc::killpg(pid as i32, libc::SIGKILL);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn kill(&mut self) {
|
||||
// TODO(windows): terminate the job object in kill
|
||||
let _ = self.process.kill();
|
||||
async fn kill(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ doctest = false
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
collections.workspace = true
|
||||
dap.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
|
||||
@@ -21,21 +21,18 @@ impl CodeLldbDebugAdapter {
|
||||
|
||||
fn request_args(
|
||||
&self,
|
||||
delegate: &Arc<dyn DapDelegate>,
|
||||
task_definition: &DebugTaskDefinition,
|
||||
) -> Result<dap::StartDebuggingRequestArguments> {
|
||||
// CodeLLDB uses `name` for a terminal label.
|
||||
let mut configuration = task_definition.config.clone();
|
||||
|
||||
let obj = configuration
|
||||
configuration
|
||||
.as_object_mut()
|
||||
.context("CodeLLDB is not a valid json object")?;
|
||||
|
||||
obj.entry("name")
|
||||
.or_insert(Value::String(String::from(task_definition.label.as_ref())));
|
||||
|
||||
obj.entry("cwd")
|
||||
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
|
||||
.context("CodeLLDB is not a valid json object")?
|
||||
.insert(
|
||||
"name".into(),
|
||||
Value::String(String::from(task_definition.label.as_ref())),
|
||||
);
|
||||
|
||||
let request = self.request_kind(&configuration)?;
|
||||
|
||||
@@ -362,13 +359,13 @@ impl DebugAdapter for CodeLldbDebugAdapter {
|
||||
};
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: Some(command.unwrap()),
|
||||
command: command.unwrap(),
|
||||
cwd: Some(delegate.worktree_root_path().to_path_buf()),
|
||||
arguments: vec![
|
||||
"--settings".into(),
|
||||
json!({"sourceLanguages": ["cpp", "rust"]}).to_string(),
|
||||
],
|
||||
request_args: self.request_args(delegate, &config)?,
|
||||
request_args: self.request_args(&config)?,
|
||||
envs: HashMap::default(),
|
||||
connection: None,
|
||||
})
|
||||
|
||||
@@ -177,23 +177,18 @@ impl DebugAdapter for GdbDebugAdapter {
|
||||
|
||||
let gdb_path = user_setting_path.unwrap_or(gdb_path?);
|
||||
|
||||
let mut configuration = config.config.clone();
|
||||
if let Some(configuration) = configuration.as_object_mut() {
|
||||
configuration
|
||||
.entry("cwd")
|
||||
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
|
||||
}
|
||||
let request_args = StartDebuggingRequestArguments {
|
||||
request: self.request_kind(&config.config)?,
|
||||
configuration: config.config.clone(),
|
||||
};
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: Some(gdb_path),
|
||||
command: gdb_path,
|
||||
arguments: vec!["-i=dap".into()],
|
||||
envs: HashMap::default(),
|
||||
cwd: Some(delegate.worktree_root_path().to_path_buf()),
|
||||
connection: None,
|
||||
request_args: StartDebuggingRequestArguments {
|
||||
request: self.request_kind(&config.config)?,
|
||||
configuration,
|
||||
},
|
||||
request_args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,21 +462,14 @@ impl DebugAdapter for GoDebugAdapter {
|
||||
]
|
||||
};
|
||||
|
||||
let mut configuration = task_definition.config.clone();
|
||||
if let Some(configuration) = configuration.as_object_mut() {
|
||||
configuration
|
||||
.entry("cwd")
|
||||
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
|
||||
}
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: Some(minidelve_path.to_string_lossy().into_owned()),
|
||||
command: minidelve_path.to_string_lossy().into_owned(),
|
||||
arguments,
|
||||
cwd: Some(cwd),
|
||||
envs: HashMap::default(),
|
||||
connection: None,
|
||||
request_args: StartDebuggingRequestArguments {
|
||||
configuration,
|
||||
configuration: task_definition.config.clone(),
|
||||
request: self.request_kind(&task_definition.config)?,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@ use adapters::latest_github_release;
|
||||
use anyhow::Context as _;
|
||||
use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
|
||||
use gpui::AsyncApp;
|
||||
use serde_json::Value;
|
||||
use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
|
||||
use task::DebugRequest;
|
||||
use util::ResultExt;
|
||||
@@ -69,24 +68,13 @@ impl JsDebugAdapter {
|
||||
let tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default();
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
|
||||
let mut configuration = task_definition.config.clone();
|
||||
if let Some(configuration) = configuration.as_object_mut() {
|
||||
configuration
|
||||
.entry("cwd")
|
||||
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
|
||||
|
||||
configuration.entry("type").and_modify(normalize_task_type);
|
||||
}
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: Some(
|
||||
delegate
|
||||
.node_runtime()
|
||||
.binary_path()
|
||||
.await?
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
),
|
||||
command: delegate
|
||||
.node_runtime()
|
||||
.binary_path()
|
||||
.await?
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
arguments: vec![
|
||||
adapter_path
|
||||
.join(Self::ADAPTER_PATH)
|
||||
@@ -103,7 +91,7 @@ impl JsDebugAdapter {
|
||||
timeout,
|
||||
}),
|
||||
request_args: StartDebuggingRequestArguments {
|
||||
configuration,
|
||||
configuration: task_definition.config.clone(),
|
||||
request: self.request_kind(&task_definition.config)?,
|
||||
},
|
||||
})
|
||||
@@ -183,7 +171,7 @@ impl DebugAdapter for JsDebugAdapter {
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["pwa-node", "node", "chrome", "pwa-chrome", "msedge", "pwa-msedge"],
|
||||
"enum": ["pwa-node", "node", "chrome", "pwa-chrome", "edge", "pwa-edge"],
|
||||
"description": "The type of debug session",
|
||||
"default": "pwa-node"
|
||||
},
|
||||
@@ -443,25 +431,4 @@ impl DebugAdapter for JsDebugAdapter {
|
||||
self.get_installed_binary(delegate, &config, user_installed_path, cx)
|
||||
.await
|
||||
}
|
||||
|
||||
fn label_for_child_session(&self, args: &StartDebuggingRequestArguments) -> Option<String> {
|
||||
let label = args.configuration.get("name")?.as_str()?;
|
||||
Some(label.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_task_type(task_type: &mut Value) {
|
||||
let Some(task_type_str) = task_type.as_str() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let new_name = match task_type_str {
|
||||
"node" | "pwa-node" => "pwa-node",
|
||||
"chrome" | "pwa-chrome" => "pwa-chrome",
|
||||
"edge" | "msedge" | "pwa-edge" | "pwa-msedge" => "pwa-msedge",
|
||||
_ => task_type_str,
|
||||
}
|
||||
.to_owned();
|
||||
|
||||
*task_type = Value::String(new_name);
|
||||
}
|
||||
|
||||
@@ -71,21 +71,13 @@ impl PhpDebugAdapter {
|
||||
let tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default();
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
|
||||
let mut configuration = task_definition.config.clone();
|
||||
if let Some(obj) = configuration.as_object_mut() {
|
||||
obj.entry("cwd")
|
||||
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
|
||||
}
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: Some(
|
||||
delegate
|
||||
.node_runtime()
|
||||
.binary_path()
|
||||
.await?
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
),
|
||||
command: delegate
|
||||
.node_runtime()
|
||||
.binary_path()
|
||||
.await?
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
arguments: vec![
|
||||
adapter_path
|
||||
.join(Self::ADAPTER_PATH)
|
||||
@@ -101,7 +93,7 @@ impl PhpDebugAdapter {
|
||||
cwd: Some(delegate.worktree_root_path().to_path_buf()),
|
||||
envs: HashMap::default(),
|
||||
request_args: StartDebuggingRequestArguments {
|
||||
configuration,
|
||||
configuration: task_definition.config.clone(),
|
||||
request: <Self as DebugAdapter>::request_kind(self, &task_definition.config)?,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -83,7 +83,6 @@ impl PythonDebugAdapter {
|
||||
|
||||
fn request_args(
|
||||
&self,
|
||||
delegate: &Arc<dyn DapDelegate>,
|
||||
task_definition: &DebugTaskDefinition,
|
||||
) -> Result<StartDebuggingRequestArguments> {
|
||||
let request = self.request_kind(&task_definition.config)?;
|
||||
@@ -96,11 +95,6 @@ impl PythonDebugAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(obj) = configuration.as_object_mut() {
|
||||
obj.entry("cwd")
|
||||
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
|
||||
}
|
||||
|
||||
Ok(StartDebuggingRequestArguments {
|
||||
configuration,
|
||||
request,
|
||||
@@ -193,7 +187,7 @@ impl PythonDebugAdapter {
|
||||
);
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: Some(python_command),
|
||||
command: python_command,
|
||||
arguments,
|
||||
connection: Some(adapters::TcpArguments {
|
||||
host,
|
||||
@@ -202,7 +196,7 @@ impl PythonDebugAdapter {
|
||||
}),
|
||||
cwd: Some(delegate.worktree_root_path().to_path_buf()),
|
||||
envs: HashMap::default(),
|
||||
request_args: self.request_args(delegate, config)?,
|
||||
request_args: self.request_args(config)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
use anyhow::{Result, bail};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use collections::FxHashMap;
|
||||
use dap::{
|
||||
DebugRequest, StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
|
||||
DebugRequest, StartDebuggingRequestArguments,
|
||||
adapters::{
|
||||
DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition,
|
||||
},
|
||||
};
|
||||
use gpui::{AsyncApp, SharedString};
|
||||
use language::LanguageName;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::path::PathBuf;
|
||||
use std::{ffi::OsStr, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use task::{DebugScenario, ZedDebugConfig};
|
||||
use util::command::new_smol_command;
|
||||
|
||||
@@ -23,18 +21,6 @@ impl RubyDebugAdapter {
|
||||
const ADAPTER_NAME: &'static str = "Ruby";
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RubyDebugConfig {
|
||||
script_or_command: Option<String>,
|
||||
script: Option<String>,
|
||||
command: Option<String>,
|
||||
#[serde(default)]
|
||||
args: Vec<String>,
|
||||
#[serde(default)]
|
||||
env: FxHashMap<String, String>,
|
||||
cwd: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl DebugAdapter for RubyDebugAdapter {
|
||||
fn name(&self) -> DebugAdapterName {
|
||||
@@ -45,70 +31,185 @@ impl DebugAdapter for RubyDebugAdapter {
|
||||
Some(SharedString::new_static("Ruby").into())
|
||||
}
|
||||
|
||||
fn request_kind(&self, _: &serde_json::Value) -> Result<StartDebuggingRequestArgumentsRequest> {
|
||||
Ok(StartDebuggingRequestArgumentsRequest::Launch)
|
||||
}
|
||||
|
||||
async fn dap_schema(&self) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "Command name (ruby, rake, bin/rails, bundle exec ruby, etc)",
|
||||
"oneOf": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["request"],
|
||||
"properties": {
|
||||
"request": {
|
||||
"type": "string",
|
||||
"enum": ["launch"],
|
||||
"description": "Request to launch a new process"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["script"],
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "Command name (ruby, rake, bin/rails, bundle exec ruby, etc)",
|
||||
"default": "ruby"
|
||||
},
|
||||
"script": {
|
||||
"type": "string",
|
||||
"description": "Absolute path to a Ruby file."
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string",
|
||||
"description": "Directory to execute the program in",
|
||||
"default": "${ZED_WORKTREE_ROOT}"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"description": "Command line arguments passed to the program",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"description": "Additional environment variables to pass to the debugging (and debugged) process",
|
||||
"default": {}
|
||||
},
|
||||
"showProtocolLog": {
|
||||
"type": "boolean",
|
||||
"description": "Show a log of DAP requests, events, and responses",
|
||||
"default": false
|
||||
},
|
||||
"useBundler": {
|
||||
"type": "boolean",
|
||||
"description": "Execute Ruby programs with `bundle exec` instead of directly",
|
||||
"default": false
|
||||
},
|
||||
"bundlePath": {
|
||||
"type": "string",
|
||||
"description": "Location of the bundle executable"
|
||||
},
|
||||
"rdbgPath": {
|
||||
"type": "string",
|
||||
"description": "Location of the rdbg executable"
|
||||
},
|
||||
"askParameters": {
|
||||
"type": "boolean",
|
||||
"description": "Ask parameters at first."
|
||||
},
|
||||
"debugPort": {
|
||||
"type": "string",
|
||||
"description": "UNIX domain socket name or TPC/IP host:port"
|
||||
},
|
||||
"waitLaunchTime": {
|
||||
"type": "number",
|
||||
"description": "Wait time before connection in milliseconds"
|
||||
},
|
||||
"localfs": {
|
||||
"type": "boolean",
|
||||
"description": "true if the VSCode and debugger run on a same machine",
|
||||
"default": false
|
||||
},
|
||||
"useTerminal": {
|
||||
"type": "boolean",
|
||||
"description": "Create a new terminal and then execute commands there",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"script": {
|
||||
"type": "string",
|
||||
"description": "Absolute path to a Ruby file."
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string",
|
||||
"description": "Directory to execute the program in",
|
||||
"default": "${ZED_WORKTREE_ROOT}"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"description": "Command line arguments passed to the program",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"description": "Additional environment variables to pass to the debugging (and debugged) process",
|
||||
"default": {}
|
||||
},
|
||||
}
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["request"],
|
||||
"properties": {
|
||||
"request": {
|
||||
"type": "string",
|
||||
"enum": ["attach"],
|
||||
"description": "Request to attach to an existing process"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rdbgPath": {
|
||||
"type": "string",
|
||||
"description": "Location of the rdbg executable"
|
||||
},
|
||||
"debugPort": {
|
||||
"type": "string",
|
||||
"description": "UNIX domain socket name or TPC/IP host:port"
|
||||
},
|
||||
"showProtocolLog": {
|
||||
"type": "boolean",
|
||||
"description": "Show a log of DAP requests, events, and responses",
|
||||
"default": false
|
||||
},
|
||||
"localfs": {
|
||||
"type": "boolean",
|
||||
"description": "true if the VSCode and debugger run on a same machine",
|
||||
"default": false
|
||||
},
|
||||
"localfsMap": {
|
||||
"type": "string",
|
||||
"description": "Specify pairs of remote root path and local root path like `/remote_dir:/local_dir`. You can specify multiple pairs like `/rem1:/loc1,/rem2:/loc2` by concatenating with `,`."
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"description": "Additional environment variables to pass to the rdbg process",
|
||||
"default": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
|
||||
match zed_scenario.request {
|
||||
let mut config = serde_json::Map::new();
|
||||
|
||||
match &zed_scenario.request {
|
||||
DebugRequest::Launch(launch) => {
|
||||
let config = RubyDebugConfig {
|
||||
script_or_command: Some(launch.program),
|
||||
script: None,
|
||||
command: None,
|
||||
args: launch.args,
|
||||
env: launch.env,
|
||||
cwd: launch.cwd.clone(),
|
||||
};
|
||||
config.insert("request".to_string(), json!("launch"));
|
||||
config.insert("script".to_string(), json!(launch.program));
|
||||
config.insert("command".to_string(), json!("ruby"));
|
||||
|
||||
let config = serde_json::to_value(config)?;
|
||||
if !launch.args.is_empty() {
|
||||
config.insert("args".to_string(), json!(launch.args));
|
||||
}
|
||||
|
||||
Ok(DebugScenario {
|
||||
adapter: zed_scenario.adapter,
|
||||
label: zed_scenario.label,
|
||||
config,
|
||||
tcp_connection: None,
|
||||
build: None,
|
||||
})
|
||||
if !launch.env.is_empty() {
|
||||
config.insert("env".to_string(), json!(launch.env));
|
||||
}
|
||||
|
||||
if let Some(cwd) = &launch.cwd {
|
||||
config.insert("cwd".to_string(), json!(cwd));
|
||||
}
|
||||
|
||||
// Ruby stops on entry so there's no need to handle that case
|
||||
}
|
||||
DebugRequest::Attach(_) => {
|
||||
anyhow::bail!("Attach requests are unsupported");
|
||||
DebugRequest::Attach(attach) => {
|
||||
config.insert("request".to_string(), json!("attach"));
|
||||
|
||||
config.insert("processId".to_string(), json!(attach.process_id));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(DebugScenario {
|
||||
adapter: zed_scenario.adapter,
|
||||
label: zed_scenario.label,
|
||||
config: serde_json::Value::Object(config),
|
||||
tcp_connection: None,
|
||||
build: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_binary(
|
||||
@@ -146,58 +247,26 @@ impl DebugAdapter for RubyDebugAdapter {
|
||||
|
||||
let tcp_connection = definition.tcp_connection.clone().unwrap_or_default();
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
let ruby_config = serde_json::from_value::<RubyDebugConfig>(definition.config.clone())?;
|
||||
|
||||
let mut arguments = vec![
|
||||
let arguments = vec![
|
||||
"--open".to_string(),
|
||||
format!("--port={}", port),
|
||||
format!("--host={}", host),
|
||||
];
|
||||
|
||||
if let Some(script) = &ruby_config.script {
|
||||
arguments.push(script.clone());
|
||||
} else if let Some(command) = &ruby_config.command {
|
||||
arguments.push("--command".to_string());
|
||||
arguments.push(command.clone());
|
||||
} else if let Some(command_or_script) = &ruby_config.script_or_command {
|
||||
if delegate
|
||||
.which(OsStr::new(&command_or_script))
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
arguments.push("--command".to_string());
|
||||
}
|
||||
arguments.push(command_or_script.clone());
|
||||
} else {
|
||||
bail!("Ruby debug config must have 'script' or 'command' args");
|
||||
}
|
||||
|
||||
arguments.extend(ruby_config.args);
|
||||
|
||||
let mut configuration = definition.config.clone();
|
||||
if let Some(configuration) = configuration.as_object_mut() {
|
||||
configuration
|
||||
.entry("cwd")
|
||||
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
|
||||
}
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: Some(rdbg_path.to_string_lossy().to_string()),
|
||||
command: rdbg_path.to_string_lossy().to_string(),
|
||||
arguments,
|
||||
connection: Some(dap::adapters::TcpArguments {
|
||||
host,
|
||||
port,
|
||||
timeout,
|
||||
}),
|
||||
cwd: Some(
|
||||
ruby_config
|
||||
.cwd
|
||||
.unwrap_or(delegate.worktree_root_path().to_owned()),
|
||||
),
|
||||
envs: ruby_config.env.into_iter().collect(),
|
||||
cwd: None,
|
||||
envs: std::collections::HashMap::default(),
|
||||
request_args: StartDebuggingRequestArguments {
|
||||
request: self.request_kind(&definition.config)?,
|
||||
configuration,
|
||||
configuration: definition.config.clone(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,17 +4,19 @@ use crate::session::running::RunningState;
|
||||
use crate::{
|
||||
ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
|
||||
FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, NewProcessModal,
|
||||
NewProcessMode, Pause, Restart, StepInto, StepOut, StepOver, Stop, ToggleExpandItem,
|
||||
ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
|
||||
NewProcessMode, Pause, Restart, ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop,
|
||||
ToggleExpandItem, ToggleIgnoreBreakpoints, ToggleSessionPicker, ToggleThreadPicker,
|
||||
persistence, spawn_task_or_modal,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use dap::StartDebuggingRequestArguments;
|
||||
use dap::adapters::DebugAdapterName;
|
||||
use dap::debugger_settings::DebugPanelDockPosition;
|
||||
use dap::{
|
||||
ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
|
||||
client::SessionId, debugger_settings::DebuggerSettings,
|
||||
};
|
||||
use dap::{DapRegistry, StartDebuggingRequestArguments};
|
||||
use gpui::{
|
||||
Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EntityId, EventEmitter,
|
||||
FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task, WeakEntity,
|
||||
@@ -27,6 +29,7 @@ use project::{Fs, WorktreeId};
|
||||
use project::{Project, debugger::session::ThreadStatus};
|
||||
use rpc::proto::{self};
|
||||
use settings::Settings;
|
||||
use std::any::TypeId;
|
||||
use std::sync::Arc;
|
||||
use task::{DebugScenario, TaskContext};
|
||||
use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
|
||||
@@ -137,6 +140,82 @@ impl DebugPanel {
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
}
|
||||
|
||||
pub(crate) fn filter_action_types(&self, cx: &mut App) {
|
||||
let (has_active_session, supports_restart, support_step_back, status) = self
|
||||
.active_session()
|
||||
.map(|item| {
|
||||
let running = item.read(cx).running_state().clone();
|
||||
let caps = running.read(cx).capabilities(cx);
|
||||
(
|
||||
!running.read(cx).session().read(cx).is_terminated(),
|
||||
caps.supports_restart_request.unwrap_or_default(),
|
||||
caps.supports_step_back.unwrap_or_default(),
|
||||
running.read(cx).thread_status(cx),
|
||||
)
|
||||
})
|
||||
.unwrap_or((false, false, false, None));
|
||||
|
||||
let filter = CommandPaletteFilter::global_mut(cx);
|
||||
let debugger_action_types = [
|
||||
TypeId::of::<Detach>(),
|
||||
TypeId::of::<Stop>(),
|
||||
TypeId::of::<ToggleIgnoreBreakpoints>(),
|
||||
];
|
||||
|
||||
let running_action_types = [TypeId::of::<Pause>()];
|
||||
|
||||
let stopped_action_type = [
|
||||
TypeId::of::<Continue>(),
|
||||
TypeId::of::<StepOver>(),
|
||||
TypeId::of::<StepInto>(),
|
||||
TypeId::of::<StepOut>(),
|
||||
TypeId::of::<ShowStackTrace>(),
|
||||
TypeId::of::<editor::actions::DebuggerRunToCursor>(),
|
||||
TypeId::of::<editor::actions::DebuggerEvaluateSelectedText>(),
|
||||
];
|
||||
|
||||
let step_back_action_type = [TypeId::of::<StepBack>()];
|
||||
let restart_action_type = [TypeId::of::<Restart>()];
|
||||
|
||||
if has_active_session {
|
||||
filter.show_action_types(debugger_action_types.iter());
|
||||
|
||||
if supports_restart {
|
||||
filter.show_action_types(restart_action_type.iter());
|
||||
} else {
|
||||
filter.hide_action_types(&restart_action_type);
|
||||
}
|
||||
|
||||
if support_step_back {
|
||||
filter.show_action_types(step_back_action_type.iter());
|
||||
} else {
|
||||
filter.hide_action_types(&step_back_action_type);
|
||||
}
|
||||
|
||||
match status {
|
||||
Some(ThreadStatus::Running) => {
|
||||
filter.show_action_types(running_action_types.iter());
|
||||
filter.hide_action_types(&stopped_action_type);
|
||||
}
|
||||
Some(ThreadStatus::Stopped) => {
|
||||
filter.show_action_types(stopped_action_type.iter());
|
||||
filter.hide_action_types(&running_action_types);
|
||||
}
|
||||
_ => {
|
||||
filter.hide_action_types(&running_action_types);
|
||||
filter.hide_action_types(&stopped_action_type);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// show only the `debug: start`
|
||||
filter.hide_action_types(&debugger_action_types);
|
||||
filter.hide_action_types(&step_back_action_type);
|
||||
filter.hide_action_types(&restart_action_type);
|
||||
filter.hide_action_types(&running_action_types);
|
||||
filter.hide_action_types(&stopped_action_type);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
@@ -154,6 +233,17 @@ impl DebugPanel {
|
||||
)
|
||||
});
|
||||
|
||||
cx.observe_new::<DebugPanel>(|debug_panel, _, cx| {
|
||||
Self::filter_action_types(debug_panel, cx);
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.observe(&debug_panel, |_, debug_panel, cx| {
|
||||
debug_panel.update(cx, |debug_panel, cx| {
|
||||
Self::filter_action_types(debug_panel, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));
|
||||
|
||||
debug_panel
|
||||
@@ -355,7 +445,10 @@ impl DebugPanel {
|
||||
};
|
||||
|
||||
let dap_store_handle = self.project.read(cx).dap_store().clone();
|
||||
let label = self.label_for_child_session(&parent_session, request, cx);
|
||||
let mut label = parent_session.read(cx).label().clone();
|
||||
if !label.ends_with("(child)") {
|
||||
label = format!("{label} (child)").into();
|
||||
}
|
||||
let adapter = parent_session.read(cx).adapter().clone();
|
||||
let mut binary = parent_session.read(cx).binary().clone();
|
||||
binary.request_args = request.clone();
|
||||
@@ -515,12 +608,6 @@ impl DebugPanel {
|
||||
}
|
||||
})
|
||||
};
|
||||
let documentation_button = || {
|
||||
IconButton::new("debug-open-documentation", IconName::CircleHelp)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(move |_, _, cx| cx.open_url("https://zed.dev/docs/debugger"))
|
||||
.tooltip(Tooltip::text("Open Documentation"))
|
||||
};
|
||||
|
||||
Some(
|
||||
div.border_b_1()
|
||||
@@ -542,8 +629,6 @@ impl DebugPanel {
|
||||
project::debugger::session::ThreadStatus::Exited,
|
||||
);
|
||||
let capabilities = running_state.read(cx).capabilities(cx);
|
||||
let supports_detach =
|
||||
running_state.read(cx).session().read(cx).is_attached();
|
||||
this.map(|this| {
|
||||
if thread_status == ThreadStatus::Running {
|
||||
this.child(
|
||||
@@ -732,48 +817,33 @@ impl DebugPanel {
|
||||
}
|
||||
}),
|
||||
)
|
||||
.when(
|
||||
supports_detach,
|
||||
|div| {
|
||||
div.child(
|
||||
IconButton::new(
|
||||
"debug-disconnect",
|
||||
IconName::DebugDetach,
|
||||
)
|
||||
.disabled(
|
||||
thread_status != ThreadStatus::Stopped
|
||||
&& thread_status != ThreadStatus::Running,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_state,
|
||||
|this, _, _, cx| {
|
||||
this.detach_client(cx);
|
||||
},
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Detach",
|
||||
&Detach,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
.child(
|
||||
IconButton::new("debug-disconnect", IconName::DebugDetach)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_state,
|
||||
|this, _, _, cx| {
|
||||
this.detach_client(cx);
|
||||
},
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Detach",
|
||||
&Detach,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
.justify_around()
|
||||
.when(is_side, |this| {
|
||||
this.child(new_session_button())
|
||||
.child(documentation_button())
|
||||
}),
|
||||
.when(is_side, |this| this.child(new_session_button())),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -814,10 +884,7 @@ impl DebugPanel {
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.when(!is_side, |this| {
|
||||
this.child(new_session_button())
|
||||
.child(documentation_button())
|
||||
}),
|
||||
.when(!is_side, |this| this.child(new_session_button())),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -972,25 +1039,6 @@ impl DebugPanel {
|
||||
cx.emit(PanelEvent::ZoomIn);
|
||||
}
|
||||
}
|
||||
|
||||
fn label_for_child_session(
|
||||
&self,
|
||||
parent_session: &Entity<Session>,
|
||||
request: &StartDebuggingRequestArguments,
|
||||
cx: &mut Context<'_, Self>,
|
||||
) -> SharedString {
|
||||
let adapter = parent_session.read(cx).adapter();
|
||||
if let Some(adapter) = DapRegistry::global(cx).adapter(&adapter) {
|
||||
if let Some(label) = adapter.label_for_child_session(request) {
|
||||
return label.into();
|
||||
}
|
||||
}
|
||||
let mut label = parent_session.read(cx).label().clone();
|
||||
if !label.ends_with("(child)") {
|
||||
label = format!("{label} (child)").into();
|
||||
}
|
||||
label
|
||||
}
|
||||
}
|
||||
|
||||
async fn register_session_inner(
|
||||
@@ -1018,11 +1066,6 @@ async fn register_session_inner(
|
||||
.ok();
|
||||
let serialized_layout = persistence::get_serialized_layout(adapter_name).await;
|
||||
let debug_session = this.update_in(cx, |this, window, cx| {
|
||||
let parent_session = this
|
||||
.sessions
|
||||
.iter()
|
||||
.find(|p| Some(p.read(cx).session_id(cx)) == session.read(cx).parent_id(cx))
|
||||
.cloned();
|
||||
this.sessions.retain(|session| {
|
||||
!session
|
||||
.read(cx)
|
||||
@@ -1036,8 +1079,8 @@ async fn register_session_inner(
|
||||
let debug_session = DebugSession::running(
|
||||
this.project.clone(),
|
||||
this.workspace.clone(),
|
||||
parent_session.map(|p| p.read(cx).running_state().read(cx).debug_terminal.clone()),
|
||||
session,
|
||||
cx.weak_entity(),
|
||||
serialized_layout,
|
||||
this.position(window, cx).axis(),
|
||||
window,
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
use std::any::TypeId;
|
||||
|
||||
use dap::debugger_settings::DebuggerSettings;
|
||||
use debugger_panel::{DebugPanel, ToggleFocus};
|
||||
use editor::Editor;
|
||||
use feature_flags::{DebuggerFeatureFlag, FeatureFlagViewExt};
|
||||
use gpui::{App, DispatchPhase, EntityInputHandler, actions};
|
||||
use gpui::{App, EntityInputHandler, actions};
|
||||
use new_process_modal::{NewProcessModal, NewProcessMode};
|
||||
use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus};
|
||||
use project::debugger::{self, breakpoint_store::SourceBreakpoint};
|
||||
use session::DebugSession;
|
||||
use settings::Settings;
|
||||
use stack_trace_view::StackTraceView;
|
||||
use tasks_ui::{Spawn, TaskOverrides};
|
||||
use ui::{FluentBuilder, InteractiveElement};
|
||||
use util::maybe;
|
||||
use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace};
|
||||
|
||||
@@ -71,6 +68,148 @@ pub fn init(cx: &mut App) {
|
||||
.register_action(|workspace, _: &ToggleFocus, window, cx| {
|
||||
workspace.toggle_panel_focus::<DebugPanel>(window, cx);
|
||||
})
|
||||
.register_action(|workspace, _: &Pause, _, cx| {
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel
|
||||
.read(cx)
|
||||
.active_session()
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
{
|
||||
active_item.update(cx, |item, cx| item.pause_thread(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &Restart, _, cx| {
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel
|
||||
.read(cx)
|
||||
.active_session()
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
{
|
||||
active_item.update(cx, |item, cx| item.restart_session(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &Continue, _, cx| {
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel
|
||||
.read(cx)
|
||||
.active_session()
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
{
|
||||
active_item.update(cx, |item, cx| item.continue_thread(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &StepInto, _, cx| {
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel
|
||||
.read(cx)
|
||||
.active_session()
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
{
|
||||
active_item.update(cx, |item, cx| item.step_in(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &StepOver, _, cx| {
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel
|
||||
.read(cx)
|
||||
.active_session()
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
{
|
||||
active_item.update(cx, |item, cx| item.step_over(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &StepOut, _, 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()
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
}) {
|
||||
active_item.update(cx, |item, cx| item.step_out(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &StepBack, _, cx| {
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel
|
||||
.read(cx)
|
||||
.active_session()
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
{
|
||||
active_item.update(cx, |item, cx| item.step_back(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &Stop, _, cx| {
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel
|
||||
.read(cx)
|
||||
.active_session()
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
{
|
||||
cx.defer(move |cx| {
|
||||
active_item.update(cx, |item, cx| item.stop_thread(cx))
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &ToggleIgnoreBreakpoints, _, cx| {
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
if let Some(active_item) = debug_panel
|
||||
.read(cx)
|
||||
.active_session()
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
{
|
||||
active_item.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
.register_action(
|
||||
|workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
project.dap_store().update(cx, |store, cx| {
|
||||
store.shutdown_sessions(cx).detach();
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
.register_action(
|
||||
|workspace: &mut Workspace, _: &ShowStackTrace, window, cx| {
|
||||
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx) {
|
||||
let is_active = workspace
|
||||
.active_item(cx)
|
||||
.is_some_and(|item| item.item_id() == existing.item_id());
|
||||
workspace.activate_item(&existing, true, !is_active, window, cx);
|
||||
} else {
|
||||
let Some(active_session) = debug_panel.read(cx).active_session() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let project = workspace.project();
|
||||
|
||||
let stack_trace_view = active_session.update(cx, |session, cx| {
|
||||
session.stack_trace_view(project, window, cx).clone()
|
||||
});
|
||||
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(stack_trace_view),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
.register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
|
||||
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
|
||||
})
|
||||
@@ -84,255 +223,90 @@ pub fn init(cx: &mut App) {
|
||||
debug_panel.rerun_last_session(workspace, window, cx);
|
||||
})
|
||||
},
|
||||
)
|
||||
.register_action(
|
||||
|workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
project.dap_store().update(cx, |store, cx| {
|
||||
store.shutdown_sessions(cx).detach();
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
.register_action_renderer(|div, workspace, _, cx| {
|
||||
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
|
||||
return div;
|
||||
};
|
||||
let Some(active_item) = debug_panel
|
||||
.read(cx)
|
||||
.active_session()
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
else {
|
||||
return div;
|
||||
};
|
||||
let running_state = active_item.read(cx);
|
||||
if running_state.session().read(cx).is_terminated() {
|
||||
return div;
|
||||
}
|
||||
|
||||
let caps = running_state.capabilities(cx);
|
||||
let supports_restart = caps.supports_restart_request.unwrap_or_default();
|
||||
let supports_step_back = caps.supports_step_back.unwrap_or_default();
|
||||
let supports_detach = running_state.session().read(cx).is_attached();
|
||||
let status = running_state.thread_status(cx);
|
||||
|
||||
let active_item = active_item.downgrade();
|
||||
div.when(status == Some(ThreadStatus::Running), |div| {
|
||||
let active_item = active_item.clone();
|
||||
div.on_action(move |_: &Pause, _, cx| {
|
||||
active_item
|
||||
.update(cx, |item, cx| item.pause_thread(cx))
|
||||
.ok();
|
||||
})
|
||||
})
|
||||
.when(status == Some(ThreadStatus::Stopped), |div| {
|
||||
div.on_action({
|
||||
let active_item = active_item.clone();
|
||||
move |_: &StepInto, _, cx| {
|
||||
active_item.update(cx, |item, cx| item.step_in(cx)).ok();
|
||||
}
|
||||
})
|
||||
.on_action({
|
||||
let active_item = active_item.clone();
|
||||
move |_: &StepOver, _, cx| {
|
||||
active_item.update(cx, |item, cx| item.step_over(cx)).ok();
|
||||
}
|
||||
})
|
||||
.on_action({
|
||||
let active_item = active_item.clone();
|
||||
move |_: &StepOut, _, cx| {
|
||||
active_item.update(cx, |item, cx| item.step_out(cx)).ok();
|
||||
}
|
||||
})
|
||||
.when(supports_step_back, |div| {
|
||||
let active_item = active_item.clone();
|
||||
div.on_action(move |_: &StepBack, _, cx| {
|
||||
active_item.update(cx, |item, cx| item.step_back(cx)).ok();
|
||||
})
|
||||
})
|
||||
.on_action({
|
||||
let active_item = active_item.clone();
|
||||
move |_: &Continue, _, cx| {
|
||||
active_item
|
||||
.update(cx, |item, cx| item.continue_thread(cx))
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.on_action(cx.listener(
|
||||
|workspace, _: &ShowStackTrace, window, cx| {
|
||||
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx)
|
||||
{
|
||||
let is_active = workspace
|
||||
.active_item(cx)
|
||||
.is_some_and(|item| item.item_id() == existing.item_id());
|
||||
workspace
|
||||
.activate_item(&existing, true, !is_active, window, cx);
|
||||
} else {
|
||||
let Some(active_session) =
|
||||
debug_panel.read(cx).active_session()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let project = workspace.project();
|
||||
|
||||
let stack_trace_view =
|
||||
active_session.update(cx, |session, cx| {
|
||||
session.stack_trace_view(project, window, cx).clone()
|
||||
});
|
||||
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(stack_trace_view),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
},
|
||||
))
|
||||
})
|
||||
.when(supports_detach, |div| {
|
||||
let active_item = active_item.clone();
|
||||
div.on_action(move |_: &Detach, _, cx| {
|
||||
active_item
|
||||
.update(cx, |item, cx| item.detach_client(cx))
|
||||
.ok();
|
||||
})
|
||||
})
|
||||
.when(supports_restart, |div| {
|
||||
let active_item = active_item.clone();
|
||||
div.on_action(move |_: &Restart, _, cx| {
|
||||
active_item
|
||||
.update(cx, |item, cx| item.restart_session(cx))
|
||||
.ok();
|
||||
})
|
||||
})
|
||||
.on_action({
|
||||
let active_item = active_item.clone();
|
||||
move |_: &Stop, _, cx| {
|
||||
active_item.update(cx, |item, cx| item.stop_thread(cx)).ok();
|
||||
}
|
||||
})
|
||||
.on_action({
|
||||
let active_item = active_item.clone();
|
||||
move |_: &ToggleIgnoreBreakpoints, _, cx| {
|
||||
active_item
|
||||
.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
});
|
||||
);
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.observe_new({
|
||||
move |editor: &mut Editor, _, _| {
|
||||
move |editor: &mut Editor, _, cx| {
|
||||
editor
|
||||
.register_action_renderer(move |editor, window, cx| {
|
||||
let Some(workspace) = editor.workspace() else {
|
||||
return;
|
||||
};
|
||||
let Some(debug_panel) = workspace.read(cx).panel::<DebugPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let Some(active_session) = debug_panel
|
||||
.clone()
|
||||
.update(cx, |panel, _| panel.active_session())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let editor = cx.entity().downgrade();
|
||||
window.on_action(TypeId::of::<editor::actions::RunToCursor>(), {
|
||||
let editor = editor.clone();
|
||||
let active_session = active_session.clone();
|
||||
move |_, phase, _, cx| {
|
||||
if phase != DispatchPhase::Bubble {
|
||||
return;
|
||||
}
|
||||
maybe!({
|
||||
let (buffer, position, _) = editor
|
||||
.update(cx, |editor, cx| {
|
||||
let cursor_point: language::Point =
|
||||
editor.selections.newest(cx).head();
|
||||
.register_action(cx.listener(
|
||||
move |editor, _: &editor::actions::DebuggerRunToCursor, _, cx| {
|
||||
maybe!({
|
||||
let debug_panel =
|
||||
editor.workspace()?.read(cx).panel::<DebugPanel>(cx)?;
|
||||
let cursor_point: language::Point = editor.selections.newest(cx).head();
|
||||
let active_session = debug_panel.read(cx).active_session()?;
|
||||
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.point_to_buffer_point(cursor_point, cx)
|
||||
})
|
||||
.ok()??;
|
||||
let (buffer, position, _) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.point_to_buffer_point(cursor_point, cx)?;
|
||||
|
||||
let path =
|
||||
let path =
|
||||
debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
|
||||
&buffer, cx,
|
||||
)?;
|
||||
|
||||
let source_breakpoint = SourceBreakpoint {
|
||||
row: position.row,
|
||||
path,
|
||||
message: None,
|
||||
condition: None,
|
||||
hit_condition: None,
|
||||
state: debugger::breakpoint_store::BreakpointState::Enabled,
|
||||
};
|
||||
|
||||
active_session.update(cx, |session, cx| {
|
||||
session.running_state().update(cx, |state, cx| {
|
||||
if let Some(thread_id) = state.selected_thread_id() {
|
||||
state.session().update(cx, |session, cx| {
|
||||
session.run_to_position(
|
||||
source_breakpoint,
|
||||
thread_id,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
window.on_action(
|
||||
TypeId::of::<editor::actions::EvaluateSelectedText>(),
|
||||
move |_, _, window, cx| {
|
||||
maybe!({
|
||||
let text = editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.text_for_range(
|
||||
editor.selections.newest(cx).range(),
|
||||
&mut None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()??;
|
||||
|
||||
active_session.update(cx, |session, cx| {
|
||||
session.running_state().update(cx, |state, cx| {
|
||||
let stack_id = state.selected_stack_frame_id(cx);
|
||||
let source_breakpoint = SourceBreakpoint {
|
||||
row: position.row,
|
||||
path,
|
||||
message: None,
|
||||
condition: None,
|
||||
hit_condition: None,
|
||||
state: debugger::breakpoint_store::BreakpointState::Enabled,
|
||||
};
|
||||
|
||||
active_session.update(cx, |session, cx| {
|
||||
session.running_state().update(cx, |state, cx| {
|
||||
if let Some(thread_id) = state.selected_thread_id() {
|
||||
state.session().update(cx, |session, cx| {
|
||||
session
|
||||
.evaluate(text, None, stack_id, None, cx)
|
||||
.detach();
|
||||
});
|
||||
session.run_to_position(
|
||||
source_breakpoint,
|
||||
thread_id,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Some(())
|
||||
});
|
||||
},
|
||||
))
|
||||
.detach();
|
||||
|
||||
editor
|
||||
.register_action(cx.listener(
|
||||
move |editor, _: &editor::actions::DebuggerEvaluateSelectedText, window, cx| {
|
||||
maybe!({
|
||||
let debug_panel =
|
||||
editor.workspace()?.read(cx).panel::<DebugPanel>(cx)?;
|
||||
let active_session = debug_panel.read(cx).active_session()?;
|
||||
|
||||
let text = editor.text_for_range(
|
||||
editor.selections.newest(cx).range(),
|
||||
&mut None,
|
||||
window,
|
||||
cx,
|
||||
)?;
|
||||
|
||||
active_session.update(cx, |session, cx| {
|
||||
session.running_state().update(cx, |state, cx| {
|
||||
let stack_id = state.selected_stack_frame_id(cx);
|
||||
|
||||
state.session().update(cx, |session, cx| {
|
||||
session.evaluate(text, None, stack_id, None, cx).detach();
|
||||
});
|
||||
});
|
||||
|
||||
Some(())
|
||||
});
|
||||
},
|
||||
);
|
||||
})
|
||||
|
||||
Some(())
|
||||
});
|
||||
},
|
||||
))
|
||||
.detach();
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use collections::{FxHashMap, HashMap};
|
||||
use collections::FxHashMap;
|
||||
use language::LanguageRegistry;
|
||||
use paths::local_debug_file_relative_path;
|
||||
use std::{
|
||||
@@ -15,9 +15,9 @@ use dap::{
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
Action, App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
HighlightStyle, InteractiveText, KeyContext, PromptButton, PromptLevel, Render, StyledText,
|
||||
Subscription, TextStyle, UnderlineStyle, WeakEntity,
|
||||
App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, HighlightStyle,
|
||||
InteractiveText, KeyContext, PromptButton, PromptLevel, Render, StyledText, Subscription,
|
||||
TextStyle, UnderlineStyle, WeakEntity,
|
||||
};
|
||||
use itertools::Itertools as _;
|
||||
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
|
||||
@@ -28,10 +28,10 @@ use theme::ThemeSettings;
|
||||
use ui::{
|
||||
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
|
||||
ContextMenu, Disableable, DropdownMenu, FluentBuilder, Icon, IconName, IconSize,
|
||||
IconWithIndicator, Indicator, InteractiveElement, IntoElement, KeyBinding, Label,
|
||||
LabelCommon as _, LabelSize, ListItem, ListItemSpacing, ParentElement, RenderOnce,
|
||||
SharedString, Styled, StyledExt, StyledTypography, ToggleButton, ToggleState, Toggleable,
|
||||
Tooltip, Window, div, h_flex, px, relative, rems, v_flex,
|
||||
IconWithIndicator, Indicator, InteractiveElement, IntoElement, Label, LabelCommon as _,
|
||||
ListItem, ListItemSpacing, ParentElement, RenderOnce, SharedString, Styled, StyledExt,
|
||||
StyledTypography, ToggleButton, ToggleState, Toggleable, Tooltip, Window, div, h_flex, px,
|
||||
relative, rems, v_flex,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use workspace::{ModalView, Workspace, pane};
|
||||
@@ -50,7 +50,7 @@ pub(super) struct NewProcessModal {
|
||||
mode: NewProcessMode,
|
||||
debug_picker: Entity<Picker<DebugDelegate>>,
|
||||
attach_mode: Entity<AttachMode>,
|
||||
configure_mode: Entity<ConfigureMode>,
|
||||
launch_mode: Entity<ConfigureMode>,
|
||||
task_mode: TaskMode,
|
||||
debugger: Option<DebugAdapterName>,
|
||||
// save_scenario_state: Option<SaveScenarioState>,
|
||||
@@ -194,12 +194,11 @@ impl NewProcessModal {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let (used_tasks, current_resolved_tasks) = task_inventory
|
||||
.update(cx, |task_inventory, cx| {
|
||||
let (used_tasks, current_resolved_tasks) =
|
||||
task_inventory.update(cx, |task_inventory, cx| {
|
||||
task_inventory
|
||||
.used_and_current_resolved_tasks(task_contexts.clone(), cx)
|
||||
})?
|
||||
.await;
|
||||
.used_and_current_resolved_tasks(&task_contexts, cx)
|
||||
})?;
|
||||
|
||||
debug_picker
|
||||
.update_in(cx, |picker, window, cx| {
|
||||
@@ -254,7 +253,7 @@ impl NewProcessModal {
|
||||
Self {
|
||||
debug_picker,
|
||||
attach_mode,
|
||||
configure_mode,
|
||||
launch_mode: configure_mode,
|
||||
task_mode,
|
||||
debugger: None,
|
||||
mode,
|
||||
@@ -284,7 +283,7 @@ impl NewProcessModal {
|
||||
NewProcessMode::Attach => self.attach_mode.update(cx, |this, cx| {
|
||||
this.clone().render(window, cx).into_any_element()
|
||||
}),
|
||||
NewProcessMode::Launch => self.configure_mode.update(cx, |this, cx| {
|
||||
NewProcessMode::Launch => self.launch_mode.update(cx, |this, cx| {
|
||||
this.clone().render(dap_menu, window, cx).into_any_element()
|
||||
}),
|
||||
NewProcessMode::Debug => v_flex()
|
||||
@@ -298,7 +297,7 @@ impl NewProcessModal {
|
||||
match self.mode {
|
||||
NewProcessMode::Task => self.task_mode.task_modal.focus_handle(cx),
|
||||
NewProcessMode::Attach => self.attach_mode.read(cx).attach_picker.focus_handle(cx),
|
||||
NewProcessMode::Launch => self.configure_mode.read(cx).program.focus_handle(cx),
|
||||
NewProcessMode::Launch => self.launch_mode.read(cx).program.focus_handle(cx),
|
||||
NewProcessMode::Debug => self.debug_picker.focus_handle(cx),
|
||||
}
|
||||
}
|
||||
@@ -306,7 +305,7 @@ impl NewProcessModal {
|
||||
fn debug_scenario(&self, debugger: &str, cx: &App) -> Option<DebugScenario> {
|
||||
let request = match self.mode {
|
||||
NewProcessMode::Launch => Some(DebugRequest::Launch(
|
||||
self.configure_mode.read(cx).debug_request(cx),
|
||||
self.launch_mode.read(cx).debug_request(cx),
|
||||
)),
|
||||
NewProcessMode::Attach => Some(DebugRequest::Attach(
|
||||
self.attach_mode.read(cx).debug_request(),
|
||||
@@ -316,7 +315,7 @@ impl NewProcessModal {
|
||||
let label = suggested_label(&request, debugger);
|
||||
|
||||
let stop_on_entry = if let NewProcessMode::Launch = &self.mode {
|
||||
Some(self.configure_mode.read(cx).stop_on_entry.selected())
|
||||
Some(self.launch_mode.read(cx).stop_on_entry.selected())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -832,7 +831,7 @@ impl Render for NewProcessModal {
|
||||
.disabled(
|
||||
self.debugger.is_none()
|
||||
|| self
|
||||
.configure_mode
|
||||
.launch_mode
|
||||
.read(cx)
|
||||
.program
|
||||
.read(cx)
|
||||
@@ -1203,7 +1202,7 @@ impl PickerDelegate for DebugDelegate {
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> std::sync::Arc<str> {
|
||||
"Find a debug task, or debug a command.".into()
|
||||
"".into()
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
@@ -1266,96 +1265,6 @@ impl PickerDelegate for DebugDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm_input(
|
||||
&mut self,
|
||||
_secondary: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) {
|
||||
let text = self.prompt.clone();
|
||||
let (task_context, worktree_id) = self
|
||||
.task_contexts
|
||||
.as_ref()
|
||||
.and_then(|task_contexts| {
|
||||
Some((
|
||||
task_contexts.active_context().cloned()?,
|
||||
task_contexts.worktree(),
|
||||
))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut args = shlex::split(&text).into_iter().flatten().peekable();
|
||||
let mut env = HashMap::default();
|
||||
while args.peek().is_some_and(|arg| arg.contains('=')) {
|
||||
let arg = args.next().unwrap();
|
||||
let (lhs, rhs) = arg.split_once('=').unwrap();
|
||||
env.insert(lhs.to_string(), rhs.to_string());
|
||||
}
|
||||
|
||||
let program = if let Some(program) = args.next() {
|
||||
program
|
||||
} else {
|
||||
env = HashMap::default();
|
||||
text
|
||||
};
|
||||
|
||||
let args = args.collect::<Vec<_>>();
|
||||
let task = task::TaskTemplate {
|
||||
label: "one-off".to_owned(),
|
||||
env,
|
||||
command: program,
|
||||
args,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let Some(location) = self
|
||||
.task_contexts
|
||||
.as_ref()
|
||||
.and_then(|cx| cx.location().cloned())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let file = location.buffer.read(cx).file();
|
||||
let language = location.buffer.read(cx).language();
|
||||
let language_name = language.as_ref().map(|l| l.name());
|
||||
let Some(adapter): Option<DebugAdapterName> =
|
||||
language::language_settings::language_settings(language_name, file, cx)
|
||||
.debuggers
|
||||
.first()
|
||||
.map(SharedString::from)
|
||||
.map(Into::into)
|
||||
.or_else(|| {
|
||||
language.and_then(|l| {
|
||||
l.config()
|
||||
.debuggers
|
||||
.first()
|
||||
.map(SharedString::from)
|
||||
.map(Into::into)
|
||||
})
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(debug_scenario) = cx
|
||||
.global::<DapRegistry>()
|
||||
.locators()
|
||||
.iter()
|
||||
.find_map(|locator| locator.1.create_scenario(&task, "one-off", adapter.clone()))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
send_telemetry(&debug_scenario, TelemetrySpawnLocation::ScenarioList, cx);
|
||||
|
||||
self.debug_panel
|
||||
.update(cx, |panel, cx| {
|
||||
panel.start_session(debug_scenario, task_context, None, worktree_id, window, cx);
|
||||
})
|
||||
.ok();
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<picker::Picker<Self>>) {
|
||||
let debug_scenario = self
|
||||
.matches
|
||||
@@ -1391,60 +1300,6 @@ impl PickerDelegate for DebugDelegate {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn render_footer(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<ui::AnyElement> {
|
||||
let current_modifiers = window.modifiers();
|
||||
let footer = h_flex()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.p_2()
|
||||
.justify_between()
|
||||
.rounded_b_sm()
|
||||
.bg(cx.theme().colors().ghost_element_selected)
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
// TODO: add button to open selected task in debug.json
|
||||
h_flex().into_any_element(),
|
||||
)
|
||||
.map(|this| {
|
||||
if (current_modifiers.alt || self.matches.is_empty()) && !self.prompt.is_empty() {
|
||||
let action = picker::ConfirmInput {
|
||||
secondary: current_modifiers.secondary(),
|
||||
}
|
||||
.boxed_clone();
|
||||
this.children(KeyBinding::for_action(&*action, window, cx).map(|keybind| {
|
||||
Button::new("launch-custom", "Launch Custom")
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(keybind)
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(action.boxed_clone(), cx)
|
||||
})
|
||||
}))
|
||||
} else {
|
||||
this.children(KeyBinding::for_action(&menu::Confirm, window, cx).map(
|
||||
|keybind| {
|
||||
let is_recent_selected =
|
||||
self.divider_index >= Some(self.selected_index);
|
||||
let run_entry_label =
|
||||
if is_recent_selected { "Rerun" } else { "Spawn" };
|
||||
|
||||
Button::new("spawn", run_entry_label)
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(keybind)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(menu::Confirm.boxed_clone(), cx);
|
||||
})
|
||||
},
|
||||
))
|
||||
}
|
||||
});
|
||||
Some(footer.into_any_element())
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pub mod running;
|
||||
|
||||
use crate::{StackTraceView, persistence::SerializedLayout, session::running::DebugTerminal};
|
||||
use crate::{StackTraceView, debugger_panel::DebugPanel, persistence::SerializedLayout};
|
||||
use dap::client::SessionId;
|
||||
use gpui::{
|
||||
App, Axis, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
|
||||
@@ -22,6 +22,7 @@ pub struct DebugSession {
|
||||
running_state: Entity<RunningState>,
|
||||
label: OnceLock<SharedString>,
|
||||
stack_trace_view: OnceCell<Entity<StackTraceView>>,
|
||||
_debug_panel: WeakEntity<DebugPanel>,
|
||||
_worktree_store: WeakEntity<WorktreeStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
_subscriptions: [Subscription; 1],
|
||||
@@ -37,8 +38,8 @@ impl DebugSession {
|
||||
pub(crate) fn running(
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
parent_terminal: Option<Entity<DebugTerminal>>,
|
||||
session: Entity<Session>,
|
||||
_debug_panel: WeakEntity<DebugPanel>,
|
||||
serialized_layout: Option<SerializedLayout>,
|
||||
dock_axis: Axis,
|
||||
window: &mut Window,
|
||||
@@ -49,7 +50,6 @@ impl DebugSession {
|
||||
session.clone(),
|
||||
project.clone(),
|
||||
workspace.clone(),
|
||||
parent_terminal,
|
||||
serialized_layout,
|
||||
dock_axis,
|
||||
window,
|
||||
@@ -64,6 +64,7 @@ impl DebugSession {
|
||||
remote_id: None,
|
||||
running_state,
|
||||
label: OnceLock::new(),
|
||||
_debug_panel,
|
||||
stack_trace_view: OnceCell::new(),
|
||||
_worktree_store: project.read(cx).worktree_store().downgrade(),
|
||||
workspace,
|
||||
|
||||
@@ -605,7 +605,6 @@ impl RunningState {
|
||||
session: Entity<Session>,
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
parent_terminal: Option<Entity<DebugTerminal>>,
|
||||
serialized_pane_layout: Option<SerializedLayout>,
|
||||
dock_axis: Axis,
|
||||
window: &mut Window,
|
||||
@@ -618,8 +617,7 @@ impl RunningState {
|
||||
StackFrameList::new(workspace.clone(), session.clone(), weak_state, window, cx)
|
||||
});
|
||||
|
||||
let debug_terminal =
|
||||
parent_terminal.unwrap_or_else(|| cx.new(|cx| DebugTerminal::empty(window, cx)));
|
||||
let debug_terminal = cx.new(|cx| DebugTerminal::empty(window, cx));
|
||||
|
||||
let variable_list =
|
||||
cx.new(|cx| VariableList::new(session.clone(), stack_frame_list.clone(), window, cx));
|
||||
@@ -818,20 +816,20 @@ impl RunningState {
|
||||
|
||||
let request_type = dap_registry
|
||||
.adapter(&adapter)
|
||||
.with_context(|| format!("{}: is not a valid adapter name", &adapter))
|
||||
.ok_or_else(|| anyhow!("{}: is not a valid adapter name", &adapter))
|
||||
.and_then(|adapter| adapter.request_kind(&config));
|
||||
|
||||
let config_is_valid = request_type.is_ok();
|
||||
|
||||
let build_output = if let Some(build) = build {
|
||||
let (task_template, locator_name) = match build {
|
||||
let (task, locator_name) = match build {
|
||||
BuildTaskDefinition::Template {
|
||||
task_template,
|
||||
locator_name,
|
||||
} => (task_template, locator_name),
|
||||
BuildTaskDefinition::ByName(ref label) => {
|
||||
let task = task_store.update(cx, |this, cx| {
|
||||
this.task_inventory().map(|inventory| {
|
||||
let Some(task) = task_store.update(cx, |this, cx| {
|
||||
this.task_inventory().and_then(|inventory| {
|
||||
inventory.read(cx).task_template_by_label(
|
||||
buffer,
|
||||
worktree_id,
|
||||
@@ -839,15 +837,14 @@ impl RunningState {
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})?;
|
||||
let task = match task {
|
||||
Some(task) => task.await,
|
||||
None => None,
|
||||
}.with_context(|| format!("Couldn't find task template for {build:?}"))?;
|
||||
})?
|
||||
else {
|
||||
anyhow::bail!("Couldn't find task template for {:?}", build)
|
||||
};
|
||||
(task, None)
|
||||
}
|
||||
};
|
||||
let Some(task) = task_template.resolve_task("debug-build-task", &task_context) else {
|
||||
let Some(task) = task.resolve_task("debug-build-task", &task_context) else {
|
||||
anyhow::bail!("Could not resolve task variables within a debug scenario");
|
||||
};
|
||||
|
||||
@@ -932,13 +929,15 @@ impl RunningState {
|
||||
};
|
||||
|
||||
if config_is_valid {
|
||||
// Ok(DebugTaskDefinition {
|
||||
// label,
|
||||
// adapter: DebugAdapterName(adapter),
|
||||
// config,
|
||||
// tcp_connection,
|
||||
// })
|
||||
} else if let Some((task, locator_name)) = build_output {
|
||||
let locator_name =
|
||||
locator_name.with_context(|| {
|
||||
format!("Could not find a valid locator for a build task and configure is invalid with error: {}", request_type.err()
|
||||
.map(|err| err.to_string())
|
||||
.unwrap_or_default())
|
||||
})?;
|
||||
locator_name.context("Could not find a valid locator for a build task")?;
|
||||
let request = dap_store
|
||||
.update(cx, |this, cx| {
|
||||
this.run_debug_locator(&locator_name, task, cx)
|
||||
@@ -954,7 +953,7 @@ impl RunningState {
|
||||
|
||||
let scenario = dap_registry
|
||||
.adapter(&adapter)
|
||||
.with_context(|| anyhow!("{}: is not a valid adapter name", &adapter))
|
||||
.ok_or_else(|| anyhow!("{}: is not a valid adapter name", &adapter))
|
||||
.map(|adapter| adapter.config_from_zed_format(zed_config))??;
|
||||
config = scenario.config;
|
||||
Self::substitute_variables_in_config(&mut config, &task_context);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::{
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
@@ -278,9 +277,10 @@ impl BreakpointList {
|
||||
let selected_ix = self.selected_ix;
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
uniform_list(
|
||||
cx.entity(),
|
||||
"breakpoint-list",
|
||||
self.breakpoints.len(),
|
||||
cx.processor(move |this, range: Range<usize>, window, cx| {
|
||||
move |this, range, window, cx| {
|
||||
range
|
||||
.clone()
|
||||
.zip(&mut this.breakpoints[range])
|
||||
@@ -291,7 +291,7 @@ impl BreakpointList {
|
||||
.into_any_element()
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
},
|
||||
)
|
||||
.track_scroll(self.scroll_handle.clone())
|
||||
.flex_grow()
|
||||
|
||||
@@ -8,7 +8,7 @@ use project::{
|
||||
ProjectItem as _, ProjectPath,
|
||||
debugger::session::{Session, SessionEvent},
|
||||
};
|
||||
use std::{ops::Range, path::Path, sync::Arc};
|
||||
use std::{path::Path, sync::Arc};
|
||||
use ui::{Scrollbar, ScrollbarState, prelude::*};
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -281,11 +281,10 @@ impl ModuleList {
|
||||
|
||||
fn render_list(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
uniform_list(
|
||||
cx.entity(),
|
||||
"module-list",
|
||||
self.entries.len(),
|
||||
cx.processor(|this, range: Range<usize>, _window, cx| {
|
||||
range.map(|ix| this.render_entry(ix, cx)).collect()
|
||||
}),
|
||||
|this, range, _window, cx| range.map(|ix| this.render_entry(ix, cx)).collect(),
|
||||
)
|
||||
.track_scroll(self.scroll_handle.clone())
|
||||
.size_full()
|
||||
|
||||
@@ -5,8 +5,8 @@ use std::time::Duration;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use dap::StackFrameId;
|
||||
use gpui::{
|
||||
AnyElement, Entity, EventEmitter, FocusHandle, Focusable, ListState, MouseButton, Stateful,
|
||||
Subscription, Task, WeakEntity, list,
|
||||
AnyElement, Entity, EventEmitter, FocusHandle, Focusable, MouseButton, ScrollStrategy,
|
||||
Stateful, Subscription, Task, UniformListScrollHandle, WeakEntity, uniform_list,
|
||||
};
|
||||
|
||||
use crate::StackTraceView;
|
||||
@@ -35,7 +35,7 @@ pub struct StackFrameList {
|
||||
selected_ix: Option<usize>,
|
||||
opened_stack_frame_id: Option<StackFrameId>,
|
||||
scrollbar_state: ScrollbarState,
|
||||
list_state: ListState,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
_refresh_task: Task<()>,
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ impl StackFrameList {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let scroll_handle = UniformListScrollHandle::new();
|
||||
|
||||
let _subscription =
|
||||
cx.subscribe_in(&session, window, |this, _, event, window, cx| match event {
|
||||
@@ -66,16 +67,8 @@ impl StackFrameList {
|
||||
_ => {}
|
||||
});
|
||||
|
||||
let list_state = ListState::new(0, gpui::ListAlignment::Top, px(1000.), {
|
||||
let this = cx.weak_entity();
|
||||
move |ix, _window, cx| {
|
||||
this.update(cx, |this, cx| this.render_entry(ix, cx))
|
||||
.unwrap_or(div().into_any())
|
||||
}
|
||||
});
|
||||
let scrollbar_state = ScrollbarState::new(list_state.clone());
|
||||
|
||||
let mut this = Self {
|
||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
|
||||
session,
|
||||
workspace,
|
||||
focus_handle,
|
||||
@@ -84,8 +77,7 @@ impl StackFrameList {
|
||||
entries: Default::default(),
|
||||
selected_ix: None,
|
||||
opened_stack_frame_id: None,
|
||||
list_state,
|
||||
scrollbar_state,
|
||||
scroll_handle,
|
||||
_refresh_task: Task::ready(()),
|
||||
};
|
||||
this.schedule_refresh(true, window, cx);
|
||||
@@ -183,7 +175,6 @@ impl StackFrameList {
|
||||
let mut entries = Vec::new();
|
||||
let mut collapsed_entries = Vec::new();
|
||||
let mut first_stack_frame = None;
|
||||
let mut first_not_subtle_frame = None;
|
||||
|
||||
let stack_frames = self.stack_frames(cx);
|
||||
for stack_frame in &stack_frames {
|
||||
@@ -198,11 +189,6 @@ impl StackFrameList {
|
||||
}
|
||||
|
||||
first_stack_frame.get_or_insert(entries.len());
|
||||
if stack_frame.dap.presentation_hint
|
||||
!= Some(dap::StackFramePresentationHint::Subtle)
|
||||
{
|
||||
first_not_subtle_frame.get_or_insert(entries.len());
|
||||
}
|
||||
entries.push(StackFrameEntry::Normal(stack_frame.dap.clone()));
|
||||
}
|
||||
}
|
||||
@@ -215,10 +201,7 @@ impl StackFrameList {
|
||||
|
||||
std::mem::swap(&mut self.entries, &mut entries);
|
||||
|
||||
if let Some(ix) = first_not_subtle_frame
|
||||
.or(first_stack_frame)
|
||||
.filter(|_| open_first_stack_frame)
|
||||
{
|
||||
if let Some(ix) = first_stack_frame.filter(|_| open_first_stack_frame) {
|
||||
self.select_ix(Some(ix), cx);
|
||||
self.activate_selected_entry(window, cx);
|
||||
} else if let Some(old_selected_frame_id) = old_selected_frame_id {
|
||||
@@ -231,7 +214,6 @@ impl StackFrameList {
|
||||
self.selected_ix = ix;
|
||||
}
|
||||
|
||||
self.list_state.reset(self.entries.len());
|
||||
cx.emit(StackFrameListEvent::BuiltEntries);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -573,6 +555,10 @@ impl StackFrameList {
|
||||
|
||||
fn select_ix(&mut self, ix: Option<usize>, cx: &mut Context<Self>) {
|
||||
self.selected_ix = ix;
|
||||
if let Some(ix) = self.selected_ix {
|
||||
self.scroll_handle
|
||||
.scroll_to_item(ix, ScrollStrategy::Center);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -656,8 +642,15 @@ impl StackFrameList {
|
||||
self.activate_selected_entry(window, cx);
|
||||
}
|
||||
|
||||
fn render_list(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
list(self.list_state.clone()).size_full()
|
||||
fn render_list(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
uniform_list(
|
||||
cx.entity(),
|
||||
"stack-frame-list",
|
||||
self.entries.len(),
|
||||
|this, range, _window, cx| range.map(|ix| this.render_entry(ix, cx)).collect(),
|
||||
)
|
||||
.track_scroll(self.scroll_handle.clone())
|
||||
.size_full()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -980,11 +980,10 @@ impl Render for VariableList {
|
||||
.on_action(cx.listener(Self::edit_variable))
|
||||
.child(
|
||||
uniform_list(
|
||||
cx.entity().clone(),
|
||||
"variable-list",
|
||||
self.entries.len(),
|
||||
cx.processor(move |this, range: Range<usize>, window, cx| {
|
||||
this.render_entries(range, window, cx)
|
||||
}),
|
||||
move |this, range, window, cx| this.render_entries(range, window, cx),
|
||||
)
|
||||
.track_scroll(self.list_handle.clone())
|
||||
.gap_1_5()
|
||||
|
||||
@@ -35,6 +35,7 @@ assets.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
convert_case.workspace = true
|
||||
dap.workspace = true
|
||||
db.workspace = true
|
||||
|
||||
@@ -243,8 +243,6 @@ impl_actions!(
|
||||
]
|
||||
);
|
||||
|
||||
actions!(debugger, [RunToCursor, EvaluateSelectedText]);
|
||||
|
||||
actions!(
|
||||
editor,
|
||||
[
|
||||
@@ -428,6 +426,8 @@ actions!(
|
||||
DisableBreakpoint,
|
||||
EnableBreakpoint,
|
||||
EditLogBreakpoint,
|
||||
DebuggerRunToCursor,
|
||||
DebuggerEvaluateSelectedText,
|
||||
ToggleAutoSignatureHelp,
|
||||
ToggleGitBlameInline,
|
||||
OpenGitBlameCommit,
|
||||
|
||||
@@ -722,9 +722,10 @@ impl CompletionsMenu {
|
||||
let last_rendered_range = self.last_rendered_range.clone();
|
||||
let style = style.clone();
|
||||
let list = uniform_list(
|
||||
cx.entity().clone(),
|
||||
"completions",
|
||||
self.entries.borrow().len(),
|
||||
cx.processor(move |_editor, range: Range<usize>, _window, cx| {
|
||||
move |_editor, range, _window, cx| {
|
||||
last_rendered_range.borrow_mut().replace(range.clone());
|
||||
let start_ix = range.start;
|
||||
let completions_guard = completions.borrow_mut();
|
||||
@@ -836,7 +837,7 @@ impl CompletionsMenu {
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
},
|
||||
)
|
||||
.occlude()
|
||||
.max_h(max_height_in_lines as f32 * window.line_height())
|
||||
@@ -1451,9 +1452,10 @@ impl CodeActionsMenu {
|
||||
let actions = self.actions.clone();
|
||||
let selected_item = self.selected_item;
|
||||
let list = uniform_list(
|
||||
cx.entity().clone(),
|
||||
"code_actions_menu",
|
||||
self.actions.len(),
|
||||
cx.processor(move |_this, range: Range<usize>, _, cx| {
|
||||
move |_this, range, _, cx| {
|
||||
actions
|
||||
.iter()
|
||||
.skip(range.start)
|
||||
@@ -1516,7 +1518,7 @@ impl CodeActionsMenu {
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
},
|
||||
)
|
||||
.occlude()
|
||||
.max_h(max_height_in_lines as f32 * window.line_height())
|
||||
|
||||
@@ -464,7 +464,7 @@ impl BlockMap {
|
||||
map
|
||||
}
|
||||
|
||||
pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapReader<'_> {
|
||||
pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapReader {
|
||||
self.sync(&wrap_snapshot, edits);
|
||||
*self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
|
||||
BlockMapReader {
|
||||
@@ -479,7 +479,7 @@ impl BlockMap {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapWriter<'_> {
|
||||
pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapWriter {
|
||||
self.sync(&wrap_snapshot, edits);
|
||||
*self.wrap_snapshot.borrow_mut() = wrap_snapshot;
|
||||
BlockMapWriter(self)
|
||||
@@ -1327,7 +1327,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
|
||||
pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows {
|
||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||
cursor.seek(&start_row, Bias::Right, &());
|
||||
let (output_start, input_start) = cursor.start();
|
||||
|
||||
@@ -357,7 +357,7 @@ impl FoldMap {
|
||||
&mut self,
|
||||
inlay_snapshot: InlaySnapshot,
|
||||
edits: Vec<InlayEdit>,
|
||||
) -> (FoldMapWriter<'_>, FoldSnapshot, Vec<FoldEdit>) {
|
||||
) -> (FoldMapWriter, FoldSnapshot, Vec<FoldEdit>) {
|
||||
let (snapshot, edits) = self.read(inlay_snapshot, edits);
|
||||
(FoldMapWriter(self), snapshot, edits)
|
||||
}
|
||||
@@ -730,7 +730,7 @@ impl FoldSnapshot {
|
||||
(line_end - line_start) as u32
|
||||
}
|
||||
|
||||
pub fn row_infos(&self, start_row: u32) -> FoldRows<'_> {
|
||||
pub fn row_infos(&self, start_row: u32) -> FoldRows {
|
||||
if start_row > self.transforms.summary().output.lines.row {
|
||||
panic!("invalid display row {}", start_row);
|
||||
}
|
||||
|
||||
@@ -726,7 +726,7 @@ impl WrapSnapshot {
|
||||
self.transforms.summary().output.longest_row
|
||||
}
|
||||
|
||||
pub fn row_infos(&self, start_row: u32) -> WrapRows<'_> {
|
||||
pub fn row_infos(&self, start_row: u32) -> WrapRows {
|
||||
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
|
||||
let mut input_row = transforms.start().1.row();
|
||||
|
||||
@@ -213,14 +213,11 @@ use workspace::{
|
||||
searchable::SearchEvent,
|
||||
};
|
||||
|
||||
use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
|
||||
use crate::{
|
||||
code_context_menus::CompletionsMenuSource,
|
||||
hover_links::{find_url, find_url_from_range},
|
||||
};
|
||||
use crate::{
|
||||
editor_settings::MultiCursorModifier,
|
||||
signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
|
||||
};
|
||||
|
||||
pub const FILE_HEADER_HEIGHT: u32 = 2;
|
||||
pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
|
||||
@@ -240,6 +237,7 @@ pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration:
|
||||
|
||||
pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
|
||||
pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
|
||||
pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
|
||||
pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
|
||||
|
||||
pub type RenderDiffHunkControlsFn = Arc<
|
||||
@@ -255,6 +253,14 @@ pub type RenderDiffHunkControlsFn = Arc<
|
||||
) -> AnyElement,
|
||||
>;
|
||||
|
||||
const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
|
||||
alt: true,
|
||||
shift: true,
|
||||
control: false,
|
||||
platform: false,
|
||||
function: false,
|
||||
};
|
||||
|
||||
struct InlineValueCache {
|
||||
enabled: bool,
|
||||
inlays: Vec<InlayId>,
|
||||
@@ -697,7 +703,7 @@ impl EditorActionId {
|
||||
// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
|
||||
|
||||
type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
|
||||
type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
|
||||
type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
|
||||
|
||||
#[derive(Default)]
|
||||
struct ScrollbarMarkerState {
|
||||
@@ -905,25 +911,9 @@ struct InlineBlamePopover {
|
||||
popover_state: InlineBlamePopoverState,
|
||||
}
|
||||
|
||||
enum SelectionDragState {
|
||||
/// State when no drag related activity is detected.
|
||||
None,
|
||||
/// State when the mouse is down on a selection that is about to be dragged.
|
||||
ReadyToDrag {
|
||||
selection: Selection<Anchor>,
|
||||
click_position: gpui::Point<Pixels>,
|
||||
},
|
||||
/// State when the mouse is dragging the selection in the editor.
|
||||
Dragging {
|
||||
selection: Selection<Anchor>,
|
||||
drop_cursor: Selection<Anchor>,
|
||||
hide_drop_cursor: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
|
||||
/// a breakpoint on them.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct PhantomBreakpointIndicator {
|
||||
display_row: DisplayRow,
|
||||
/// There's a small debounce between hovering over the line and showing the indicator.
|
||||
@@ -931,7 +921,6 @@ struct PhantomBreakpointIndicator {
|
||||
is_active: bool,
|
||||
collides_with_existing_breakpoint: bool,
|
||||
}
|
||||
|
||||
/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
|
||||
///
|
||||
/// See the [module level documentation](self) for more information.
|
||||
@@ -1053,9 +1042,8 @@ pub struct Editor {
|
||||
style: Option<EditorStyle>,
|
||||
text_style_refinement: Option<TextStyleRefinement>,
|
||||
next_editor_action_id: EditorActionId,
|
||||
editor_actions: Rc<
|
||||
RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
|
||||
>,
|
||||
editor_actions:
|
||||
Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
|
||||
use_autoclose: bool,
|
||||
use_auto_surround: bool,
|
||||
auto_replace_emoji_shortcode: bool,
|
||||
@@ -1108,8 +1096,6 @@ pub struct Editor {
|
||||
hide_mouse_mode: HideMouseMode,
|
||||
pub change_list: ChangeList,
|
||||
inline_value_cache: InlineValueCache,
|
||||
selection_drag_state: SelectionDragState,
|
||||
drag_and_drop_selection_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
||||
@@ -1201,12 +1187,10 @@ struct SelectionHistoryEntry {
|
||||
add_selections_state: Option<AddSelectionsState>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum SelectionHistoryMode {
|
||||
Normal,
|
||||
Undoing,
|
||||
Redoing,
|
||||
Skipping,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
@@ -1240,19 +1224,11 @@ struct SelectionHistory {
|
||||
}
|
||||
|
||||
impl SelectionHistory {
|
||||
#[track_caller]
|
||||
fn insert_transaction(
|
||||
&mut self,
|
||||
transaction_id: TransactionId,
|
||||
selections: Arc<[Selection<Anchor>]>,
|
||||
) {
|
||||
if selections.is_empty() {
|
||||
log::error!(
|
||||
"SelectionHistory::insert_transaction called with empty selections. Caller: {}",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
return;
|
||||
}
|
||||
self.selections_by_transaction
|
||||
.insert(transaction_id, (selections, None));
|
||||
}
|
||||
@@ -1282,7 +1258,6 @@ impl SelectionHistory {
|
||||
}
|
||||
SelectionHistoryMode::Undoing => self.push_redo(entry),
|
||||
SelectionHistoryMode::Redoing => self.push_undo(entry),
|
||||
SelectionHistoryMode::Skipping => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2015,8 +1990,6 @@ impl Editor {
|
||||
.unwrap_or_default(),
|
||||
change_list: ChangeList::new(),
|
||||
mode,
|
||||
selection_drag_state: SelectionDragState::None,
|
||||
drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
|
||||
};
|
||||
if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
|
||||
editor
|
||||
@@ -2102,11 +2075,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
// skip adding the initial selection to selection history
|
||||
editor.selection_history.mode = SelectionHistoryMode::Skipping;
|
||||
editor.end_selection(window, cx);
|
||||
editor.selection_history.mode = SelectionHistoryMode::Normal;
|
||||
|
||||
editor.scroll_manager.show_scrollbars(window, cx);
|
||||
jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
|
||||
|
||||
@@ -3566,7 +3535,6 @@ impl Editor {
|
||||
|
||||
pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.selection_mark_mode = false;
|
||||
self.selection_drag_state = SelectionDragState::None;
|
||||
|
||||
if self.clear_expanded_diff_hunks(cx) {
|
||||
cx.notify();
|
||||
@@ -5139,13 +5107,10 @@ impl Editor {
|
||||
.as_ref()
|
||||
.map_or(true, |provider| provider.filter_completions());
|
||||
|
||||
// When `is_incomplete` is false, can filter completions instead of re-querying when the
|
||||
// current query is a suffix of the initial query.
|
||||
if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
|
||||
if filter_completions {
|
||||
menu.filter(query.clone(), provider.clone(), window, cx);
|
||||
}
|
||||
// When `is_incomplete` is false, no need to re-query completions when the current query
|
||||
// is a suffix of the initial query.
|
||||
if !menu.is_incomplete {
|
||||
if !menu.is_incomplete && filter_completions {
|
||||
// If the new query is a suffix of the old query (typing more characters) and
|
||||
// the previous result was complete, the existing completions can be filtered.
|
||||
//
|
||||
@@ -5163,6 +5128,7 @@ impl Editor {
|
||||
menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
|
||||
};
|
||||
if position_matches {
|
||||
menu.filter(query.clone(), provider.clone(), window, cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -7125,29 +7091,6 @@ impl Editor {
|
||||
)
|
||||
}
|
||||
|
||||
fn multi_cursor_modifier(
|
||||
cursor_event: bool,
|
||||
modifiers: &Modifiers,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
|
||||
if cursor_event {
|
||||
match multi_cursor_setting {
|
||||
MultiCursorModifier::Alt => modifiers.alt,
|
||||
MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
|
||||
}
|
||||
} else {
|
||||
match multi_cursor_setting {
|
||||
MultiCursorModifier::Alt => modifiers.secondary(),
|
||||
MultiCursorModifier::CmdOrCtrl => modifiers.alt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
|
||||
modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
|
||||
}
|
||||
|
||||
fn update_selection_mode(
|
||||
&mut self,
|
||||
modifiers: &Modifiers,
|
||||
@@ -7155,10 +7098,7 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
|
||||
if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
|
||||
|| self.selections.pending.is_none()
|
||||
{
|
||||
if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7541,7 +7481,8 @@ impl Editor {
|
||||
"Set Breakpoint"
|
||||
};
|
||||
|
||||
let run_to_cursor = window.is_action_available(&RunToCursor, cx);
|
||||
let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
|
||||
.map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
|
||||
|
||||
let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
|
||||
BreakpointState::Enabled => Some("Disable"),
|
||||
@@ -7565,7 +7506,7 @@ impl Editor {
|
||||
})
|
||||
.ok();
|
||||
|
||||
window.dispatch_action(Box::new(RunToCursor), cx);
|
||||
window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
|
||||
})
|
||||
.separator()
|
||||
})
|
||||
@@ -10622,44 +10563,6 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn move_selection_on_drop(
|
||||
&mut self,
|
||||
selection: &Selection<Anchor>,
|
||||
target: DisplayPoint,
|
||||
is_cut: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = &display_map.buffer_snapshot;
|
||||
let mut edits = Vec::new();
|
||||
let insert_point = display_map
|
||||
.clip_point(target, Bias::Left)
|
||||
.to_point(&display_map);
|
||||
let text = buffer
|
||||
.text_for_range(selection.start..selection.end)
|
||||
.collect::<String>();
|
||||
if is_cut {
|
||||
edits.push(((selection.start..selection.end), String::new()));
|
||||
}
|
||||
let insert_anchor = buffer.anchor_before(insert_point);
|
||||
edits.push(((insert_anchor..insert_anchor), text));
|
||||
let last_edit_start = insert_anchor.bias_left(buffer);
|
||||
let last_edit_end = insert_anchor.bias_right(buffer);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, None, cx);
|
||||
});
|
||||
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.select_anchor_ranges([last_edit_start..last_edit_end]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn clear_selection_drag_state(&mut self) {
|
||||
self.selection_drag_state = SelectionDragState::None;
|
||||
}
|
||||
|
||||
pub fn duplicate(
|
||||
&mut self,
|
||||
upwards: bool,
|
||||
@@ -14037,8 +13940,7 @@ impl Editor {
|
||||
prefer_lsp && !lsp_tasks_by_rows.is_empty(),
|
||||
new_rows,
|
||||
cx.clone(),
|
||||
)
|
||||
.await;
|
||||
);
|
||||
editor
|
||||
.update(cx, |editor, _| {
|
||||
editor.clear_tasks();
|
||||
@@ -14068,40 +13970,35 @@ impl Editor {
|
||||
snapshot: DisplaySnapshot,
|
||||
prefer_lsp: bool,
|
||||
runnable_ranges: Vec<RunnableRange>,
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
|
||||
cx.spawn(async move |cx| {
|
||||
let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
|
||||
for mut runnable in runnable_ranges {
|
||||
let Some(tasks) = cx
|
||||
mut cx: AsyncWindowContext,
|
||||
) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
|
||||
runnable_ranges
|
||||
.into_iter()
|
||||
.filter_map(|mut runnable| {
|
||||
let mut tasks = cx
|
||||
.update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
|
||||
.ok()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let mut tasks = tasks.await;
|
||||
|
||||
.ok()?;
|
||||
if prefer_lsp {
|
||||
tasks.retain(|(task_kind, _)| {
|
||||
!matches!(task_kind, TaskSourceKind::Language { .. })
|
||||
});
|
||||
}
|
||||
if tasks.is_empty() {
|
||||
continue;
|
||||
return None;
|
||||
}
|
||||
|
||||
let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
|
||||
let Some(row) = snapshot
|
||||
|
||||
let row = snapshot
|
||||
.buffer_snapshot
|
||||
.buffer_line_for_row(MultiBufferRow(point.row))
|
||||
.map(|(_, range)| range.start.row)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
.buffer_line_for_row(MultiBufferRow(point.row))?
|
||||
.1
|
||||
.start
|
||||
.row;
|
||||
|
||||
let context_range =
|
||||
BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
|
||||
runnable_rows.push((
|
||||
Some((
|
||||
(runnable.buffer_id, row),
|
||||
RunnableTasks {
|
||||
templates: tasks,
|
||||
@@ -14112,17 +14009,16 @@ impl Editor {
|
||||
column: point.column,
|
||||
extra_variables: runnable.extra_captures,
|
||||
},
|
||||
));
|
||||
}
|
||||
runnable_rows
|
||||
})
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn templates_with_tags(
|
||||
project: &Entity<Project>,
|
||||
runnable: &mut Runnable,
|
||||
cx: &mut App,
|
||||
) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
|
||||
) -> Vec<(TaskSourceKind, TaskTemplate)> {
|
||||
let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
|
||||
let (worktree_id, file) = project
|
||||
.buffer_for_id(runnable.buffer, cx)
|
||||
@@ -14137,40 +14033,39 @@ impl Editor {
|
||||
)
|
||||
});
|
||||
|
||||
let tags = mem::take(&mut runnable.tags);
|
||||
let language = runnable.language.clone();
|
||||
cx.spawn(async move |cx| {
|
||||
let mut templates_with_tags = Vec::new();
|
||||
if let Some(inventory) = inventory {
|
||||
for RunnableTag(tag) in tags {
|
||||
let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
|
||||
inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
|
||||
}) else {
|
||||
return templates_with_tags;
|
||||
};
|
||||
templates_with_tags.extend(new_tasks.await.into_iter().filter(
|
||||
move |(_, template)| {
|
||||
template.tags.iter().any(|source_tag| source_tag == &tag)
|
||||
},
|
||||
));
|
||||
}
|
||||
let mut templates_with_tags = mem::take(&mut runnable.tags)
|
||||
.into_iter()
|
||||
.flat_map(|RunnableTag(tag)| {
|
||||
inventory
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.flat_map(|inventory| {
|
||||
inventory.read(cx).list_tasks(
|
||||
file.clone(),
|
||||
Some(runnable.language.clone()),
|
||||
worktree_id,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.filter(move |(_, template)| {
|
||||
template.tags.iter().any(|source_tag| source_tag == &tag)
|
||||
})
|
||||
})
|
||||
.sorted_by_key(|(kind, _)| kind.to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
if let Some((leading_tag_source, _)) = templates_with_tags.first() {
|
||||
// Strongest source wins; if we have worktree tag binding, prefer that to
|
||||
// global and language bindings;
|
||||
// if we have a global binding, prefer that to language binding.
|
||||
let first_mismatch = templates_with_tags
|
||||
.iter()
|
||||
.position(|(tag_source, _)| tag_source != leading_tag_source);
|
||||
if let Some(index) = first_mismatch {
|
||||
templates_with_tags.truncate(index);
|
||||
}
|
||||
templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
|
||||
}
|
||||
|
||||
if let Some((leading_tag_source, _)) = templates_with_tags.first() {
|
||||
// Strongest source wins; if we have worktree tag binding, prefer that to
|
||||
// global and language bindings;
|
||||
// if we have a global binding, prefer that to language binding.
|
||||
let first_mismatch = templates_with_tags
|
||||
.iter()
|
||||
.position(|(tag_source, _)| tag_source != leading_tag_source);
|
||||
if let Some(index) = first_mismatch {
|
||||
templates_with_tags.truncate(index);
|
||||
}
|
||||
}
|
||||
|
||||
templates_with_tags
|
||||
})
|
||||
templates_with_tags
|
||||
}
|
||||
|
||||
pub fn move_to_enclosing_bracket(
|
||||
@@ -14237,20 +14132,18 @@ impl Editor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
|
||||
self.end_selection(window, cx);
|
||||
self.selection_history.mode = SelectionHistoryMode::Undoing;
|
||||
if let Some(entry) = self.selection_history.undo_stack.pop_back() {
|
||||
self.selection_history.mode = SelectionHistoryMode::Undoing;
|
||||
self.with_selection_effects_deferred(window, cx, |this, window, cx| {
|
||||
this.end_selection(window, cx);
|
||||
this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
|
||||
s.select_anchors(entry.selections.to_vec())
|
||||
});
|
||||
self.change_selections(None, window, cx, |s| {
|
||||
s.select_anchors(entry.selections.to_vec())
|
||||
});
|
||||
self.selection_history.mode = SelectionHistoryMode::Normal;
|
||||
|
||||
self.select_next_state = entry.select_next_state;
|
||||
self.select_prev_state = entry.select_prev_state;
|
||||
self.add_selections_state = entry.add_selections_state;
|
||||
self.request_autoscroll(Autoscroll::newest(), cx);
|
||||
}
|
||||
self.selection_history.mode = SelectionHistoryMode::Normal;
|
||||
}
|
||||
|
||||
pub fn redo_selection(
|
||||
@@ -14260,20 +14153,18 @@ impl Editor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
|
||||
self.end_selection(window, cx);
|
||||
self.selection_history.mode = SelectionHistoryMode::Redoing;
|
||||
if let Some(entry) = self.selection_history.redo_stack.pop_back() {
|
||||
self.selection_history.mode = SelectionHistoryMode::Redoing;
|
||||
self.with_selection_effects_deferred(window, cx, |this, window, cx| {
|
||||
this.end_selection(window, cx);
|
||||
this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
|
||||
s.select_anchors(entry.selections.to_vec())
|
||||
});
|
||||
self.change_selections(None, window, cx, |s| {
|
||||
s.select_anchors(entry.selections.to_vec())
|
||||
});
|
||||
self.selection_history.mode = SelectionHistoryMode::Normal;
|
||||
|
||||
self.select_next_state = entry.select_next_state;
|
||||
self.select_prev_state = entry.select_prev_state;
|
||||
self.add_selections_state = entry.add_selections_state;
|
||||
self.request_autoscroll(Autoscroll::newest(), cx);
|
||||
}
|
||||
self.selection_history.mode = SelectionHistoryMode::Normal;
|
||||
}
|
||||
|
||||
pub fn expand_excerpts(
|
||||
@@ -16086,16 +15977,13 @@ impl Editor {
|
||||
window: &Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<()> {
|
||||
if !self.mode().is_full() {
|
||||
return None;
|
||||
}
|
||||
let project = self.project.as_ref()?.downgrade();
|
||||
let pull_diagnostics_settings = ProjectSettings::get_global(cx)
|
||||
.diagnostics
|
||||
.lsp_pull_diagnostics;
|
||||
if !pull_diagnostics_settings.enabled {
|
||||
return None;
|
||||
}
|
||||
let project = self.project.as_ref()?.downgrade();
|
||||
let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
|
||||
let mut buffers = self.buffer.read(cx).all_buffers();
|
||||
if let Some(buffer_id) = buffer_id {
|
||||
@@ -18146,9 +18034,16 @@ impl Editor {
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
.iter()
|
||||
.map(|range| Location {
|
||||
buffer: buffer.clone(),
|
||||
range: range.start.text_anchor..range.end.text_anchor,
|
||||
.map(|selection| {
|
||||
let range = if selection.reversed {
|
||||
selection.end.text_anchor..selection.start.text_anchor
|
||||
} else {
|
||||
selection.start.text_anchor..selection.end.text_anchor
|
||||
};
|
||||
Location {
|
||||
buffer: buffer.clone(),
|
||||
range,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -18403,12 +18298,12 @@ impl Editor {
|
||||
|
||||
pub fn highlight_gutter<T: 'static>(
|
||||
&mut self,
|
||||
ranges: impl Into<Vec<Range<Anchor>>>,
|
||||
ranges: &[Range<Anchor>],
|
||||
color_fetcher: fn(&App) -> Hsla,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.gutter_highlights
|
||||
.insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
|
||||
.insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -18420,65 +18315,6 @@ impl Editor {
|
||||
self.gutter_highlights.remove(&TypeId::of::<T>())
|
||||
}
|
||||
|
||||
pub fn insert_gutter_highlight<T: 'static>(
|
||||
&mut self,
|
||||
range: Range<Anchor>,
|
||||
color_fetcher: fn(&App) -> Hsla,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer().read(cx).snapshot(cx);
|
||||
let mut highlights = self
|
||||
.gutter_highlights
|
||||
.remove(&TypeId::of::<T>())
|
||||
.map(|(_, highlights)| highlights)
|
||||
.unwrap_or_default();
|
||||
let ix = highlights.binary_search_by(|highlight| {
|
||||
Ordering::Equal
|
||||
.then_with(|| highlight.start.cmp(&range.start, &snapshot))
|
||||
.then_with(|| highlight.end.cmp(&range.end, &snapshot))
|
||||
});
|
||||
if let Err(ix) = ix {
|
||||
highlights.insert(ix, range);
|
||||
}
|
||||
self.gutter_highlights
|
||||
.insert(TypeId::of::<T>(), (color_fetcher, highlights));
|
||||
}
|
||||
|
||||
pub fn remove_gutter_highlights<T: 'static>(
|
||||
&mut self,
|
||||
ranges_to_remove: Vec<Range<Anchor>>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer().read(cx).snapshot(cx);
|
||||
let Some((color_fetcher, mut gutter_highlights)) =
|
||||
self.gutter_highlights.remove(&TypeId::of::<T>())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let mut ranges_to_remove = ranges_to_remove.iter().peekable();
|
||||
gutter_highlights.retain(|highlight| {
|
||||
while let Some(range_to_remove) = ranges_to_remove.peek() {
|
||||
match range_to_remove.end.cmp(&highlight.start, &snapshot) {
|
||||
Ordering::Less | Ordering::Equal => {
|
||||
ranges_to_remove.next();
|
||||
}
|
||||
Ordering::Greater => {
|
||||
match range_to_remove.start.cmp(&highlight.end, &snapshot) {
|
||||
Ordering::Less | Ordering::Equal => {
|
||||
return false;
|
||||
}
|
||||
Ordering::Greater => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
self.gutter_highlights
|
||||
.insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn all_text_background_highlights(
|
||||
&self,
|
||||
@@ -18934,11 +18770,6 @@ impl Editor {
|
||||
cx.emit(EditorEvent::BufferEdited);
|
||||
cx.emit(SearchEvent::MatchesInvalidated);
|
||||
if *singleton_buffer_edited {
|
||||
if let Some(buffer) = edited_buffer {
|
||||
if buffer.read(cx).file().is_none() {
|
||||
cx.emit(EditorEvent::TitleChanged);
|
||||
}
|
||||
}
|
||||
if let Some(project) = &self.project {
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
|
||||
@@ -19130,7 +18961,6 @@ impl Editor {
|
||||
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
|
||||
self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
|
||||
self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
|
||||
self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
|
||||
}
|
||||
|
||||
if old_cursor_shape != self.cursor_shape {
|
||||
@@ -19741,7 +19571,7 @@ impl Editor {
|
||||
.flatten()
|
||||
.filter_map(|keystroke| {
|
||||
if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
|
||||
keystroke.key_char.clone()
|
||||
Some(keystroke.key_char.clone().unwrap_or(keystroke.key.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -19818,21 +19648,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_action_renderer(
|
||||
&mut self,
|
||||
listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
|
||||
) -> Subscription {
|
||||
let id = self.next_editor_action_id.post_inc();
|
||||
self.editor_actions
|
||||
.borrow_mut()
|
||||
.insert(id, Box::new(listener));
|
||||
|
||||
let editor_actions = self.editor_actions.clone();
|
||||
Subscription::new(move || {
|
||||
editor_actions.borrow_mut().remove(&id);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn register_action<A: Action>(
|
||||
&mut self,
|
||||
listener: impl Fn(&A, &mut Window, &mut App) + 'static,
|
||||
@@ -19841,7 +19656,7 @@ impl Editor {
|
||||
let listener = Arc::new(listener);
|
||||
self.editor_actions.borrow_mut().insert(
|
||||
id,
|
||||
Box::new(move |_, window, _| {
|
||||
Box::new(move |window, _| {
|
||||
let listener = listener.clone();
|
||||
window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
|
||||
let action = action.downcast_ref().unwrap();
|
||||
@@ -20039,15 +19854,12 @@ impl Editor {
|
||||
if !selections.is_empty() {
|
||||
let snapshot =
|
||||
buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
|
||||
// skip adding the initial selection to selection history
|
||||
self.selection_history.mode = SelectionHistoryMode::Skipping;
|
||||
self.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges(selections.into_iter().map(|(start, end)| {
|
||||
snapshot.clip_offset(start, Bias::Left)
|
||||
..snapshot.clip_offset(end, Bias::Right)
|
||||
}));
|
||||
});
|
||||
self.selection_history.mode = SelectionHistoryMode::Normal;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -20073,13 +19885,8 @@ fn process_completion_for_edit(
|
||||
let buffer = buffer.read(cx);
|
||||
let buffer_snapshot = buffer.snapshot();
|
||||
let (snippet, new_text) = if completion.is_snippet() {
|
||||
// Workaround for typescript language server issues so that methods don't expand within
|
||||
// strings and functions with type expressions. The previous point is used because the query
|
||||
// for function identifier doesn't match when the cursor is immediately after. See PR #30312
|
||||
let mut snippet_source = completion.new_text.clone();
|
||||
let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
|
||||
previous_point.column = previous_point.column.saturating_sub(1);
|
||||
if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
|
||||
if let Some(scope) = buffer_snapshot.language_scope_at(cursor_position) {
|
||||
if scope.prefers_label_for_snippet_in_completion() {
|
||||
if let Some(label) = completion.label() {
|
||||
if matches!(
|
||||
@@ -21557,8 +21364,7 @@ impl EditorSnapshot {
|
||||
.unwrap_or(gutter_settings.line_numbers);
|
||||
let line_gutter_width = if show_line_numbers {
|
||||
// Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
|
||||
let min_width_for_number_on_gutter =
|
||||
em_advance * gutter_settings.min_line_number_digits as f32;
|
||||
let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
|
||||
max_line_number_width.max(min_width_for_number_on_gutter)
|
||||
} else {
|
||||
0.0.into()
|
||||
|
||||
@@ -49,7 +49,6 @@ pub struct EditorSettings {
|
||||
#[serde(default)]
|
||||
pub diagnostics_max_severity: Option<DiagnosticSeverity>,
|
||||
pub inline_code_actions: bool,
|
||||
pub drag_and_drop_selection: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
@@ -150,7 +149,6 @@ impl Minimap {
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct Gutter {
|
||||
pub min_line_number_digits: usize,
|
||||
pub line_numbers: bool,
|
||||
pub runnables: bool,
|
||||
pub breakpoints: bool,
|
||||
@@ -424,7 +422,7 @@ pub struct EditorSettingsContent {
|
||||
/// Default: always
|
||||
pub seed_search_query_from_cursor: Option<SeedQuerySetting>,
|
||||
pub use_smartcase_search: Option<bool>,
|
||||
/// Determines the modifier to be used to add multiple cursors with the mouse. The open hover link mouse gestures will adapt such that it do not conflict with the multicursor modifier.
|
||||
/// The key to use for adding multiple cursors
|
||||
///
|
||||
/// Default: alt
|
||||
pub multi_cursor_modifier: Option<MultiCursorModifier>,
|
||||
@@ -497,11 +495,6 @@ pub struct EditorSettingsContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub inline_code_actions: Option<bool>,
|
||||
|
||||
/// Whether to allow drag and drop text selection in buffer.
|
||||
///
|
||||
/// Default: true
|
||||
pub drag_and_drop_selection: Option<bool>,
|
||||
}
|
||||
|
||||
// Toolbar related settings
|
||||
@@ -610,10 +603,6 @@ pub struct GutterContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub line_numbers: Option<bool>,
|
||||
/// Minimum number of characters to reserve space for in the gutter.
|
||||
///
|
||||
/// Default: 4
|
||||
pub min_line_number_digits: Option<usize>,
|
||||
/// Whether to show runnable buttons in the gutter.
|
||||
///
|
||||
/// Default: true
|
||||
|
||||
@@ -1907,6 +1907,7 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
|
||||
DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
|
||||
])
|
||||
});
|
||||
|
||||
editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
|
||||
assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
|
||||
|
||||
@@ -1926,29 +1927,29 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
|
||||
assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
|
||||
|
||||
editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
|
||||
assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
|
||||
assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
|
||||
|
||||
editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
|
||||
assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
|
||||
assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
|
||||
|
||||
editor.move_right(&MoveRight, window, cx);
|
||||
editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
|
||||
assert_selection_ranges(
|
||||
"use std::«ˇs»tr::{foo, bar}\n«ˇ\n» {baz.qux()}",
|
||||
"use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
|
||||
editor,
|
||||
cx,
|
||||
);
|
||||
|
||||
editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
|
||||
assert_selection_ranges(
|
||||
"use std«ˇ::s»tr::{foo, bar«ˇ}\n\n» {baz.qux()}",
|
||||
"use std«ˇ::s»tr::{foo, bar}\n\n«ˇ {b»az.qux()}",
|
||||
editor,
|
||||
cx,
|
||||
);
|
||||
|
||||
editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
|
||||
assert_selection_ranges(
|
||||
"use std::«ˇs»tr::{foo, bar}«ˇ\n\n» {baz.qux()}",
|
||||
"use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
|
||||
editor,
|
||||
cx,
|
||||
);
|
||||
@@ -21941,7 +21942,7 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
|
||||
.expect("created a singleton buffer")
|
||||
.read(cx)
|
||||
.remote_id();
|
||||
let buffer_result_id = project.lsp_store().read(cx).result_id(buffer_id, cx);
|
||||
let buffer_result_id = project.lsp_store().read(cx).result_id(buffer_id);
|
||||
assert_eq!(expected, buffer_result_id);
|
||||
});
|
||||
};
|
||||
@@ -21987,6 +21988,7 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
|
||||
"Cursor movement should not trigger diagnostic request"
|
||||
);
|
||||
ensure_result_id(Some("2".to_string()), cx);
|
||||
|
||||
// Multiple rapid edits should be debounced
|
||||
for _ in 0..5 {
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use crate::{
|
||||
ActiveDiagnostic, BlockId, CURSORS_VISIBLE_FOR, ChunkRendererContext, ChunkReplacement,
|
||||
CodeActionSource, ConflictsOurs, ConflictsOursMarker, ConflictsOuter, ConflictsTheirs,
|
||||
ConflictsTheirsMarker, ContextMenuPlacement, CursorShape, CustomBlockId, DisplayDiffHunk,
|
||||
DisplayPoint, DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode,
|
||||
Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, FILE_HEADER_HEIGHT,
|
||||
FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
|
||||
InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineHighlight, LineUp,
|
||||
MAX_LINE_LEN, MINIMAP_FONT_SIZE, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts, PageDown,
|
||||
PageUp, PhantomBreakpointIndicator, Point, RowExt, RowRangeExt, SelectPhase,
|
||||
SelectedTextHighlight, Selection, SelectionDragState, SoftWrap, StickyHeaderExcerpt, ToPoint,
|
||||
ToggleFold,
|
||||
ActiveDiagnostic, BlockId, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
|
||||
ChunkRendererContext, ChunkReplacement, CodeActionSource, ConflictsOurs, ConflictsOursMarker,
|
||||
ConflictsOuter, ConflictsTheirs, ConflictsTheirsMarker, ContextMenuPlacement, CursorShape,
|
||||
CustomBlockId, DisplayDiffHunk, DisplayPoint, DisplayRow, DocumentHighlightRead,
|
||||
DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode, EditorSettings, EditorSnapshot,
|
||||
EditorStyle, FILE_HEADER_HEIGHT, FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp,
|
||||
HandleInput, HoveredCursor, InlayHintRefreshReason, InlineCompletion, JumpData, LineDown,
|
||||
LineHighlight, LineUp, MAX_LINE_LEN, MIN_LINE_NUMBER_DIGITS, MINIMAP_FONT_SIZE,
|
||||
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts, PageDown, PageUp, PhantomBreakpointIndicator,
|
||||
Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
|
||||
StickyHeaderExcerpt, ToPoint, ToggleFold,
|
||||
code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
|
||||
display_map::{
|
||||
Block, BlockContext, BlockStyle, DisplaySnapshot, EditorMargins, FoldId, HighlightedChunk,
|
||||
@@ -17,7 +17,8 @@ use crate::{
|
||||
},
|
||||
editor_settings::{
|
||||
CurrentLineHighlight, DoubleClickInMultibuffer, MinimapThumb, MinimapThumbBorder,
|
||||
ScrollBeyondLastLine, ScrollbarAxes, ScrollbarDiagnostics, ShowMinimap, ShowScrollbar,
|
||||
MultiCursorModifier, ScrollBeyondLastLine, ScrollbarAxes, ScrollbarDiagnostics,
|
||||
ShowMinimap, ShowScrollbar,
|
||||
},
|
||||
git::blame::{BlameRenderer, GitBlame, GlobalBlameRenderer},
|
||||
hover_popover::{
|
||||
@@ -78,11 +79,10 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use sum_tree::Bias;
|
||||
use text::{BufferId, SelectionGoal};
|
||||
use text::BufferId;
|
||||
use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor};
|
||||
use ui::{ButtonLike, KeyBinding, POPOVER_Y_PADDING, Tooltip, h_flex, prelude::*};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use util::post_inc;
|
||||
use util::{RangeExt, ResultExt, debug_panic};
|
||||
use workspace::{CollaboratorId, Workspace, item::Item, notifications::NotifyTaskExt};
|
||||
|
||||
@@ -187,7 +187,7 @@ impl EditorElement {
|
||||
let editor = &self.editor;
|
||||
editor.update(cx, |editor, cx| {
|
||||
for action in editor.editor_actions.borrow().values() {
|
||||
(action)(editor, window, cx)
|
||||
(action)(window, cx)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -620,7 +620,6 @@ impl EditorElement {
|
||||
|
||||
let text_hitbox = &position_map.text_hitbox;
|
||||
let gutter_hitbox = &position_map.gutter_hitbox;
|
||||
let point_for_position = position_map.point_for_position(event.position);
|
||||
let mut click_count = event.click_count;
|
||||
let mut modifiers = event.modifiers;
|
||||
|
||||
@@ -634,20 +633,6 @@ impl EditorElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if editor.drag_and_drop_selection_enabled && click_count == 1 {
|
||||
let newest_anchor = editor.selections.newest_anchor();
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let selection = newest_anchor.map(|anchor| anchor.to_display_point(&snapshot));
|
||||
if point_for_position.intersects_selection(&selection) {
|
||||
editor.selection_drag_state = SelectionDragState::ReadyToDrag {
|
||||
selection: newest_anchor.clone(),
|
||||
click_position: event.position,
|
||||
};
|
||||
cx.stop_propagation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let is_singleton = editor.buffer().read(cx).is_singleton();
|
||||
|
||||
if click_count == 2 && !is_singleton {
|
||||
@@ -691,9 +676,9 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
let point_for_position = position_map.point_for_position(event.position);
|
||||
let position = point_for_position.previous_valid;
|
||||
let multi_cursor_modifier = Editor::multi_cursor_modifier(true, &modifiers, cx);
|
||||
if Editor::columnar_selection_modifiers(multi_cursor_modifier, &modifiers) {
|
||||
if modifiers == COLUMNAR_SELECTION_MODIFIERS {
|
||||
editor.select(
|
||||
SelectPhase::BeginColumnar {
|
||||
position,
|
||||
@@ -714,6 +699,11 @@ impl EditorElement {
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
|
||||
let multi_cursor_modifier = match multi_cursor_setting {
|
||||
MultiCursorModifier::Alt => modifiers.alt,
|
||||
MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
|
||||
};
|
||||
editor.select(
|
||||
SelectPhase::Begin {
|
||||
position,
|
||||
@@ -831,51 +821,6 @@ impl EditorElement {
|
||||
let text_hitbox = &position_map.text_hitbox;
|
||||
let end_selection = editor.has_pending_selection();
|
||||
let pending_nonempty_selections = editor.has_pending_nonempty_selection();
|
||||
let point_for_position = position_map.point_for_position(event.position);
|
||||
|
||||
match editor.selection_drag_state {
|
||||
SelectionDragState::ReadyToDrag {
|
||||
selection: _,
|
||||
ref click_position,
|
||||
} => {
|
||||
if event.position == *click_position {
|
||||
editor.select(
|
||||
SelectPhase::Begin {
|
||||
position: point_for_position.previous_valid,
|
||||
add: false,
|
||||
click_count: 1, // ready to drag state only occurs on click count 1
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.selection_drag_state = SelectionDragState::None;
|
||||
cx.stop_propagation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
SelectionDragState::Dragging { ref selection, .. } => {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let selection_display = selection.map(|anchor| anchor.to_display_point(&snapshot));
|
||||
if !point_for_position.intersects_selection(&selection_display)
|
||||
&& text_hitbox.is_hovered(window)
|
||||
{
|
||||
let is_cut = !(cfg!(target_os = "macos") && event.modifiers.alt
|
||||
|| cfg!(not(target_os = "macos")) && event.modifiers.control);
|
||||
editor.move_selection_on_drop(
|
||||
&selection.clone(),
|
||||
point_for_position.previous_valid,
|
||||
is_cut,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
editor.selection_drag_state = SelectionDragState::None;
|
||||
cx.stop_propagation();
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if end_selection {
|
||||
editor.select(SelectPhase::End, window, cx);
|
||||
@@ -922,9 +867,13 @@ impl EditorElement {
|
||||
let text_hitbox = &position_map.text_hitbox;
|
||||
let pending_nonempty_selections = editor.has_pending_nonempty_selection();
|
||||
|
||||
let hovered_link_modifier = Editor::multi_cursor_modifier(false, &event.modifiers(), cx);
|
||||
let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
|
||||
let multi_cursor_modifier = match multi_cursor_setting {
|
||||
MultiCursorModifier::Alt => event.modifiers().secondary(),
|
||||
MultiCursorModifier::CmdOrCtrl => event.modifiers().alt,
|
||||
};
|
||||
|
||||
if !pending_nonempty_selections && hovered_link_modifier && text_hitbox.is_hovered(window) {
|
||||
if !pending_nonempty_selections && multi_cursor_modifier && text_hitbox.is_hovered(window) {
|
||||
let point = position_map.point_for_position(event.up.position);
|
||||
editor.handle_click_hovered_link(point, event.modifiers(), window, cx);
|
||||
|
||||
@@ -939,16 +888,12 @@ impl EditorElement {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
if !editor.has_pending_selection()
|
||||
&& matches!(editor.selection_drag_state, SelectionDragState::None)
|
||||
{
|
||||
if !editor.has_pending_selection() {
|
||||
return;
|
||||
}
|
||||
|
||||
let text_hitbox = &position_map.text_hitbox;
|
||||
let text_bounds = text_hitbox.bounds;
|
||||
let text_bounds = position_map.text_hitbox.bounds;
|
||||
let point_for_position = position_map.point_for_position(event.position);
|
||||
|
||||
let mut scroll_delta = gpui::Point::<f32>::default();
|
||||
let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
|
||||
let top = text_bounds.origin.y + vertical_margin;
|
||||
@@ -980,49 +925,15 @@ impl EditorElement {
|
||||
scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
|
||||
}
|
||||
|
||||
if !editor.has_pending_selection() {
|
||||
let drop_anchor = position_map
|
||||
.snapshot
|
||||
.display_point_to_anchor(point_for_position.previous_valid, Bias::Left);
|
||||
match editor.selection_drag_state {
|
||||
SelectionDragState::Dragging {
|
||||
ref mut drop_cursor,
|
||||
ref mut hide_drop_cursor,
|
||||
..
|
||||
} => {
|
||||
drop_cursor.start = drop_anchor;
|
||||
drop_cursor.end = drop_anchor;
|
||||
*hide_drop_cursor = !text_hitbox.is_hovered(window);
|
||||
}
|
||||
SelectionDragState::ReadyToDrag { ref selection, .. } => {
|
||||
let drop_cursor = Selection {
|
||||
id: post_inc(&mut editor.selections.next_selection_id),
|
||||
start: drop_anchor,
|
||||
end: drop_anchor,
|
||||
reversed: false,
|
||||
goal: SelectionGoal::None,
|
||||
};
|
||||
editor.selection_drag_state = SelectionDragState::Dragging {
|
||||
selection: selection.clone(),
|
||||
drop_cursor,
|
||||
hide_drop_cursor: false,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
editor.apply_scroll_delta(scroll_delta, window, cx);
|
||||
cx.notify();
|
||||
} else {
|
||||
editor.select(
|
||||
SelectPhase::Update {
|
||||
position: point_for_position.previous_valid,
|
||||
goal_column: point_for_position.exact_unclipped.column(),
|
||||
scroll_delta,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
editor.select(
|
||||
SelectPhase::Update {
|
||||
position: point_for_position.previous_valid,
|
||||
goal_column: point_for_position.exact_unclipped.column(),
|
||||
scroll_delta,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_moved(
|
||||
@@ -1039,7 +950,7 @@ impl EditorElement {
|
||||
editor.set_gutter_hovered(gutter_hovered, cx);
|
||||
editor.mouse_cursor_hidden = false;
|
||||
|
||||
let breakpoint_indicator = if gutter_hovered {
|
||||
if gutter_hovered {
|
||||
let new_point = position_map
|
||||
.point_for_position(event.position)
|
||||
.previous_valid;
|
||||
@@ -1053,6 +964,7 @@ impl EditorElement {
|
||||
.buffer_for_excerpt(buffer_anchor.excerpt_id)
|
||||
.and_then(|buffer| buffer.file().map(|file| (buffer, file)))
|
||||
{
|
||||
let was_hovered = editor.gutter_breakpoint_indicator.0.is_some();
|
||||
let as_point = text::ToPoint::to_point(&buffer_anchor.text_anchor, buffer_snapshot);
|
||||
|
||||
let is_visible = editor
|
||||
@@ -1080,44 +992,39 @@ impl EditorElement {
|
||||
.is_some()
|
||||
});
|
||||
|
||||
if !is_visible {
|
||||
editor.gutter_breakpoint_indicator.1.get_or_insert_with(|| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(200))
|
||||
.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
if let Some(indicator) = this.gutter_breakpoint_indicator.0.as_mut()
|
||||
{
|
||||
indicator.is_active = true;
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
Some(PhantomBreakpointIndicator {
|
||||
editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
|
||||
display_row: new_point.row(),
|
||||
is_active: is_visible,
|
||||
collides_with_existing_breakpoint: has_existing_breakpoint,
|
||||
})
|
||||
});
|
||||
|
||||
editor.gutter_breakpoint_indicator.1.get_or_insert_with(|| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
if !was_hovered {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(200))
|
||||
.await;
|
||||
}
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
if let Some(indicator) = this.gutter_breakpoint_indicator.0.as_mut() {
|
||||
indicator.is_active = true;
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
});
|
||||
} else {
|
||||
editor.gutter_breakpoint_indicator.1 = None;
|
||||
None
|
||||
editor.gutter_breakpoint_indicator = (None, None);
|
||||
}
|
||||
} else {
|
||||
editor.gutter_breakpoint_indicator.1 = None;
|
||||
None
|
||||
};
|
||||
|
||||
if &breakpoint_indicator != &editor.gutter_breakpoint_indicator.0 {
|
||||
editor.gutter_breakpoint_indicator.0 = breakpoint_indicator;
|
||||
cx.notify();
|
||||
editor.gutter_breakpoint_indicator = (None, None);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
|
||||
// Don't trigger hover popover if mouse is hovering over context menu
|
||||
if text_hitbox.is_hovered(window) {
|
||||
let point_for_position = position_map.point_for_position(event.position);
|
||||
@@ -1255,36 +1162,6 @@ impl EditorElement {
|
||||
|
||||
let player = editor.current_user_player_color(cx);
|
||||
selections.push((player, layouts));
|
||||
|
||||
if let SelectionDragState::Dragging {
|
||||
ref selection,
|
||||
ref drop_cursor,
|
||||
ref hide_drop_cursor,
|
||||
} = editor.selection_drag_state
|
||||
{
|
||||
if !hide_drop_cursor
|
||||
&& (drop_cursor
|
||||
.start
|
||||
.cmp(&selection.start, &snapshot.buffer_snapshot)
|
||||
.eq(&Ordering::Less)
|
||||
|| drop_cursor
|
||||
.end
|
||||
.cmp(&selection.end, &snapshot.buffer_snapshot)
|
||||
.eq(&Ordering::Greater))
|
||||
{
|
||||
let drag_cursor_layout = SelectionLayout::new(
|
||||
drop_cursor.clone(),
|
||||
false,
|
||||
CursorShape::Bar,
|
||||
&snapshot.display_snapshot,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
let absent_color = cx.theme().players().absent();
|
||||
selections.push((absent_color, vec![drag_cursor_layout]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(collaboration_hub) = &editor.collaboration_hub {
|
||||
@@ -2826,8 +2703,7 @@ impl EditorElement {
|
||||
let available_width = gutter_dimensions.left_padding - git_gutter_width;
|
||||
|
||||
let editor = self.editor.clone();
|
||||
let is_wide = max_line_number_length
|
||||
>= EditorSettings::get_global(cx).gutter.min_line_number_digits as u32
|
||||
let is_wide = max_line_number_length >= MIN_LINE_NUMBER_DIGITS
|
||||
&& row_info
|
||||
.buffer_row
|
||||
.is_some_and(|row| (row + 1).ilog10() + 1 == max_line_number_length)
|
||||
@@ -7340,17 +7216,6 @@ impl LineWithInvisibles {
|
||||
paint(window, cx);
|
||||
}),
|
||||
|
||||
ShowWhitespaceSetting::Trailing => {
|
||||
let mut previous_start = self.len;
|
||||
for ([start, end], paint) in invisible_iter.rev() {
|
||||
if previous_start != end {
|
||||
break;
|
||||
}
|
||||
previous_start = start;
|
||||
paint(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
// For a whitespace to be on a boundary, any of the following conditions need to be met:
|
||||
// - It is a tab
|
||||
// - It is adjacent to an edge (start or end)
|
||||
@@ -9377,35 +9242,6 @@ impl PointForPosition {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intersects_selection(&self, selection: &Selection<DisplayPoint>) -> bool {
|
||||
let Some(valid_point) = self.as_valid() else {
|
||||
return false;
|
||||
};
|
||||
let range = selection.range();
|
||||
|
||||
let candidate_row = valid_point.row();
|
||||
let candidate_col = valid_point.column();
|
||||
|
||||
let start_row = range.start.row();
|
||||
let start_col = range.start.column();
|
||||
let end_row = range.end.row();
|
||||
let end_col = range.end.column();
|
||||
|
||||
if candidate_row < start_row || candidate_row > end_row {
|
||||
false
|
||||
} else if start_row == end_row {
|
||||
candidate_col >= start_col && candidate_col < end_col
|
||||
} else {
|
||||
if candidate_row == start_row {
|
||||
candidate_col >= start_col
|
||||
} else if candidate_row == end_row {
|
||||
candidate_col < end_col
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PositionMap {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
Anchor, Editor, EditorSettings, EditorSnapshot, FindAllReferences, GoToDefinition,
|
||||
GoToTypeDefinition, GotoDefinitionKind, InlayId, Navigated, PointForPosition, SelectPhase,
|
||||
editor_settings::GoToDefinitionFallback,
|
||||
editor_settings::{GoToDefinitionFallback, MultiCursorModifier},
|
||||
hover_popover::{self, InlayHover},
|
||||
scroll::ScrollAmount,
|
||||
};
|
||||
@@ -120,7 +120,11 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let hovered_link_modifier = Editor::multi_cursor_modifier(false, &modifiers, cx);
|
||||
let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
|
||||
let hovered_link_modifier = match multi_cursor_setting {
|
||||
MultiCursorModifier::Alt => modifiers.secondary(),
|
||||
MultiCursorModifier::CmdOrCtrl => modifiers.alt,
|
||||
};
|
||||
if !hovered_link_modifier || self.has_pending_selection() {
|
||||
self.hide_hovered_link(cx);
|
||||
return;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
Copy, CopyAndTrim, CopyPermalinkToLine, Cut, DisplayPoint, DisplaySnapshot, Editor,
|
||||
EvaluateSelectedText, FindAllReferences, GoToDeclaration, GoToDefinition, GoToImplementation,
|
||||
GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, SelectionExt,
|
||||
ToDisplayPoint, ToggleCodeActions,
|
||||
Copy, CopyAndTrim, CopyPermalinkToLine, Cut, DebuggerEvaluateSelectedText, DisplayPoint,
|
||||
DisplaySnapshot, Editor, FindAllReferences, GoToDeclaration, GoToDefinition,
|
||||
GoToImplementation, GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode,
|
||||
SelectionExt, ToDisplayPoint, ToggleCodeActions,
|
||||
actions::{Format, FormatSelections},
|
||||
selections_collection::SelectionsCollection,
|
||||
};
|
||||
@@ -199,14 +199,17 @@ pub fn deploy_context_menu(
|
||||
.is_some()
|
||||
});
|
||||
|
||||
let evaluate_selection = window.is_action_available(&EvaluateSelectedText, cx);
|
||||
let evaluate_selection = command_palette_hooks::CommandPaletteFilter::try_global(cx)
|
||||
.map_or(false, |filter| {
|
||||
!filter.is_hidden(&DebuggerEvaluateSelectedText)
|
||||
});
|
||||
|
||||
ui::ContextMenu::build(window, cx, |menu, _window, _cx| {
|
||||
let builder = menu
|
||||
.on_blur_subscription(Subscription::new(|| {}))
|
||||
.when(evaluate_selection && has_selections, |builder| {
|
||||
builder
|
||||
.action("Evaluate Selection", Box::new(EvaluateSelectedText))
|
||||
.action("Evaluate Selection", Box::new(DebuggerEvaluateSelectedText))
|
||||
.separator()
|
||||
})
|
||||
.action("Go to Definition", Box::new(GoToDefinition))
|
||||
|
||||
@@ -266,11 +266,10 @@ pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> Displa
|
||||
|
||||
let mut is_first_iteration = true;
|
||||
find_preceding_boundary_display_point(map, point, FindRange::MultiLine, |left, right| {
|
||||
// Make alt-left skip punctuation to respect VSCode behaviour. For example: hello.| goes to |hello.
|
||||
// Make alt-left skip punctuation on Mac OS to respect Mac VSCode behaviour. For example: hello.| goes to |hello.
|
||||
if is_first_iteration
|
||||
&& classifier.is_punctuation(right)
|
||||
&& !classifier.is_punctuation(left)
|
||||
&& left != '\n'
|
||||
{
|
||||
is_first_iteration = false;
|
||||
return false;
|
||||
@@ -319,11 +318,10 @@ pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint
|
||||
let classifier = map.buffer_snapshot.char_classifier_at(raw_point);
|
||||
let mut is_first_iteration = true;
|
||||
find_boundary(map, point, FindRange::MultiLine, |left, right| {
|
||||
// Make alt-right skip punctuation to respect VSCode behaviour. For example: |.hello goes to .hello|
|
||||
// Make alt-right skip punctuation on Mac OS to respect the Mac behaviour. For example: |.hello goes to .hello|
|
||||
if is_first_iteration
|
||||
&& classifier.is_punctuation(left)
|
||||
&& !classifier.is_punctuation(right)
|
||||
&& right != '\n'
|
||||
{
|
||||
is_first_iteration = false;
|
||||
return false;
|
||||
|
||||
@@ -411,7 +411,7 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
self.collection.display_map(self.cx)
|
||||
}
|
||||
|
||||
pub fn buffer(&self) -> Ref<'_, MultiBufferSnapshot> {
|
||||
pub fn buffer(&self) -> Ref<MultiBufferSnapshot> {
|
||||
self.collection.buffer(self.cx)
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ interface dap {
|
||||
}
|
||||
|
||||
record debug-adapter-binary {
|
||||
command: option<string>,
|
||||
command: string,
|
||||
arguments: list<string>,
|
||||
envs: env-vars,
|
||||
cwd: option<string>,
|
||||
|
||||
@@ -1682,15 +1682,12 @@ impl ExtensionStore {
|
||||
|
||||
pub fn register_ssh_client(&mut self, client: Entity<SshRemoteClient>, cx: &mut Context<Self>) {
|
||||
let connection_options = client.read(cx).connection_options();
|
||||
let ssh_url = connection_options.ssh_url();
|
||||
|
||||
if let Some(existing_client) = self.ssh_clients.get(&ssh_url) {
|
||||
if existing_client.upgrade().is_some() {
|
||||
return;
|
||||
}
|
||||
if self.ssh_clients.contains_key(&connection_options.ssh_url()) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ssh_clients.insert(ssh_url, client.downgrade());
|
||||
self.ssh_clients
|
||||
.insert(connection_options.ssh_url(), client.downgrade());
|
||||
self.ssh_registered_tx.unbounded_send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,7 +724,7 @@ impl IncrementalCompilationCache {
|
||||
}
|
||||
|
||||
impl CacheStore for IncrementalCompilationCache {
|
||||
fn get(&self, key: &[u8]) -> Option<Cow<'_, [u8]>> {
|
||||
fn get(&self, key: &[u8]) -> Option<Cow<[u8]>> {
|
||||
self.cache.get(key).map(|v| v.into())
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user