Compare commits

..

1 Commits

Author SHA1 Message Date
Mikayla Maki
69e2130295 gpui 0.2.2 2025-10-21 20:22:21 -07:00
196 changed files with 4864 additions and 7042 deletions

103
Cargo.lock generated
View File

@@ -1182,20 +1182,6 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "async-tar"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1937db2d56578aa3919b9bdb0e5100693fd7d1c0f145c53eb81fbb03e217550"
dependencies = [
"async-std",
"filetime",
"libc",
"pin-project",
"redox_syscall 0.2.16",
"xattr",
]
[[package]]
name = "async-task"
version = "4.7.1"
@@ -1293,7 +1279,6 @@ name = "audio"
version = "0.1.0"
dependencies = [
"anyhow",
"async-tar",
"collections",
"crossbeam",
"denoise",
@@ -1307,6 +1292,7 @@ dependencies = [
"smol",
"thiserror 2.0.17",
"util",
"zed-async-tar",
]
[[package]]
@@ -3393,7 +3379,6 @@ dependencies = [
"rpc",
"scrypt",
"sea-orm",
"sea-orm-macros",
"semantic_version",
"semver",
"serde",
@@ -4473,7 +4458,6 @@ dependencies = [
"anyhow",
"async-compression",
"async-pipe",
"async-tar",
"async-trait",
"client",
"collections",
@@ -4500,6 +4484,7 @@ dependencies = [
"tree-sitter",
"tree-sitter-go",
"util",
"zed-async-tar",
"zlog",
]
@@ -5368,7 +5353,6 @@ dependencies = [
"rand 0.9.2",
"regex",
"release_channel",
"rope",
"rpc",
"schemars 1.0.4",
"serde",
@@ -5692,61 +5676,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "eval"
version = "0.1.0"
dependencies = [
"acp_thread",
"agent",
"agent-client-protocol",
"agent_settings",
"agent_ui",
"anyhow",
"async-trait",
"buffer_diff",
"chrono",
"clap",
"client",
"collections",
"debug_adapter_extension",
"dirs 4.0.0",
"dotenvy",
"env_logger 0.11.8",
"extension",
"fs",
"futures 0.3.31",
"gpui",
"gpui_tokio",
"handlebars 4.5.0",
"language",
"language_extension",
"language_model",
"language_models",
"languages",
"markdown",
"node_runtime",
"pathdiff",
"paths",
"pretty_assertions",
"project",
"prompt_store",
"rand 0.9.2",
"regex",
"release_channel",
"reqwest_client",
"serde",
"serde_json",
"settings",
"shellexpand 2.1.2",
"telemetry",
"terminal_view",
"toml 0.8.23",
"unindent",
"util",
"uuid",
"watch",
]
[[package]]
name = "event-listener"
version = "2.5.3"
@@ -5820,7 +5749,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-compression",
"async-tar",
"async-trait",
"collections",
"dap",
@@ -5843,6 +5771,7 @@ dependencies = [
"util",
"wasm-encoder 0.221.3",
"wasmparser 0.221.3",
"zed-async-tar",
]
[[package]]
@@ -5874,7 +5803,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-compression",
"async-tar",
"async-trait",
"client",
"collections",
@@ -5915,6 +5843,7 @@ dependencies = [
"wasmparser 0.221.3",
"wasmtime",
"wasmtime-wasi",
"zed-async-tar",
"zlog",
]
@@ -6375,7 +6304,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"ashpd 0.11.0",
"async-tar",
"async-trait",
"cocoa 0.26.0",
"collections",
@@ -6400,6 +6328,7 @@ dependencies = [
"time",
"util",
"windows 0.61.3",
"zed-async-tar",
]
[[package]]
@@ -7738,7 +7667,6 @@ dependencies = [
"anyhow",
"async-compression",
"async-fs",
"async-tar",
"bytes 1.10.1",
"derive_more 0.99.20",
"futures 0.3.31",
@@ -7752,6 +7680,7 @@ dependencies = [
"tempfile",
"url",
"util",
"zed-async-tar",
"zed-reqwest",
]
@@ -8947,7 +8876,6 @@ dependencies = [
"anyhow",
"async-compression",
"async-fs",
"async-tar",
"async-trait",
"chrono",
"collections",
@@ -9004,6 +8932,7 @@ dependencies = [
"url",
"util",
"workspace",
"zed-async-tar",
]
[[package]]
@@ -10247,7 +10176,6 @@ dependencies = [
"anyhow",
"async-compression",
"async-std",
"async-tar",
"async-trait",
"futures 0.3.31",
"http_client",
@@ -10260,6 +10188,7 @@ dependencies = [
"util",
"watch",
"which 6.0.3",
"zed-async-tar",
]
[[package]]
@@ -14955,7 +14884,6 @@ dependencies = [
"futures 0.3.31",
"gpui",
"language",
"lsp",
"menu",
"project",
"schemars 1.0.4",
@@ -20988,7 +20916,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.211.0"
version = "0.210.0"
dependencies = [
"acp_tools",
"activity_indicator",
@@ -21140,6 +21068,19 @@ dependencies = [
"zlog_settings",
]
[[package]]
name = "zed-async-tar"
version = "0.5.0-zed"
source = "git+https://github.com/zed-industries/async-tar?rev=a307f6bf3e4219c3a457bea0cab198b6d7c36e25#a307f6bf3e4219c3a457bea0cab198b6d7c36e25"
dependencies = [
"async-std",
"filetime",
"libc",
"pin-project",
"redox_syscall 0.2.16",
"xattr",
]
[[package]]
name = "zed-font-kit"
version = "0.14.1-zed"

View File

@@ -58,7 +58,7 @@ members = [
"crates/edit_prediction_context",
"crates/zeta2_tools",
"crates/editor",
"crates/eval",
# "crates/eval",
"crates/explorer_command_injector",
"crates/extension",
"crates/extension_api",
@@ -452,7 +452,8 @@ async-fs = "2.1"
async-lock = "2.1"
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
async-recursion = "1.0.0"
async-tar = "0.5.1"
# WARNING: If you change this, you must also publish a new version of zed-async-tar to crates.io
async-tar = { git = "https://github.com/zed-industries/async-tar", rev = "a307f6bf3e4219c3a457bea0cab198b6d7c36e25", package = "zed-async-tar", version = "0.5.0-zed" }
async-task = "4.7"
async-trait = "0.1"
async-tungstenite = "0.31.0"
@@ -900,5 +901,4 @@ ignored = [
"serde",
"component",
"documented",
"sea-orm-macros",
]

View File

@@ -139,7 +139,7 @@
"find": "buffer_search::Deploy",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
"ctrl->": "agent::AddSelectionToThread",
"ctrl->": "agent::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
@@ -243,7 +243,7 @@
"ctrl-shift-i": "agent::ToggleOptionsMenu",
"ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl->": "agent::AddSelectionToThread",
"ctrl->": "agent::QuoteSelection",
"ctrl-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
@@ -539,7 +539,7 @@
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
"ctrl-shift-space": "editor::ShowWordCompletions",
"ctrl-shift-space": "editor::ShowSignatureHelp",
"ctrl-.": "editor::ToggleCodeActions",
"ctrl-k r": "editor::RevealInFileManager",
"ctrl-k p": "editor::CopyPath",
@@ -799,7 +799,7 @@
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPreviousHunk",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-i": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints"
}
},
@@ -1094,7 +1094,7 @@
"paste": "terminal::Paste",
"shift-insert": "terminal::Paste",
"ctrl-shift-v": "terminal::Paste",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-i": "assistant::InlineAssist",
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
"alt-.": ["terminal::SendText", "\u001b."],

View File

@@ -142,7 +142,7 @@
"cmd-\"": "editor::ExpandAllDiffHunks",
"cmd-alt-g b": "git::Blame",
"cmd-alt-g m": "git::OpenModifiedFiles",
"cmd-i": "editor::ShowSignatureHelp",
"cmd-shift-space": "editor::ShowSignatureHelp",
"f9": "editor::ToggleBreakpoint",
"shift-f9": "editor::EditLogBreakpoint",
"ctrl-f12": "editor::GoToDeclaration",
@@ -163,7 +163,7 @@
"cmd-alt-f": "buffer_search::DeployReplace",
"cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
"cmd-e": ["buffer_search::Deploy", { "focus": false }],
"cmd->": "agent::AddSelectionToThread",
"cmd->": "agent::QuoteSelection",
"cmd-<": "assistant::InsertIntoEditor",
"cmd-alt-e": "editor::SelectEnclosingSymbol",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
@@ -282,7 +282,7 @@
"cmd-shift-i": "agent::ToggleOptionsMenu",
"cmd-alt-shift-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"cmd->": "agent::AddSelectionToThread",
"cmd->": "agent::QuoteSelection",
"cmd-alt-e": "agent::RemoveAllContext",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-ctrl-b": "agent::ToggleBurnMode",
@@ -864,7 +864,7 @@
"cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPreviousHunk",
"ctrl-enter": "assistant::InlineAssist",
"cmd-i": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints"
}
},
@@ -1168,7 +1168,7 @@
"cmd-a": "editor::SelectAll",
"cmd-k": "terminal::Clear",
"cmd-n": "workspace::NewTerminal",
"ctrl-enter": "assistant::InlineAssist",
"cmd-i": "assistant::InlineAssist",
"ctrl-_": null, // emacs undo
// Some nice conveniences
"cmd-backspace": ["terminal::SendText", "\u0015"], // ctrl-u: clear line

View File

@@ -134,7 +134,7 @@
"ctrl-k z": "editor::ToggleSoftWrap",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
"ctrl-shift-.": "agent::AddSelectionToThread",
"ctrl-shift-.": "agent::QuoteSelection",
"ctrl-shift-,": "assistant::InsertIntoEditor",
"shift-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
@@ -244,7 +244,7 @@
"ctrl-shift-i": "agent::ToggleOptionsMenu",
// "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl-shift-.": "agent::AddSelectionToThread",
"ctrl-shift-.": "agent::QuoteSelection",
"shift-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
@@ -548,7 +548,7 @@
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
"ctrl-shift-space": "editor::ShowWordCompletions",
"ctrl-shift-space": "editor::ShowSignatureHelp",
"ctrl-.": "editor::ToggleCodeActions",
"ctrl-k r": "editor::RevealInFileManager",
"ctrl-k p": "editor::CopyPath",
@@ -812,7 +812,7 @@
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPreviousHunk",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-i": "assistant::InlineAssist",
"ctrl-shift-;": "editor::ToggleInlayHints"
}
},
@@ -1120,7 +1120,7 @@
"shift-insert": "terminal::Paste",
"ctrl-v": "terminal::Paste",
"ctrl-shift-v": "terminal::Paste",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-i": "assistant::InlineAssist",
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
"alt-.": ["terminal::SendText", "\u001b."],

View File

@@ -17,8 +17,8 @@
"bindings": {
"ctrl-i": "agent::ToggleFocus",
"ctrl-shift-i": "agent::ToggleFocus",
"ctrl-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
"ctrl-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
"ctrl-shift-l": "agent::QuoteSelection", // In cursor uses "Ask" mode
"ctrl-l": "agent::QuoteSelection", // In cursor uses "Agent" mode
"ctrl-k": "assistant::InlineAssist",
"ctrl-shift-k": "assistant::InsertIntoEditor"
}

View File

@@ -8,23 +8,13 @@
"ctrl-g": "menu::Cancel"
}
},
{
// Workaround to avoid falling back to default bindings.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE: must be declared before the `Editor` override.
// NOTE: in macos the 'ctrl-x' 'ctrl-p' and 'ctrl-n' rebindings are not needed, since they default to 'cmd'.
"context": "Editor",
"bindings": {
"ctrl-g": null, // currently activates `go_to_line::Toggle` when there is nothing to cancel
"ctrl-x": null, // currently activates `editor::Cut` if no following key is pressed for 1 second
"ctrl-p": null, // currently activates `file_finder::Toggle` when the cursor is on the first character of the buffer
"ctrl-n": null // currently activates `workspace::NewFile` when the cursor is on the last character of the buffer
}
},
{
"context": "Editor",
"bindings": {
"alt-x": "command_palette::Toggle",
"ctrl-g": "editor::Cancel",
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
"alt-g g": "go_to_line::Toggle", // goto-line
"alt-g alt-g": "go_to_line::Toggle", // goto-line
"ctrl-space": "editor::SetMark", // set-mark
@@ -43,8 +33,8 @@
"alt-m": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], // back-to-indentation
"alt-left": "editor::MoveToPreviousWordStart", // left-word
"alt-right": "editor::MoveToNextWordEnd", // right-word
"alt-f": "editor::MoveToNextWordEnd", // forward-word
"alt-b": "editor::MoveToPreviousWordStart", // backward-word
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
"alt-l": "editor::ConvertToLowerCase", // downcase-word
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
@@ -108,7 +98,7 @@
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-m": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }],
"alt-f": "editor::SelectToNextWordEnd",
"alt-b": "editor::SelectToPreviousWordStart",
"alt-b": "editor::SelectToPreviousSubwordStart",
"alt-{": "editor::SelectToStartOfParagraph",
"alt-}": "editor::SelectToEndOfParagraph",
"ctrl-up": "editor::SelectToStartOfParagraph",
@@ -136,28 +126,15 @@
"ctrl-n": "editor::SignatureHelpNext"
}
},
// Example setting for using emacs-style tab
// (i.e. indent the current line / selection or perform symbol completion depending on context)
// {
// "context": "Editor && !showing_code_actions && !showing_completions",
// "bindings": {
// "tab": "editor::AutoIndent" // indent-for-tab-command
// }
// },
{
"context": "Workspace",
"bindings": {
"alt-x": "command_palette::Toggle", // execute-extended-command
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
// "ctrl-x ctrl-c": "workspace::CloseWindow" // in case you only want to exit the current Zed instance
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
"ctrl-x 5 2": "workspace::NewWindow", // make-frame-command
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
// "ctrl-x 1": "pane::JoinAll", // in case you prefer to delete the splits but keep the buffers open
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
@@ -168,19 +145,10 @@
}
},
{
// Workaround to enable using native emacs from the Zed terminal.
// Workaround to enable using emacs in the Zed terminal.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE:
// "terminal::SendKeystroke" only works for a single key stroke (e.g. ctrl-x),
// so override with null for compound sequences (e.g. ctrl-x ctrl-c).
"context": "Terminal",
"bindings": {
// If you want to perfect your emacs-in-zed setup, also consider the following.
// You may need to enable "option_as_meta" from the Zed settings for "alt-x" to work.
// "alt-x": ["terminal::SendKeystroke", "alt-x"],
// "ctrl-x": ["terminal::SendKeystroke", "ctrl-x"],
// "ctrl-n": ["terminal::SendKeystroke", "ctrl-n"],
// ...
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
"ctrl-x ctrl-f": null, // find-file
"ctrl-x ctrl-s": null, // save-buffer

View File

@@ -17,8 +17,8 @@
"bindings": {
"cmd-i": "agent::ToggleFocus",
"cmd-shift-i": "agent::ToggleFocus",
"cmd-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
"cmd-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
"cmd-shift-l": "agent::QuoteSelection", // In cursor uses "Ask" mode
"cmd-l": "agent::QuoteSelection", // In cursor uses "Agent" mode
"cmd-k": "assistant::InlineAssist",
"cmd-shift-k": "assistant::InsertIntoEditor"
}

View File

@@ -9,19 +9,13 @@
"ctrl-g": "menu::Cancel"
}
},
{
// Workaround to avoid falling back to default bindings.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE: must be declared before the `Editor` override.
"context": "Editor",
"bindings": {
"ctrl-g": null // currently activates `go_to_line::Toggle` when there is nothing to cancel
}
},
{
"context": "Editor",
"bindings": {
"alt-x": "command_palette::Toggle",
"ctrl-g": "editor::Cancel",
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
"alt-g g": "go_to_line::Toggle", // goto-line
"alt-g alt-g": "go_to_line::Toggle", // goto-line
"ctrl-space": "editor::SetMark", // set-mark
@@ -40,8 +34,8 @@
"alt-m": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], // back-to-indentation
"alt-left": "editor::MoveToPreviousWordStart", // left-word
"alt-right": "editor::MoveToNextWordEnd", // right-word
"alt-f": "editor::MoveToNextWordEnd", // forward-word
"alt-b": "editor::MoveToPreviousWordStart", // backward-word
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
"alt-l": "editor::ConvertToLowerCase", // downcase-word
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
@@ -105,7 +99,7 @@
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-m": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }],
"alt-f": "editor::SelectToNextWordEnd",
"alt-b": "editor::SelectToPreviousWordStart",
"alt-b": "editor::SelectToPreviousSubwordStart",
"alt-{": "editor::SelectToStartOfParagraph",
"alt-}": "editor::SelectToEndOfParagraph",
"ctrl-up": "editor::SelectToStartOfParagraph",
@@ -133,28 +127,15 @@
"ctrl-n": "editor::SignatureHelpNext"
}
},
// Example setting for using emacs-style tab
// (i.e. indent the current line / selection or perform symbol completion depending on context)
// {
// "context": "Editor && !showing_code_actions && !showing_completions",
// "bindings": {
// "tab": "editor::AutoIndent" // indent-for-tab-command
// }
// },
{
"context": "Workspace",
"bindings": {
"alt-x": "command_palette::Toggle", // execute-extended-command
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
// "ctrl-x ctrl-c": "workspace::CloseWindow" // in case you only want to exit the current Zed instance
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
"ctrl-x 5 2": "workspace::NewWindow", // make-frame-command
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
// "ctrl-x 1": "pane::JoinAll", // in case you prefer to delete the splits but keep the buffers open
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
@@ -165,19 +146,10 @@
}
},
{
// Workaround to enable using native emacs from the Zed terminal.
// Workaround to enable using emacs in the Zed terminal.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE:
// "terminal::SendKeystroke" only works for a single key stroke (e.g. ctrl-x),
// so override with null for compound sequences (e.g. ctrl-x ctrl-c).
"context": "Terminal",
"bindings": {
// If you want to perfect your emacs-in-zed setup, also consider the following.
// You may need to enable "option_as_meta" from the Zed settings for "alt-x" to work.
// "alt-x": ["terminal::SendKeystroke", "alt-x"],
// "ctrl-x": ["terminal::SendKeystroke", "ctrl-x"],
// "ctrl-n": ["terminal::SendKeystroke", "ctrl-n"],
// ...
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
"ctrl-x ctrl-f": null, // find-file
"ctrl-x ctrl-s": null, // save-buffer

View File

@@ -1820,7 +1820,6 @@
},
"SystemVerilog": {
"format_on_save": "off",
"language_servers": ["!slang", "..."],
"use_on_type_format": false
},
"Vue.js": {

View File

@@ -1,15 +1,10 @@
use agent_client_protocol as acp;
use anyhow::Result;
use futures::{FutureExt as _, future::Shared};
use gpui::{App, AppContext, AsyncApp, Context, Entity, Task};
use gpui::{App, AppContext, Context, Entity, Task};
use language::LanguageRegistry;
use markdown::Markdown;
use project::Project;
use settings::{Settings as _, SettingsLocation};
use std::{path::PathBuf, process::ExitStatus, sync::Arc, time::Instant};
use task::Shell;
use terminal::terminal_settings::TerminalSettings;
use util::get_default_system_shell_preferring_bash;
pub struct Terminal {
id: acp::TerminalId,
@@ -175,68 +170,3 @@ impl Terminal {
)
}
}
pub async fn create_terminal_entity(
command: String,
args: &[String],
env_vars: Vec<(String, String)>,
cwd: Option<PathBuf>,
project: &Entity<Project>,
cx: &mut AsyncApp,
) -> Result<Entity<terminal::Terminal>> {
let mut env = if let Some(dir) = &cwd {
project
.update(cx, |project, cx| {
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
project.directory_environment(&shell, dir.clone().into(), cx)
})?
.await
.unwrap_or_default()
} else {
Default::default()
};
// Disables paging for `git` and hopefully other commands
env.insert("PAGER".into(), "".into());
env.extend(env_vars);
// Use remote shell or default system shell, as appropriate
let shell = project
.update(cx, |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
.map(Shell::Program)
})?
.unwrap_or_else(|| Shell::Program(get_default_system_shell_preferring_bash()));
let is_windows = project
.read_with(cx, |project, cx| project.path_style(cx).is_windows())
.unwrap_or(cfg!(windows));
let (task_command, task_args) = task::ShellBuilder::new(&shell, is_windows)
.redirect_stdin_to_dev_null()
.build(Some(command.clone()), &args);
project
.update(cx, |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(task_command),
args: task_args,
cwd,
env,
..Default::default()
},
cx,
)
})?
.await
}

View File

@@ -10,8 +10,6 @@ path = "src/agent.rs"
[features]
test-support = ["db/test-support"]
eval = []
edit-agent-eval = []
e2e = []
[lints]

View File

@@ -31,7 +31,7 @@ use std::{
use util::path;
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_extract_handle_command_output() {
// Test how well agent generates multiple edit hunks.
//
@@ -108,7 +108,7 @@ fn eval_extract_handle_command_output() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_delete_run_git_blame() {
// Model | Pass rate
// ----------------------------|----------
@@ -171,7 +171,7 @@ fn eval_delete_run_git_blame() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_translate_doc_comments() {
// Model | Pass rate
// ============================================
@@ -234,7 +234,7 @@ fn eval_translate_doc_comments() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
// Model | Pass rate
// ============================================
@@ -360,7 +360,7 @@ fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_disable_cursor_blinking() {
// Model | Pass rate
// ============================================
@@ -446,7 +446,7 @@ fn eval_disable_cursor_blinking() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_from_pixels_constructor() {
// Results for 2025-06-13
//
@@ -656,7 +656,7 @@ fn eval_from_pixels_constructor() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_zode() {
// Model | Pass rate
// ============================================
@@ -763,7 +763,7 @@ fn eval_zode() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_add_overwrite_test() {
// Model | Pass rate
// ============================================
@@ -995,7 +995,7 @@ fn eval_add_overwrite_test() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_create_empty_file() {
// Check that Edit Agent can create a file without writing its
// thoughts into it. This issue is not specific to empty files, but
@@ -1490,20 +1490,9 @@ impl EditAgentTest {
&std::env::var("ZED_JUDGE_MODEL").unwrap_or("anthropic/claude-4-sonnet-latest".into()),
)
.unwrap();
let authenticate_provider_tasks = cx.update(|cx| {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry
.providers()
.iter()
.map(|p| p.authenticate(cx))
.collect::<Vec<_>>()
})
});
let (agent_model, judge_model) = cx
.update(|cx| {
cx.spawn(async move |cx| {
futures::future::join_all(authenticate_provider_tasks).await;
let agent_model = Self::load_model(&agent_model, cx).await;
let judge_model = Self::load_model(&judge_model, cx).await;
(agent_model.unwrap(), judge_model.unwrap())

View File

@@ -1995,7 +1995,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) {
locations: vec![],
raw_input: Some(json!({})),
raw_output: None,
meta: Some(json!({ "tool_name": "thinking" })),
meta: None,
}
);
let update = expect_tool_call_update_fields(&mut events).await;

View File

@@ -745,13 +745,7 @@ impl Thread {
let title = tool.initial_title(tool_use.input.clone(), cx);
let kind = tool.kind();
stream.send_tool_call(
&tool_use.id,
&tool_use.name,
title,
kind,
tool_use.input.clone(),
);
stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
let output = tool_result
.as_ref()
@@ -1050,18 +1044,14 @@ impl Thread {
Ok(())
}
pub fn latest_request_token_usage(&self) -> Option<language_model::TokenUsage> {
pub fn latest_token_usage(&self) -> Option<acp_thread::TokenUsage> {
let last_user_message = self.last_user_message()?;
let tokens = self.request_token_usage.get(&last_user_message.id)?;
Some(*tokens)
}
pub fn latest_token_usage(&self) -> Option<acp_thread::TokenUsage> {
let usage = self.latest_request_token_usage()?;
let model = self.model.clone()?;
Some(acp_thread::TokenUsage {
max_tokens: model.max_token_count_for_mode(self.completion_mode.into()),
used_tokens: usage.total_tokens(),
used_tokens: tokens.total_tokens(),
})
}
@@ -1104,14 +1094,6 @@ impl Thread {
self.run_turn(cx)
}
#[cfg(feature = "eval")]
pub fn proceed(
&mut self,
cx: &mut Context<Self>,
) -> Result<mpsc::UnboundedReceiver<Result<ThreadEvent>>> {
self.run_turn(cx)
}
fn run_turn(
&mut self,
cx: &mut Context<Self>,
@@ -1479,13 +1461,7 @@ impl Thread {
});
if push_new_tool_use {
event_stream.send_tool_call(
&tool_use.id,
&tool_use.name,
title,
kind,
tool_use.input.clone(),
);
event_stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
last_message
.content
.push(AgentMessageContent::ToolUse(tool_use.clone()));
@@ -1857,7 +1833,7 @@ impl Thread {
.tools
.iter()
.filter_map(|(tool_name, tool)| {
if tool.supports_provider(&model.provider_id())
if tool.supported_provider(&model.provider_id())
&& profile.is_tool_enabled(tool_name)
{
Some((truncate(tool_name), tool.clone()))
@@ -2133,7 +2109,7 @@ where
/// Some tools rely on a provider for the underlying billing or other reasons.
/// Allow the tool to check if they are compatible, or should be filtered out.
fn supports_provider(_provider: &LanguageModelProviderId) -> bool {
fn supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
true
}
@@ -2174,7 +2150,7 @@ pub trait AnyAgentTool {
fn kind(&self) -> acp::ToolKind;
fn initial_title(&self, input: serde_json::Value, _cx: &mut App) -> SharedString;
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value>;
fn supports_provider(&self, _provider: &LanguageModelProviderId) -> bool {
fn supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
true
}
fn run(
@@ -2219,8 +2195,8 @@ where
Ok(json)
}
fn supports_provider(&self, provider: &LanguageModelProviderId) -> bool {
T::supports_provider(provider)
fn supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
self.0.supported_provider(provider)
}
fn run(
@@ -2280,7 +2256,6 @@ impl ThreadEventStream {
fn send_tool_call(
&self,
id: &LanguageModelToolUseId,
tool_name: &str,
title: SharedString,
kind: acp::ToolKind,
input: serde_json::Value,
@@ -2288,7 +2263,6 @@ impl ThreadEventStream {
self.0
.unbounded_send(Ok(ThreadEvent::ToolCall(Self::initial_tool_call(
id,
tool_name,
title.to_string(),
kind,
input,
@@ -2298,15 +2272,12 @@ impl ThreadEventStream {
fn initial_tool_call(
id: &LanguageModelToolUseId,
tool_name: &str,
title: String,
kind: acp::ToolKind,
input: serde_json::Value,
) -> acp::ToolCall {
acp::ToolCall {
meta: Some(serde_json::json!({
"tool_name": tool_name
})),
meta: None,
id: acp::ToolCallId(id.to_string().into()),
title,
kind,

View File

@@ -40,19 +40,13 @@ pub use web_search_tool::*;
macro_rules! tools {
($($tool:ty),* $(,)?) => {
/// A list of all built-in tool names
pub fn supported_built_in_tool_names(provider: Option<language_model::LanguageModelProviderId>) -> impl Iterator<Item = String> {
pub fn built_in_tool_names() -> impl Iterator<Item = String> {
[
$(
(if let Some(provider) = provider.as_ref() {
<$tool>::supports_provider(provider)
} else {
true
})
.then(|| <$tool>::name().to_string()),
<$tool>::name().to_string(),
)*
]
.into_iter()
.flatten()
}
/// A list of all built-in tools

View File

@@ -57,7 +57,7 @@ impl AgentTool for WebSearchTool {
}
/// We currently only support Zed Cloud as a provider.
fn supports_provider(provider: &LanguageModelProviderId) -> bool {
fn supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
provider == &ZED_CLOUD_PROVIDER_ID
}

View File

@@ -9,7 +9,9 @@ use futures::io::BufReader;
use project::Project;
use project::agent_server_store::AgentServerCommand;
use serde::Deserialize;
use util::ResultExt as _;
use settings::{Settings as _, SettingsLocation};
use task::Shell;
use util::{ResultExt as _, get_default_system_shell_preferring_bash};
use std::path::PathBuf;
use std::{any::Any, cell::RefCell};
@@ -21,7 +23,7 @@ use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntit
use acp_thread::{AcpThread, AuthRequired, LoadError, TerminalProviderEvent};
use terminal::TerminalBuilder;
use terminal::terminal_settings::{AlternateScroll, CursorShape};
use terminal::terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
#[derive(Debug, Error)]
#[error("Unsupported version")]
@@ -814,18 +816,62 @@ impl acp::Client for ClientDelegate {
let thread = self.session_thread(&args.session_id)?;
let project = thread.read_with(&self.cx, |thread, _cx| thread.project().clone())?;
let terminal_entity = acp_thread::create_terminal_entity(
args.command.clone(),
&args.args,
args.env
.into_iter()
.map(|env| (env.name, env.value))
.collect(),
args.cwd.clone(),
&project,
&mut self.cx.clone(),
)
.await?;
let mut env = if let Some(dir) = &args.cwd {
project
.update(&mut self.cx.clone(), |project, cx| {
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
project.directory_environment(&shell, dir.clone().into(), cx)
})?
.await
.unwrap_or_default()
} else {
Default::default()
};
// Disables paging for `git` and hopefully other commands
env.insert("PAGER".into(), "".into());
for var in args.env {
env.insert(var.name, var.value);
}
// Use remote shell or default system shell, as appropriate
let shell = project
.update(&mut self.cx.clone(), |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
.map(Shell::Program)
})?
.unwrap_or_else(|| Shell::Program(get_default_system_shell_preferring_bash()));
let is_windows = project
.read_with(&self.cx, |project, cx| project.path_style(cx).is_windows())
.unwrap_or(cfg!(windows));
let (task_command, task_args) = task::ShellBuilder::new(&shell, is_windows)
.redirect_stdin_to_dev_null()
.build(Some(args.command.clone()), &args.args);
let terminal_entity = project
.update(&mut self.cx.clone(), |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(task_command),
args: task_args,
cwd: args.cwd.clone(),
env,
..Default::default()
},
cx,
)
})?
.await?;
// Register with renderer
let terminal_entity = thread.update(&mut self.cx.clone(), |thread, cx| {

View File

@@ -11,10 +11,10 @@ use assistant_slash_commands::codeblock_fence_for_path;
use collections::{HashMap, HashSet};
use editor::{
Addon, Anchor, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, Inlay,
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, InlayId,
MultiBuffer, ToOffset,
actions::Paste,
display_map::{Crease, CreaseId, FoldId},
display_map::{Crease, CreaseId, FoldId, Inlay},
};
use futures::{
FutureExt as _,
@@ -29,8 +29,7 @@ use language::{Buffer, Language, language_settings::InlayHintKind};
use language_model::LanguageModelImage;
use postage::stream::Stream as _;
use project::{
CompletionIntent, InlayHint, InlayHintLabel, InlayId, Project, ProjectItem, ProjectPath,
Worktree,
CompletionIntent, InlayHint, InlayHintLabel, Project, ProjectItem, ProjectPath, Worktree,
};
use prompt_store::{PromptId, PromptStore};
use rope::Point;
@@ -76,7 +75,7 @@ pub enum MessageEditorEvent {
impl EventEmitter<MessageEditorEvent> for MessageEditor {}
const COMMAND_HINT_INLAY_ID: InlayId = InlayId::Hint(0);
const COMMAND_HINT_INLAY_ID: u32 = 0;
impl MessageEditor {
pub fn new(
@@ -152,7 +151,7 @@ impl MessageEditor {
let has_new_hint = !new_hints.is_empty();
editor.splice_inlays(
if has_hint {
&[COMMAND_HINT_INLAY_ID]
&[InlayId::Hint(COMMAND_HINT_INLAY_ID)]
} else {
&[]
},

View File

@@ -194,7 +194,7 @@ impl Render for ModeSelector {
trigger_button,
Tooltip::element({
let focus_handle = self.focus_handle.clone();
move |_window, cx| {
move |window, cx| {
v_flex()
.gap_1()
.child(
@@ -205,9 +205,10 @@ impl Render for ModeSelector {
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.child(Label::new("Cycle Through Modes"))
.child(KeyBinding::for_action_in(
.children(KeyBinding::for_action_in(
&CycleModeSelector,
&focus_handle,
window,
cx,
)),
)
@@ -216,9 +217,10 @@ impl Render for ModeSelector {
.gap_2()
.justify_between()
.child(Label::new("Toggle Mode Menu"))
.child(KeyBinding::for_action_in(
.children(KeyBinding::for_action_in(
&ToggleProfileSelector,
&focus_handle,
window,
cx,
)),
)

View File

@@ -77,8 +77,14 @@ impl Render for AcpModelSelectorPopover {
.ml_0p5(),
)
.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
move |_window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
gpui::Corner::BottomRight,
cx,

View File

@@ -423,8 +423,8 @@ impl AcpThreadHistory {
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.tooltip(move |_window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
.tooltip(move |window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
})
.on_click(
cx.listener(move |this, _, _, cx| this.remove_thread(ix, cx)),
@@ -595,8 +595,8 @@ impl RenderOnce for AcpHistoryEntryElement {
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.tooltip(move |_window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
.tooltip(move |window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
})
.on_click({
let thread_view = self.thread_view.clone();

View File

@@ -1259,7 +1259,6 @@ impl AcpThreadView {
.await?;
this.update_in(cx, |this, window, cx| {
this.send_impl(message_editor, window, cx);
this.focus_handle(cx).focus(window);
})?;
anyhow::Ok(())
})
@@ -2158,6 +2157,7 @@ impl AcpThreadView {
options,
entry_ix,
tool_call.id.clone(),
window,
cx,
))
.into_any(),
@@ -2558,6 +2558,7 @@ impl AcpThreadView {
options: &[acp::PermissionOption],
entry_ix: usize,
tool_call_id: acp::ToolCallId,
window: &Window,
cx: &Context<Self>,
) -> Div {
let is_first = self.thread().is_some_and(|thread| {
@@ -2614,7 +2615,7 @@ impl AcpThreadView {
seen_kinds.push(option.kind);
this.key_binding(
KeyBinding::for_action_in(action, &self.focus_handle, cx)
KeyBinding::for_action_in(action, &self.focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(10.))),
)
})
@@ -2795,11 +2796,12 @@ impl AcpThreadView {
.icon_size(IconSize::Small)
.icon_color(Color::Error)
.label_size(LabelSize::Small)
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
"Stop This Command",
None,
"Also possible by placing your cursor inside the terminal and using regular terminal bindings.",
window,
cx,
)
})
@@ -3100,7 +3102,7 @@ impl AcpThreadView {
)
}
fn render_recent_history(&self, cx: &mut Context<Self>) -> AnyElement {
fn render_recent_history(&self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
let render_history = self
.agent
.clone()
@@ -3129,6 +3131,7 @@ impl AcpThreadView {
KeyBinding::for_action_in(
&OpenHistory,
&self.focus_handle(cx),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -3456,6 +3459,7 @@ impl AcpThreadView {
&changed_buffers,
self.edits_expanded,
pending_edits,
window,
cx,
))
.when(self.edits_expanded, |parent| {
@@ -3615,6 +3619,7 @@ impl AcpThreadView {
changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
expanded: bool,
pending_edits: bool,
window: &mut Window,
cx: &Context<Self>,
) -> Div {
const EDIT_NOT_READY_TOOLTIP_LABEL: &str = "Wait until file edits are complete.";
@@ -3690,11 +3695,12 @@ impl AcpThreadView {
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Review Changes",
&OpenAgentDiff,
&focus_handle,
window,
cx,
)
}
@@ -3712,8 +3718,13 @@ impl AcpThreadView {
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
})
.key_binding(
KeyBinding::for_action_in(&RejectAll, &focus_handle.clone(), cx)
.map(|kb| kb.size(rems_from_px(10.))),
KeyBinding::for_action_in(
&RejectAll,
&focus_handle.clone(),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(move |this, _, window, cx| {
this.reject_all(&RejectAll, window, cx);
@@ -3727,7 +3738,7 @@ impl AcpThreadView {
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
})
.key_binding(
KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
KeyBinding::for_action_in(&KeepAll, &focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(move |this, _, window, cx| {
@@ -3957,11 +3968,12 @@ impl AcpThreadView {
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip({
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
expand_tooltip,
&ExpandMessageEditor,
&focus_handle,
window,
cx,
)
}
@@ -4186,8 +4198,8 @@ impl AcpThreadView {
IconButton::new("stop-generation", IconName::Stop)
.icon_color(Color::Error)
.style(ButtonStyle::Tinted(ui::TintColor::Error))
.tooltip(move |_window, cx| {
Tooltip::for_action("Stop Generation", &editor::actions::Cancel, cx)
.tooltip(move |window, cx| {
Tooltip::for_action("Stop Generation", &editor::actions::Cancel, window, cx)
})
.on_click(cx.listener(|this, _event, _, cx| this.cancel_generation(cx)))
.into_any_element()
@@ -4209,7 +4221,7 @@ impl AcpThreadView {
this.icon_color(Color::Accent)
}
})
.tooltip(move |_window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, cx))
.tooltip(move |window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, window, cx))
.on_click(cx.listener(|this, _, window, cx| {
this.send(window, cx);
}))
@@ -4270,14 +4282,15 @@ impl AcpThreadView {
.icon_color(Color::Muted)
.toggle_state(following)
.selected_icon_color(Some(Color::Custom(cx.theme().players().agent().cursor)))
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
if following {
Tooltip::for_action(tooltip_label.clone(), &Follow, cx)
Tooltip::for_action(tooltip_label.clone(), &Follow, window, cx)
} else {
Tooltip::with_meta(
tooltip_label.clone(),
Some(&Follow),
"Track the agent's location as it reads and edits files.",
window,
cx,
)
}
@@ -5066,7 +5079,7 @@ impl AcpThreadView {
}
}
fn render_thread_error(&self, cx: &mut Context<Self>) -> Option<Div> {
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
let content = match self.thread_error.as_ref()? {
ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx),
ThreadError::Refusal => self.render_refusal_error(cx),
@@ -5077,7 +5090,9 @@ impl AcpThreadView {
ThreadError::ModelRequestLimitReached(plan) => {
self.render_model_request_limit_reached_error(*plan, cx)
}
ThreadError::ToolUseLimitReached => self.render_tool_use_limit_reached_error(cx)?,
ThreadError::ToolUseLimitReached => {
self.render_tool_use_limit_reached_error(window, cx)?
}
};
Some(div().child(content))
@@ -5268,7 +5283,11 @@ impl AcpThreadView {
.dismiss_action(self.dismiss_error_button(cx))
}
fn render_tool_use_limit_reached_error(&self, cx: &mut Context<Self>) -> Option<Callout> {
fn render_tool_use_limit_reached_error(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Callout> {
let thread = self.as_native_thread(cx)?;
let supports_burn_mode = thread
.read(cx)
@@ -5295,6 +5314,7 @@ impl AcpThreadView {
KeyBinding::for_action_in(
&ContinueWithBurnMode,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
@@ -5318,8 +5338,13 @@ impl AcpThreadView {
.layer(ElevationIndex::ModalSurface)
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(&ContinueThread, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(10.))),
KeyBinding::for_action_in(
&ContinueThread,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(|this, _, _window, cx| {
this.resume_chat(cx);
@@ -5495,7 +5520,7 @@ impl Render for AcpThreadView {
.into_any(),
ThreadState::Loading { .. } => v_flex()
.flex_1()
.child(self.render_recent_history(cx))
.child(self.render_recent_history(window, cx))
.into_any(),
ThreadState::LoadError(e) => v_flex()
.flex_1()
@@ -5526,7 +5551,8 @@ impl Render for AcpThreadView {
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
.into_any()
} else {
this.child(self.render_recent_history(cx)).into_any()
this.child(self.render_recent_history(window, cx))
.into_any()
}
}),
})
@@ -5550,7 +5576,7 @@ impl Render for AcpThreadView {
Vec::<Empty>::new()
}
})
.children(self.render_thread_error(cx))
.children(self.render_thread_error(window, cx))
.when_some(
self.new_server_version_available.as_ref().filter(|_| {
!has_messages || !matches!(self.thread_state, ThreadState::Ready { .. })

View File

@@ -431,7 +431,7 @@ impl Focusable for AddLlmProviderModal {
impl ModalView for AddLlmProviderModal {}
impl Render for AddLlmProviderModal {
fn render(&mut self, _window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
div()
@@ -484,6 +484,7 @@ impl Render for AddLlmProviderModal {
KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -498,6 +499,7 @@ impl Render for AddLlmProviderModal {
KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),

View File

@@ -566,7 +566,7 @@ impl ConfigureContextServerModal {
.into_any_element()
}
fn render_modal_footer(&self, cx: &mut Context<Self>) -> ModalFooter {
fn render_modal_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> ModalFooter {
let focus_handle = self.focus_handle(cx);
let is_connecting = matches!(self.state, State::Waiting);
@@ -584,11 +584,12 @@ impl ConfigureContextServerModal {
.icon_size(IconSize::Small)
.tooltip({
let repository_url = repository_url.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::with_meta(
"Open Repository",
None,
repository_url.clone(),
window,
cx,
)
}
@@ -615,7 +616,7 @@ impl ConfigureContextServerModal {
},
)
.key_binding(
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, cx)
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(
@@ -633,7 +634,7 @@ impl ConfigureContextServerModal {
)
.disabled(is_connecting)
.key_binding(
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(
@@ -708,7 +709,7 @@ impl Render for ConfigureContextServerModal {
State::Error(error) => Self::render_modal_error(error.clone()),
}),
)
.footer(self.render_modal_footer(cx)),
.footer(self.render_modal_footer(window, cx)),
)
}
}

View File

@@ -7,7 +7,6 @@ use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profil
use editor::Editor;
use fs::Fs;
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*};
use language_model::LanguageModel;
use settings::Settings as _;
use ui::{
KeyBinding, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry, prelude::*,
@@ -97,7 +96,6 @@ pub struct NewProfileMode {
pub struct ManageProfilesModal {
fs: Arc<dyn Fs>,
context_server_registry: Entity<ContextServerRegistry>,
active_model: Option<Arc<dyn LanguageModel>>,
focus_handle: FocusHandle,
mode: Mode,
}
@@ -111,14 +109,9 @@ impl ManageProfilesModal {
workspace.register_action(|workspace, action: &ManageProfiles, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
let fs = workspace.app_state().fs.clone();
let active_model = panel
.read(cx)
.active_native_agent_thread(cx)
.and_then(|thread| thread.read(cx).model().cloned());
let context_server_registry = panel.read(cx).context_server_registry().clone();
workspace.toggle_modal(window, cx, |window, cx| {
let mut this = Self::new(fs, active_model, context_server_registry, window, cx);
let mut this = Self::new(fs, context_server_registry, window, cx);
if let Some(profile_id) = action.customize_tools.clone() {
this.configure_builtin_tools(profile_id, window, cx);
@@ -132,7 +125,6 @@ impl ManageProfilesModal {
pub fn new(
fs: Arc<dyn Fs>,
active_model: Option<Arc<dyn LanguageModel>>,
context_server_registry: Entity<ContextServerRegistry>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -141,7 +133,6 @@ impl ManageProfilesModal {
Self {
fs,
active_model,
context_server_registry,
focus_handle,
mode: Mode::choose_profile(window, cx),
@@ -237,11 +228,9 @@ impl ManageProfilesModal {
let tool_picker = cx.new(|cx| {
let delegate = ToolPickerDelegate::builtin_tools(
//todo: This causes the web search tool to show up even it only works when using zed hosted models
agent::supported_built_in_tool_names(
self.active_model.as_ref().map(|model| model.provider_id()),
)
.map(|s| s.into())
.collect::<Vec<_>>(),
agent::built_in_tool_names()
.map(|s| s.into())
.collect::<Vec<_>>(),
self.fs.clone(),
profile_id.clone(),
profile,
@@ -352,9 +341,10 @@ impl ManageProfilesModal {
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(KeyBinding::for_action_in(
.children(KeyBinding::for_action_in(
&menu::Confirm,
&self.focus_handle,
window,
cx,
)),
)
@@ -648,13 +638,14 @@ impl ManageProfilesModal {
)
.child(Label::new("Go Back"))
.end_slot(
div().child(
div().children(
KeyBinding::for_action_in(
&menu::Cancel,
&self.focus_handle,
window,
cx,
)
.size(rems_from_px(12.)),
.map(|kb| kb.size(rems_from_px(12.))),
),
)
.on_click({
@@ -698,9 +689,14 @@ impl Render for ManageProfilesModal {
)
.child(Label::new("Go Back"))
.end_slot(
div().child(
KeyBinding::for_action_in(&menu::Cancel, &self.focus_handle, cx)
.size(rems_from_px(12.)),
div().children(
KeyBinding::for_action_in(
&menu::Cancel,
&self.focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
),
)
.on_click({

View File

@@ -671,7 +671,7 @@ impl Item for AgentDiffPane {
}
impl Render for AgentDiffPane {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_empty = self.multibuffer.read(cx).is_empty();
let focus_handle = &self.focus_handle;
@@ -704,6 +704,7 @@ impl Render for AgentDiffPane {
.key_binding(KeyBinding::for_action_in(
&ToggleFocus,
&focus_handle.clone(),
window,
cx,
))
.on_click(|_event, window, cx| {
@@ -720,7 +721,14 @@ fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControl
let thread = thread.clone();
Arc::new(
move |row, status, hunk_range, is_created_file, line_height, editor, _, cx| {
move |row,
status: &DiffHunkStatus,
hunk_range,
is_created_file,
line_height,
editor: &Entity<Editor>,
window: &mut Window,
cx: &mut App| {
{
render_diff_hunk_controls(
row,
@@ -730,6 +738,7 @@ fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControl
line_height,
&thread,
editor,
window,
cx,
)
}
@@ -745,6 +754,7 @@ fn render_diff_hunk_controls(
line_height: Pixels,
thread: &AgentDiffThread,
editor: &Entity<Editor>,
window: &mut Window,
cx: &mut App,
) -> AnyElement {
let editor = editor.clone();
@@ -767,8 +777,13 @@ fn render_diff_hunk_controls(
Button::new(("reject", row as u64), "Reject")
.disabled(is_created_file)
.key_binding(
KeyBinding::for_action_in(&Reject, &editor.read(cx).focus_handle(cx), cx)
.map(|kb| kb.size(rems_from_px(12.))),
KeyBinding::for_action_in(
&Reject,
&editor.read(cx).focus_handle(cx),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click({
let editor = editor.clone();
@@ -789,7 +804,7 @@ fn render_diff_hunk_controls(
}),
Button::new(("keep", row as u64), "Keep")
.key_binding(
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), cx)
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click({
@@ -820,8 +835,14 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |_window, cx| {
Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in(
"Next Hunk",
&GoToHunk,
&focus_handle,
window,
cx,
)
}
})
.on_click({
@@ -850,11 +871,12 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Previous Hunk",
&GoToPreviousHunk,
&focus_handle,
window,
cx,
)
}
@@ -1019,7 +1041,7 @@ impl ToolbarItemView for AgentDiffToolbar {
}
impl Render for AgentDiffToolbar {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let spinner_icon = div()
.px_0p5()
.id("generating")
@@ -1094,6 +1116,7 @@ impl Render for AgentDiffToolbar {
KeyBinding::for_action_in(
&RejectAll,
&editor_focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
@@ -1108,6 +1131,7 @@ impl Render for AgentDiffToolbar {
KeyBinding::for_action_in(
&KeepAll,
&editor_focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
@@ -1184,8 +1208,13 @@ impl Render for AgentDiffToolbar {
.child(
Button::new("reject-all", "Reject All")
.key_binding({
KeyBinding::for_action_in(&RejectAll, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.)))
KeyBinding::for_action_in(
&RejectAll,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
})
.on_click(cx.listener(|this, _, window, cx| {
this.dispatch_action(&RejectAll, window, cx)
@@ -1194,8 +1223,13 @@ impl Render for AgentDiffToolbar {
.child(
Button::new("keep-all", "Keep All")
.key_binding({
KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.)))
KeyBinding::for_action_in(
&KeepAll,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
})
.on_click(cx.listener(|this, _, window, cx| {
this.dispatch_action(&KeepAll, window, cx)

View File

@@ -96,8 +96,14 @@ impl Render for AgentModelSelector {
.color(color)
.size(IconSize::XSmall),
),
move |_window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
gpui::Corner::TopRight,
cx,

View File

@@ -1595,11 +1595,12 @@ impl AgentPanel {
.icon_size(IconSize::Small),
{
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Toggle Agent Menu",
&ToggleOptionsMenu,
&focus_handle,
window,
cx,
)
}
@@ -1663,7 +1664,7 @@ impl AgentPanel {
.separator();
menu = menu
.action("Rules", Box::new(OpenRulesLibrary::default()))
.action("Rules", Box::new(OpenRulesLibrary::default()))
.action("Settings", Box::new(OpenSettings))
.separator()
.action(full_screen_label, Box::new(ToggleZoom));
@@ -1690,11 +1691,12 @@ impl AgentPanel {
.trigger_with_tooltip(
IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small),
{
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Toggle Recent Threads",
&ToggleNavigationMenu,
&focus_handle,
window,
cx,
)
}
@@ -1728,8 +1730,8 @@ impl AgentPanel {
this.go_back(&workspace::GoBack, window, cx);
}))
.tooltip({
move |_window, cx| {
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
}
})
}
@@ -1750,11 +1752,12 @@ impl AgentPanel {
IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
{
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"New…",
&ToggleNewThreadMenu,
&focus_handle,
window,
cx,
)
}
@@ -2000,8 +2003,14 @@ impl AgentPanel {
.when_some(self.selected_agent.icon(), |this, icon| {
this.px(DynamicSpacing::Base02.rems(cx))
.child(Icon::new(icon).color(Color::Muted))
.tooltip(move |_window, cx| {
Tooltip::with_meta(selected_agent_label.clone(), None, "Selected Agent", cx)
.tooltip(move |window, cx| {
Tooltip::with_meta(
selected_agent_label.clone(),
None,
"Selected Agent",
window,
cx,
)
})
})
.into_any_element();
@@ -2177,6 +2186,7 @@ impl AgentPanel {
border_bottom: bool,
configuration_error: &ConfigurationError,
focus_handle: &FocusHandle,
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
let zed_provider_configured = AgentSettings::get_global(cx)
@@ -2225,7 +2235,7 @@ impl AgentPanel {
.style(ButtonStyle::Tinted(ui::TintColor::Warning))
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(&OpenSettings, focus_handle, cx)
KeyBinding::for_action_in(&OpenSettings, focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(|_event, window, cx| {
@@ -2443,6 +2453,7 @@ impl Render for AgentPanel {
true,
err,
&self.focus_handle(cx),
window,
cx,
))
} else {

View File

@@ -130,6 +130,12 @@ actions!(
]
);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Action)]
#[action(namespace = agent)]
#[action(deprecated_aliases = ["assistant::QuoteSelection"])]
/// Quotes the current selection in the agent panel's message editor.
pub struct QuoteSelection;
/// Creates a new conversation thread, optionally based on an existing thread.
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
#[action(namespace = agent)]

View File

@@ -483,11 +483,12 @@ impl Render for ContextStrip {
.style(ui::ButtonStyle::Filled),
{
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
window,
cx,
)
}
@@ -557,11 +558,12 @@ impl Render for ContextStrip {
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Remove All Context",
&RemoveAllContext,
&focus_handle,
window,
cx,
)
}

View File

@@ -468,11 +468,12 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
mode.tooltip_interrupt(),
Some(&menu::Cancel),
"Changes won't be discarded",
window,
cx,
)
})
@@ -486,11 +487,12 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
mode.tooltip_restart(),
Some(&menu::Confirm),
"Changes will be discarded",
window,
cx,
)
})
@@ -503,8 +505,8 @@ impl<T: 'static> PromptEditor<T> {
let accept = IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |_window, cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, cx)
.tooltip(move |window, cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, window, cx)
})
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
@@ -517,10 +519,11 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("confirm", IconName::PlayFilled)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|_window, cx| {
.tooltip(|window, cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
window,
cx,
)
})
@@ -612,12 +615,13 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |_window, cx| {
move |window, cx| {
cx.new(|cx| {
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
KeyBinding::for_action_in(
&CyclePreviousInlineAssist,
&focus_handle,
window,
cx,
),
);
@@ -653,12 +657,13 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |_window, cx| {
move |window, cx| {
cx.new(|cx| {
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
KeyBinding::for_action_in(
&CycleNextInlineAssist,
&focus_handle,
window,
cx,
),
);

View File

@@ -162,11 +162,12 @@ impl Render for ProfileSelector {
PickerPopoverMenu::new(
picker,
trigger_button,
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Toggle Profile Menu",
&ToggleProfileSelector,
&focus_handle,
window,
cx,
)
},

View File

@@ -1,4 +1,5 @@
use crate::{
QuoteSelection,
language_model_selector::{LanguageModelSelector, language_model_selector},
ui::BurnModeTooltip,
};
@@ -71,7 +72,7 @@ use workspace::{
pane,
searchable::{SearchEvent, SearchableItem},
};
use zed_actions::agent::{AddSelectionToThread, ToggleModelSelector};
use zed_actions::agent::ToggleModelSelector;
use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};
use assistant_context::{
@@ -1084,11 +1085,12 @@ impl TextThreadEditor {
.child(label)
.children(spinner),
)
.tooltip(|_window, cx| {
.tooltip(|window, cx| {
Tooltip::with_meta(
"Toggle message role",
None,
"Available roles: You (User), Agent, System",
window,
cx,
)
})
@@ -1124,11 +1126,12 @@ impl TextThreadEditor {
.size(IconSize::XSmall)
.color(Color::Hint),
)
.tooltip(|_window, cx| {
.tooltip(|window, cx| {
Tooltip::with_meta(
"Context Cached",
None,
"Large messages cached to optimize performance",
window,
cx,
)
})
@@ -1447,7 +1450,7 @@ impl TextThreadEditor {
pub fn quote_selection(
workspace: &mut Workspace,
_: &AddSelectionToThread,
_: &QuoteSelection,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
@@ -1944,7 +1947,7 @@ impl TextThreadEditor {
})
.layer(ElevationIndex::ModalSurface)
.key_binding(
KeyBinding::for_action_in(&Assist, &focus_handle, cx)
KeyBinding::for_action_in(&Assist, &focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(move |_event, window, cx| {
@@ -1979,8 +1982,14 @@ impl TextThreadEditor {
.icon_color(Color::Muted)
.selected_icon_color(Color::Accent)
.selected_style(ButtonStyle::Filled),
move |_window, cx| {
Tooltip::with_meta("Add Context", None, "Type / to insert via keyboard", cx)
move |window, cx| {
Tooltip::with_meta(
"Add Context",
None,
"Type / to insert via keyboard",
window,
cx,
)
},
)
}
@@ -2069,8 +2078,14 @@ impl TextThreadEditor {
)
.child(Icon::new(icon).color(color).size(IconSize::XSmall)),
),
move |_window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
gpui::Corner::BottomRight,
cx,

View File

@@ -18,7 +18,7 @@ impl BurnModeTooltip {
}
impl Render for BurnModeTooltip {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (icon, color) = if self.selected {
(IconName::ZedBurnModeOn, Color::Error)
} else {
@@ -45,7 +45,8 @@ impl Render for BurnModeTooltip {
.child(Label::new("Burn Mode"))
.when(self.selected, |title| title.child(turned_on));
let keybinding = KeyBinding::for_action(&ToggleBurnMode, cx).size(rems_from_px(12.));
let keybinding = KeyBinding::for_action(&ToggleBurnMode, window, cx)
.map(|kb| kb.size(rems_from_px(12.)));
tooltip_container(cx, |this, _| {
this
@@ -53,7 +54,7 @@ impl Render for BurnModeTooltip {
h_flex()
.justify_between()
.child(title)
.child(keybinding)
.children(keybinding)
)
.child(
div()

View File

@@ -244,8 +244,8 @@ impl RenderOnce for ContextPill {
.truncate(),
),
)
.tooltip(|_window, cx| {
Tooltip::with_meta("Suggested Context", None, "Click to add it", cx)
.tooltip(|window, cx| {
Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
})
.when_some(on_click.as_ref(), |element, on_click| {
let on_click = on_click.clone();

View File

@@ -119,19 +119,21 @@ impl Render for Breadcrumbs {
}
}
})
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
if let Some(editor) = editor.upgrade() {
let focus_handle = editor.read(cx).focus_handle(cx);
Tooltip::for_action_in(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
&focus_handle,
window,
cx,
)
} else {
Tooltip::for_action(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
window,
cx,
)
}

View File

@@ -47,9 +47,7 @@ reqwest = { version = "0.11", features = ["json"] }
reqwest_client.workspace = true
rpc.workspace = true
scrypt = "0.11"
# sea-orm and sea-orm-macros versions must match exactly.
sea-orm = { version = "=1.1.10", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
sea-orm-macros = "=1.1.10"
semantic_version.workspace = true
semver.workspace = true
serde.workspace = true

View File

@@ -343,6 +343,7 @@ impl Server {
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferForSymbol>)
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferById>)
.add_request_handler(forward_read_only_project_request::<proto::SynchronizeBuffers>)
.add_request_handler(forward_read_only_project_request::<proto::InlayHints>)
.add_request_handler(forward_read_only_project_request::<proto::ResolveInlayHint>)
.add_request_handler(forward_read_only_project_request::<proto::GetColorPresentation>)
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)

View File

@@ -1849,40 +1849,10 @@ async fn test_mutual_editor_inlay_hint_cache_update(
..lsp::ServerCapabilities::default()
};
client_a.language_registry().add(rust_lang());
// Set up the language server to return an additional inlay hint on each request.
let edits_made = Arc::new(AtomicUsize::new(0));
let closure_edits_made = Arc::clone(&edits_made);
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: capabilities.clone(),
initializer: Some(Box::new(move |fake_language_server| {
let closure_edits_made = closure_edits_made.clone();
fake_language_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
move |params, _| {
let edits_made_2 = Arc::clone(&closure_edits_made);
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
);
let edits_made =
AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
Ok(Some(vec![lsp::InlayHint {
position: lsp::Position::new(0, edits_made as u32),
label: lsp::InlayHintLabel::String(edits_made.to_string()),
kind: None,
text_edits: None,
tooltip: None,
padding_left: None,
padding_right: None,
data: None,
}]))
}
},
);
})),
..FakeLspAdapter::default()
},
);
@@ -1924,20 +1894,61 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.unwrap();
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
executor.start_waiting();
// The host opens a rust file.
let file_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
});
let _buffer_a = project_a
.update(cx_a, |project, cx| {
project.open_local_buffer(path!("/a/main.rs"), cx)
})
.await
.unwrap();
let editor_a = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let fake_language_server = fake_language_servers.next().await.unwrap();
let editor_a = file_a.await.unwrap().downcast::<Editor>().unwrap();
// Set up the language server to return an additional inlay hint on each request.
let edits_made = Arc::new(AtomicUsize::new(0));
let closure_edits_made = Arc::clone(&edits_made);
fake_language_server
.set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
let edits_made_2 = Arc::clone(&closure_edits_made);
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
);
let edits_made = AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
Ok(Some(vec![lsp::InlayHint {
position: lsp::Position::new(0, edits_made as u32),
label: lsp::InlayHintLabel::String(edits_made.to_string()),
kind: None,
text_edits: None,
tooltip: None,
padding_left: None,
padding_right: None,
data: None,
}]))
}
})
.next()
.await
.unwrap();
executor.run_until_parked();
let initial_edit = edits_made.load(atomic::Ordering::Acquire);
editor_a.update(cx_a, |editor, cx| {
editor_a.update(cx_a, |editor, _| {
assert_eq!(
vec![initial_edit.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
"Host should get its first hints when opens an editor"
);
});
@@ -1952,10 +1963,10 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.unwrap();
executor.run_until_parked();
editor_b.update(cx_b, |editor, cx| {
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec![initial_edit.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
"Client should get its first hints when opens an editor"
);
});
@@ -1970,16 +1981,16 @@ async fn test_mutual_editor_inlay_hint_cache_update(
cx_b.focus(&editor_b);
executor.run_until_parked();
editor_a.update(cx_a, |editor, cx| {
editor_a.update(cx_a, |editor, _| {
assert_eq!(
vec![after_client_edit.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
);
});
editor_b.update(cx_b, |editor, cx| {
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec![after_client_edit.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
);
});
@@ -1993,16 +2004,16 @@ async fn test_mutual_editor_inlay_hint_cache_update(
cx_a.focus(&editor_a);
executor.run_until_parked();
editor_a.update(cx_a, |editor, cx| {
editor_a.update(cx_a, |editor, _| {
assert_eq!(
vec![after_host_edit.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
);
});
editor_b.update(cx_b, |editor, cx| {
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec![after_host_edit.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
);
});
@@ -2014,22 +2025,26 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.expect("inlay refresh request failed");
executor.run_until_parked();
editor_a.update(cx_a, |editor, cx| {
editor_a.update(cx_a, |editor, _| {
assert_eq!(
vec![after_special_edit_for_refresh.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
"Host should react to /refresh LSP request"
);
});
editor_b.update(cx_b, |editor, cx| {
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec![after_special_edit_for_refresh.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
"Guest should get a /refresh LSP request propagated by host"
);
});
}
// This test started hanging on seed 2 after the theme settings
// PR. The hypothesis is that it's been buggy for a while, but got lucky
// on seeds.
#[ignore]
#[gpui::test(iterations = 10)]
async fn test_inlay_hint_refresh_is_forwarded(
cx_a: &mut TestAppContext,
@@ -2191,18 +2206,18 @@ async fn test_inlay_hint_refresh_is_forwarded(
executor.finish_waiting();
executor.run_until_parked();
editor_a.update(cx_a, |editor, cx| {
editor_a.update(cx_a, |editor, _| {
assert!(
extract_hint_labels(editor, cx).is_empty(),
extract_hint_labels(editor).is_empty(),
"Host should get no hints due to them turned off"
);
});
executor.run_until_parked();
editor_b.update(cx_b, |editor, cx| {
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec!["initial hint".to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
"Client should get its first hints when opens an editor"
);
});
@@ -2214,18 +2229,18 @@ async fn test_inlay_hint_refresh_is_forwarded(
.into_response()
.expect("inlay refresh request failed");
executor.run_until_parked();
editor_a.update(cx_a, |editor, cx| {
editor_a.update(cx_a, |editor, _| {
assert!(
extract_hint_labels(editor, cx).is_empty(),
extract_hint_labels(editor).is_empty(),
"Host should get no hints due to them turned off, even after the /refresh"
);
});
executor.run_until_parked();
editor_b.update(cx_b, |editor, cx| {
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec!["other hint".to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
"Guest should get a /refresh LSP request propagated by host despite host hints are off"
);
});
@@ -4202,35 +4217,15 @@ fn tab_undo_assert(
cx_b.assert_editor_state(expected_initial);
}
fn extract_hint_labels(editor: &Editor, cx: &mut App) -> Vec<String> {
let lsp_store = editor.project().unwrap().read(cx).lsp_store();
let mut all_cached_labels = Vec::new();
let mut all_fetched_hints = Vec::new();
for buffer in editor.buffer().read(cx).all_buffers() {
lsp_store.update(cx, |lsp_store, cx| {
let hints = &lsp_store.latest_lsp_data(&buffer, cx).inlay_hints();
all_cached_labels.extend(hints.all_cached_hints().into_iter().map(|hint| {
let mut label = hint.text().to_string();
if hint.padding_left {
label.insert(0, ' ');
}
if hint.padding_right {
label.push_str(" ");
}
label
}));
all_fetched_hints.extend(hints.all_fetched_hints());
});
fn extract_hint_labels(editor: &Editor) -> Vec<String> {
let mut labels = Vec::new();
for hint in editor.inlay_hint_cache().hints() {
match hint.label {
project::InlayHintLabel::String(s) => labels.push(s),
_ => unreachable!(),
}
}
assert!(
all_fetched_hints.is_empty(),
"Did not expect background hints fetch tasks, but got {} of them",
all_fetched_hints.len()
);
all_cached_labels
labels
}
#[track_caller]

View File

@@ -738,17 +738,19 @@ impl Render for NotificationToast {
.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
.child(
IconButton::new(close_id, close_icon)
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
if suppress {
Tooltip::for_action(
"Suppress.\nClose with click.",
&workspace::SuppressNotification,
window,
cx,
)
} else {
Tooltip::for_action(
"Close.\nSuppress with shift-click",
&menu::Cancel,
window,
cx,
)
}

View File

@@ -443,7 +443,7 @@ impl PickerDelegate for CommandPaletteDelegate {
&self,
ix: usize,
selected: bool,
_: &mut Window,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let matching_command = self.matches.get(ix)?;
@@ -462,9 +462,10 @@ impl PickerDelegate for CommandPaletteDelegate {
command.name.clone(),
matching_command.positions.clone(),
))
.child(KeyBinding::for_action_in(
.children(KeyBinding::for_action_in(
&*command.action,
&self.previous_focus_handle,
window,
cx,
)),
),

View File

@@ -33,31 +33,17 @@ const CRASH_HANDLER_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
static PANIC_THREAD_ID: AtomicU32 = AtomicU32::new(0);
pub async fn init(crash_init: InitCrashHandler) {
let gen_var = match env::var("ZED_GENERATE_MINIDUMPS") {
Ok(v) => {
if v == "false" || v == "0" {
Some(false)
} else {
Some(true)
}
}
Err(_) => None,
};
match (gen_var, *RELEASE_CHANNEL) {
(Some(false), _) | (None, ReleaseChannel::Dev) => {
let old_hook = panic::take_hook();
panic::set_hook(Box::new(move |info| {
unsafe { env::set_var("RUST_BACKTRACE", "1") };
old_hook(info);
// prevent the macOS crash dialog from popping up
std::process::exit(1);
}));
return;
}
(Some(true), _) | (None, _) => {
panic::set_hook(Box::new(panic_hook));
}
if *RELEASE_CHANNEL == ReleaseChannel::Dev && env::var("ZED_GENERATE_MINIDUMPS").is_err() {
let old_hook = panic::take_hook();
panic::set_hook(Box::new(move |info| {
unsafe { env::set_var("RUST_BACKTRACE", "1") };
old_hook(info);
// prevent the macOS crash dialog from popping up
std::process::exit(1);
}));
return;
} else {
panic::set_hook(Box::new(panic_hook));
}
let exe = env::current_exe().expect("unable to find ourselves");

View File

@@ -616,11 +616,12 @@ impl DebugPanel {
})
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Start Debug Session",
&crate::Start,
&focus_handle,
window,
cx,
)
}
@@ -693,11 +694,12 @@ impl DebugPanel {
))
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Pause Program",
&Pause,
&focus_handle,
window,
cx,
)
}
@@ -717,11 +719,12 @@ impl DebugPanel {
.disabled(thread_status != ThreadStatus::Stopped)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Continue Program",
&Continue,
&focus_handle,
window,
cx,
)
}
@@ -741,11 +744,12 @@ impl DebugPanel {
.disabled(thread_status != ThreadStatus::Stopped)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Step Over",
&StepOver,
&focus_handle,
window,
cx,
)
}
@@ -766,11 +770,12 @@ impl DebugPanel {
.disabled(thread_status != ThreadStatus::Stopped)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Step In",
&StepInto,
&focus_handle,
window,
cx,
)
}
@@ -788,11 +793,12 @@ impl DebugPanel {
.disabled(thread_status != ThreadStatus::Stopped)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Step Out",
&StepOut,
&focus_handle,
window,
cx,
)
}
@@ -810,11 +816,12 @@ impl DebugPanel {
))
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Rerun Session",
&RerunSession,
&focus_handle,
window,
cx,
)
}
@@ -854,11 +861,12 @@ impl DebugPanel {
} else {
"Terminate All Threads"
};
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
label,
&Stop,
&focus_handle,
window,
cx,
)
}
@@ -885,11 +893,12 @@ impl DebugPanel {
))
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Detach",
&Detach,
&focus_handle,
window,
cx,
)
}

View File

@@ -745,15 +745,22 @@ impl Render for NewProcessModal {
== 0;
let secondary_action = menu::SecondaryConfirm.boxed_clone();
container
.child(div().child({
Button::new("edit-attach-task", "Edit in debug.json")
.label_size(LabelSize::Small)
.key_binding(KeyBinding::for_action(&*secondary_action, cx))
.on_click(move |_, window, cx| {
window.dispatch_action(secondary_action.boxed_clone(), cx)
})
.disabled(disabled)
}))
.child(div().children(
KeyBinding::for_action(&*secondary_action, window, cx).map(
|keybind| {
Button::new("edit-attach-task", "Edit in debug.json")
.label_size(LabelSize::Small)
.key_binding(keybind)
.on_click(move |_, window, cx| {
window.dispatch_action(
secondary_action.boxed_clone(),
cx,
)
})
.disabled(disabled)
},
),
))
.child(
h_flex()
.child(div().child(self.adapter_drop_down_menu(window, cx))),
@@ -1440,48 +1447,56 @@ impl PickerDelegate for DebugDelegate {
.justify_between()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.child({
.children({
let action = menu::SecondaryConfirm.boxed_clone();
if self.matches.is_empty() {
Button::new("edit-debug-json", "Edit debug.json")
.label_size(LabelSize::Small)
.on_click(cx.listener(|_picker, _, window, cx| {
window.dispatch_action(
zed_actions::OpenProjectDebugTasks.boxed_clone(),
cx,
);
cx.emit(DismissEvent);
}))
Some(
Button::new("edit-debug-json", "Edit debug.json")
.label_size(LabelSize::Small)
.on_click(cx.listener(|_picker, _, window, cx| {
window.dispatch_action(
zed_actions::OpenProjectDebugTasks.boxed_clone(),
cx,
);
cx.emit(DismissEvent);
})),
)
} else {
Button::new("edit-debug-task", "Edit in debug.json")
.label_size(LabelSize::Small)
.key_binding(KeyBinding::for_action(&*action, cx))
.on_click(move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx)
})
KeyBinding::for_action(&*action, window, cx).map(|keybind| {
Button::new("edit-debug-task", "Edit in debug.json")
.label_size(LabelSize::Small)
.key_binding(keybind)
.on_click(move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx)
})
})
}
})
.map(|this| {
if (current_modifiers.alt || self.matches.is_empty()) && !self.prompt.is_empty() {
let action = picker::ConfirmInput { secondary: false }.boxed_clone();
this.child({
this.children(KeyBinding::for_action(&*action, window, cx).map(|keybind| {
Button::new("launch-custom", "Launch Custom")
.key_binding(KeyBinding::for_action(&*action, cx))
.key_binding(keybind)
.on_click(move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx)
})
})
}))
} else {
this.child({
let is_recent_selected = self.divider_index >= Some(self.selected_index);
let run_entry_label = if is_recent_selected { "Rerun" } else { "Spawn" };
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)
.key_binding(KeyBinding::for_action(&menu::Confirm, cx))
.on_click(|_, window, cx| {
window.dispatch_action(menu::Confirm.boxed_clone(), cx);
})
})
Button::new("spawn", run_entry_label)
.key_binding(keybind)
.on_click(|_, window, cx| {
window.dispatch_action(menu::Confirm.boxed_clone(), cx);
})
},
))
}
});
Some(footer.into_any_element())

View File

@@ -566,13 +566,14 @@ pub(crate) fn new_debugger_pane(
}))
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
let zoomed_text =
if zoomed { "Minimize" } else { "Expand" };
Tooltip::for_action_in(
zoomed_text,
&ToggleExpandItem,
&focus_handle,
window,
cx,
)
}

View File

@@ -607,12 +607,13 @@ impl BreakpointList {
.when_some(toggle_label, |this, (label, meta)| {
this.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::with_meta_in(
label,
Some(&ToggleEnableBreakpoint),
meta,
&focus_handle,
window,
cx,
)
}
@@ -633,12 +634,13 @@ impl BreakpointList {
.when_some(remove_breakpoint_tooltip, |this, tooltip| {
this.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::with_meta_in(
"Remove Breakpoint",
Some(&UnsetBreakpoint),
tooltip,
&focus_handle,
window,
cx,
)
}
@@ -817,7 +819,7 @@ impl LineBreakpoint {
)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
if is_enabled {
"Disable Breakpoint"
@@ -826,6 +828,7 @@ impl LineBreakpoint {
},
&ToggleEnableBreakpoint,
&focus_handle,
window,
cx,
)
}
@@ -977,7 +980,7 @@ impl DataBreakpoint {
)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
if is_enabled {
"Disable Data Breakpoint"
@@ -986,6 +989,7 @@ impl DataBreakpoint {
},
&ToggleEnableBreakpoint,
&focus_handle,
window,
cx,
)
}
@@ -1081,7 +1085,7 @@ impl ExceptionBreakpoint {
)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
if is_enabled {
"Disable Exception Breakpoint"
@@ -1090,6 +1094,7 @@ impl ExceptionBreakpoint {
},
&ToggleEnableBreakpoint,
&focus_handle,
window,
cx,
)
}
@@ -1397,11 +1402,12 @@ impl RenderOnce for BreakpointOptionsStrip {
.disabled(!supports_logs)
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::Log))
.on_click(self.on_click_callback(ActiveBreakpointStripMode::Log))
.tooltip(|_window, cx| {
.tooltip(|window, cx| {
Tooltip::with_meta(
"Set Log Message",
None,
"Set log message to display (instead of stopping) when a breakpoint is hit.",
window,
cx,
)
}),
@@ -1432,11 +1438,12 @@ impl RenderOnce for BreakpointOptionsStrip {
.disabled(!supports_condition)
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::Condition))
.on_click(self.on_click_callback(ActiveBreakpointStripMode::Condition))
.tooltip(|_window, cx| {
.tooltip(|window, cx| {
Tooltip::with_meta(
"Set Condition",
None,
"Set condition to evaluate when a breakpoint is hit. Program execution will stop only when the condition is met.",
window,
cx,
)
}),
@@ -1467,11 +1474,12 @@ impl RenderOnce for BreakpointOptionsStrip {
.disabled(!supports_hit_condition)
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::HitCondition))
.on_click(self.on_click_callback(ActiveBreakpointStripMode::HitCondition))
.tooltip(|_window, cx| {
.tooltip(|window, cx| {
Tooltip::with_meta(
"Set Hit Condition",
None,
"Set expression that controls how many hits of the breakpoint are ignored.",
window,
cx,
)
}),

View File

@@ -484,11 +484,12 @@ impl Render for Console {
.tooltip({
let query_focus_handle = query_focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Evaluate",
&Confirm,
&query_focus_handle,
window,
cx,
)
}

View File

@@ -872,8 +872,8 @@ impl StackFrameList {
"filter-by-visible-worktree-stack-frame-list",
IconName::ListFilter,
)
.tooltip(move |_window, cx| {
Tooltip::for_action(tooltip_title, &ToggleUserFrames, cx)
.tooltip(move |window, cx| {
Tooltip::for_action(tooltip_title, &ToggleUserFrames, window, cx)
})
.toggle_state(self.list_filter == StackFrameFilter::OnlyUserFrames)
.icon_size(IconSize::Small)

View File

@@ -1306,8 +1306,14 @@ impl VariableList {
.ok();
}
})
.tooltip(move |_window, cx| {
Tooltip::for_action_in("Remove Watch", &RemoveWatch, &focus_handle, cx)
.tooltip(move |window, cx| {
Tooltip::for_action_in(
"Remove Watch",
&RemoveWatch,
&focus_handle,
window,
cx,
)
})
.icon_size(ui::IconSize::Indicator),
),

View File

@@ -1,9 +1,9 @@
use super::*;
use collections::{HashMap, HashSet};
use editor::{
DisplayPoint, EditorSettings, Inlay,
DisplayPoint, EditorSettings,
actions::{GoToDiagnostic, GoToPreviousDiagnostic, Hover, MoveToBeginning},
display_map::DisplayRow,
display_map::{DisplayRow, Inlay},
test::{
editor_content_with_blocks, editor_lsp_test_context::EditorLspTestContext,
editor_test_context::EditorTestContext,

View File

@@ -67,10 +67,11 @@ impl Render for DiagnosticIndicator {
Some(
Button::new("diagnostic_message", SharedString::new(message))
.label_size(LabelSize::Small)
.tooltip(|_window, cx| {
.tooltip(|window, cx| {
Tooltip::for_action(
"Next Diagnostic",
&editor::actions::GoToDiagnostic::default(),
window,
cx,
)
})
@@ -86,8 +87,8 @@ impl Render for DiagnosticIndicator {
.child(
ButtonLike::new("diagnostic-indicator")
.child(diagnostic_indicator)
.tooltip(move |_window, cx| {
Tooltip::for_action("Project Diagnostics", &Deploy, cx)
.tooltip(|window, cx| {
Tooltip::for_action("Project Diagnostics", &Deploy, window, cx)
})
.on_click(cx.listener(|this, _, window, cx| {
if let Some(workspace) = this.workspace.upgrade() {

View File

@@ -123,8 +123,8 @@ impl Render for EditPredictionButton {
});
}
}))
.tooltip(|_window, cx| {
Tooltip::for_action("GitHub Copilot", &ToggleMenu, cx)
.tooltip(|window, cx| {
Tooltip::for_action("GitHub Copilot", &ToggleMenu, window, cx)
}),
);
}
@@ -146,7 +146,9 @@ impl Render for EditPredictionButton {
.anchor(Corner::BottomRight)
.trigger_with_tooltip(
IconButton::new("copilot-icon", icon),
|_window, cx| Tooltip::for_action("GitHub Copilot", &ToggleMenu, cx),
|window, cx| {
Tooltip::for_action("GitHub Copilot", &ToggleMenu, window, cx)
},
)
.with_handle(self.popover_menu_handle.clone()),
)
@@ -218,7 +220,12 @@ impl Render for EditPredictionButton {
IconButton::new("supermaven-icon", icon),
move |window, cx| {
if has_menu {
Tooltip::for_action(tooltip_text.clone(), &ToggleMenu, cx)
Tooltip::for_action(
tooltip_text.clone(),
&ToggleMenu,
window,
cx,
)
} else {
Tooltip::text(tooltip_text.clone())(window, cx)
}
@@ -281,7 +288,9 @@ impl Render for EditPredictionButton {
cx.theme().colors().status_bar_background,
))
}),
move |_window, cx| Tooltip::for_action("Codestral", &ToggleMenu, cx),
move |window, cx| {
Tooltip::for_action("Codestral", &ToggleMenu, window, cx)
},
)
.with_handle(self.popover_menu_handle.clone()),
)
@@ -308,8 +317,14 @@ impl Render for EditPredictionButton {
.shape(IconButtonShape::Square)
.indicator(Indicator::dot().color(Color::Muted))
.indicator_border_color(Some(cx.theme().colors().status_bar_background))
.tooltip(move |_window, cx| {
Tooltip::with_meta("Edit Predictions", None, tooltip_meta, cx)
.tooltip(move |window, cx| {
Tooltip::with_meta(
"Edit Predictions",
None,
tooltip_meta,
window,
cx,
)
})
.on_click(cx.listener(move |_, _, window, cx| {
telemetry::event!(
@@ -350,15 +365,16 @@ impl Render for EditPredictionButton {
},
)
.when(!self.popover_menu_handle.is_deployed(), |element| {
element.tooltip(move |_window, cx| {
element.tooltip(move |window, cx| {
if enabled {
if show_editor_predictions {
Tooltip::for_action("Edit Prediction", &ToggleMenu, cx)
Tooltip::for_action("Edit Prediction", &ToggleMenu, window, cx)
} else {
Tooltip::with_meta(
"Edit Prediction",
Some(&ToggleMenu),
"Hidden For This File",
window,
cx,
)
}
@@ -367,6 +383,7 @@ impl Render for EditPredictionButton {
"Edit Prediction",
Some(&ToggleMenu),
"Disabled For This File",
window,
cx,
)
}

View File

@@ -64,7 +64,6 @@ project.workspace = true
rand.workspace = true
regex.workspace = true
rpc.workspace = true
rope.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true

View File

@@ -27,7 +27,7 @@ mod tab_map;
mod wrap_map;
use crate::{
EditorStyle, RowExt, hover_links::InlayHighlight, inlays::Inlay, movement::TextLayoutDetails,
EditorStyle, InlayId, RowExt, hover_links::InlayHighlight, movement::TextLayoutDetails,
};
pub use block_map::{
Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
@@ -42,6 +42,7 @@ pub use fold_map::{
ChunkRenderer, ChunkRendererContext, ChunkRendererId, Fold, FoldId, FoldPlaceholder, FoldPoint,
};
use gpui::{App, Context, Entity, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle};
pub use inlay_map::Inlay;
use inlay_map::InlaySnapshot;
pub use inlay_map::{InlayOffset, InlayPoint};
pub use invisibles::{is_invisible, replacement};
@@ -49,10 +50,9 @@ use language::{
OffsetUtf16, Point, Subscription as BufferSubscription, language_settings::language_settings,
};
use multi_buffer::{
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
RowInfo, ToOffset, ToPoint,
Anchor, AnchorRangeExt, ExcerptId, MultiBuffer, MultiBufferPoint, MultiBufferRow,
MultiBufferSnapshot, RowInfo, ToOffset, ToPoint,
};
use project::InlayId;
use project::project_settings::DiagnosticSeverity;
use serde::Deserialize;
@@ -594,6 +594,25 @@ impl DisplayMap {
self.block_map.read(snapshot, edits);
}
pub fn remove_inlays_for_excerpts(
&mut self,
excerpts_removed: &[ExcerptId],
cx: &mut Context<Self>,
) {
let to_remove = self
.inlay_map
.current_inlays()
.filter_map(|inlay| {
if excerpts_removed.contains(&inlay.position.excerpt_id) {
Some(inlay.id)
} else {
None
}
})
.collect::<Vec<_>>();
self.splice_inlays(&to_remove, Vec::new(), cx);
}
fn tab_size(buffer: &Entity<MultiBuffer>, cx: &App) -> NonZeroU32 {
let buffer = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx));
let language = buffer

View File

@@ -26,8 +26,8 @@ use sum_tree::{Bias, ContextLessSummary, Dimensions, SumTree, TreeMap};
use text::{BufferId, Edit};
use ui::ElementId;
const NEWLINES: &[u8; rope::Chunk::MASK_BITS] = &[b'\n'; _];
const BULLETS: &[u8; rope::Chunk::MASK_BITS] = &[b'*'; _];
const NEWLINES: &[u8; u128::BITS as usize] = &[b'\n'; _];
const BULLETS: &[u8; u128::BITS as usize] = &[b'*'; _];
/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
///
@@ -1783,11 +1783,11 @@ impl<'a> Iterator for BlockChunks<'a> {
if self.masked {
// Not great for multibyte text because to keep cursor math correct we
// need to have the same number of chars in the input as output.
// need to have the same number of bytes in the input as output.
let chars_count = prefix.chars().count();
let bullet_len = chars_count;
prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
chars = 1u128.unbounded_shl(bullet_len as u32) - 1;
tabs = 0;
}

View File

@@ -132,31 +132,37 @@ impl<'a> Iterator for CustomHighlightsChunks<'a> {
}
}
let chunk = match &mut self.buffer_chunk {
Some(it) => it,
slot => slot.insert(self.buffer_chunks.next()?),
};
while chunk.text.is_empty() {
let chunk = self
.buffer_chunk
.get_or_insert_with(|| self.buffer_chunks.next().unwrap_or_default());
if chunk.text.is_empty() {
*chunk = self.buffer_chunks.next()?;
}
let split_idx = chunk.text.len().min(next_highlight_endpoint - self.offset);
let (prefix, suffix) = chunk.text.split_at(split_idx);
self.offset += prefix.len();
let mask = 1u128.unbounded_shl(split_idx as u32).wrapping_sub(1);
let chars = chunk.chars & mask;
let tabs = chunk.tabs & mask;
let (chars, tabs) = if split_idx == 128 {
let output = (chunk.chars, chunk.tabs);
chunk.chars = 0;
chunk.tabs = 0;
output
} else {
let mask = (1 << split_idx) - 1;
let output = (chunk.chars & mask, chunk.tabs & mask);
chunk.chars = chunk.chars >> split_idx;
chunk.tabs = chunk.tabs >> split_idx;
output
};
chunk.text = suffix;
self.offset += prefix.len();
let mut prefix = Chunk {
text: prefix,
chars,
tabs,
..chunk.clone()
};
chunk.chars = chunk.chars.unbounded_shr(split_idx as u32);
chunk.tabs = chunk.tabs.unbounded_shr(split_idx as u32);
chunk.text = suffix;
if !self.active_highlights.is_empty() {
prefix.highlight_style = self
.active_highlights

View File

@@ -1,4 +1,4 @@
use crate::display_map::inlay_map::InlayChunk;
use crate::{InlayId, display_map::inlay_map::InlayChunk};
use super::{
Highlights,
@@ -9,7 +9,6 @@ use language::{Edit, HighlightId, Point, TextSummary};
use multi_buffer::{
Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset,
};
use project::InlayId;
use std::{
any::TypeId,
cmp::{self, Ordering},
@@ -1437,15 +1436,14 @@ impl<'a> Iterator for FoldChunks<'a> {
let transform_end = self.transform_cursor.end().1;
let chunk_end = buffer_chunk_end.min(transform_end);
let bit_start = (self.inlay_offset - buffer_chunk_start).0;
let bit_end = (chunk_end - buffer_chunk_start).0;
chunk.text = &chunk.text[bit_start..bit_end];
chunk.text = &chunk.text
[(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0];
let bit_end = (chunk_end - buffer_chunk_start).0;
let mask = 1u128.unbounded_shl(bit_end as u32).wrapping_sub(1);
chunk.tabs = (chunk.tabs >> bit_start) & mask;
chunk.chars = (chunk.chars >> bit_start) & mask;
chunk.tabs = (chunk.tabs >> (self.inlay_offset - buffer_chunk_start).0) & mask;
chunk.chars = (chunk.chars >> (self.inlay_offset - buffer_chunk_start).0) & mask;
if chunk_end == transform_end {
self.transform_cursor.next();

View File

@@ -1,18 +1,17 @@
use crate::{
ChunkRenderer, HighlightStyles,
inlays::{Inlay, InlayContent},
};
use crate::{ChunkRenderer, HighlightStyles, InlayId};
use collections::BTreeSet;
use gpui::{Hsla, Rgba};
use language::{Chunk, Edit, Point, TextSummary};
use multi_buffer::{MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, RowInfo, ToOffset};
use project::InlayId;
use multi_buffer::{
Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, RowInfo, ToOffset,
};
use std::{
cmp,
ops::{Add, AddAssign, Range, Sub, SubAssign},
sync::Arc,
sync::{Arc, OnceLock},
};
use sum_tree::{Bias, Cursor, Dimensions, SumTree};
use text::{ChunkBitmaps, Patch};
use text::{ChunkBitmaps, Patch, Rope};
use ui::{ActiveTheme, IntoElement as _, ParentElement as _, Styled as _, div};
use super::{Highlights, custom_highlights::CustomHighlightsChunks, fold_map::ChunkRendererId};
@@ -38,6 +37,85 @@ enum Transform {
Inlay(Inlay),
}
#[derive(Debug, Clone)]
pub struct Inlay {
pub id: InlayId,
pub position: Anchor,
pub content: InlayContent,
}
#[derive(Debug, Clone)]
pub enum InlayContent {
Text(text::Rope),
Color(Hsla),
}
impl Inlay {
pub fn hint(id: u32, position: Anchor, hint: &project::InlayHint) -> Self {
let mut text = hint.text();
if hint.padding_right && text.reversed_chars_at(text.len()).next() != Some(' ') {
text.push(" ");
}
if hint.padding_left && text.chars_at(0).next() != Some(' ') {
text.push_front(" ");
}
Self {
id: InlayId::Hint(id),
position,
content: InlayContent::Text(text),
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn mock_hint(id: u32, position: Anchor, text: impl Into<Rope>) -> Self {
Self {
id: InlayId::Hint(id),
position,
content: InlayContent::Text(text.into()),
}
}
pub fn color(id: u32, position: Anchor, color: Rgba) -> Self {
Self {
id: InlayId::Color(id),
position,
content: InlayContent::Color(color.into()),
}
}
pub fn edit_prediction<T: Into<Rope>>(id: u32, position: Anchor, text: T) -> Self {
Self {
id: InlayId::EditPrediction(id),
position,
content: InlayContent::Text(text.into()),
}
}
pub fn debugger<T: Into<Rope>>(id: u32, position: Anchor, text: T) -> Self {
Self {
id: InlayId::DebuggerValue(id),
position,
content: InlayContent::Text(text.into()),
}
}
pub fn text(&self) -> &Rope {
static COLOR_TEXT: OnceLock<Rope> = OnceLock::new();
match &self.content {
InlayContent::Text(text) => text,
InlayContent::Color(_) => COLOR_TEXT.get_or_init(|| Rope::from("")),
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn get_color(&self) -> Option<Hsla> {
match self.content {
InlayContent::Color(color) => Some(color),
_ => None,
}
}
}
impl sum_tree::Item for Transform {
type Summary = TransformSummary;
@@ -247,16 +325,21 @@ impl<'a> Iterator for InlayChunks<'a> {
};
let (prefix, suffix) = chunk.text.split_at(split_index);
self.output_offset.0 += prefix.len();
let mask = 1u128.unbounded_shl(split_index as u32).wrapping_sub(1);
let chars = chunk.chars & mask;
let tabs = chunk.tabs & mask;
chunk.chars = chunk.chars.unbounded_shr(split_index as u32);
chunk.tabs = chunk.tabs.unbounded_shr(split_index as u32);
let (chars, tabs) = if split_index == 128 {
let output = (chunk.chars, chunk.tabs);
chunk.chars = 0;
chunk.tabs = 0;
output
} else {
let mask = (1 << split_index) - 1;
let output = (chunk.chars & mask, chunk.tabs & mask);
chunk.chars = chunk.chars >> split_index;
chunk.tabs = chunk.tabs >> split_index;
output
};
chunk.text = suffix;
self.output_offset.0 += prefix.len();
InlayChunk {
chunk: Chunk {
text: prefix,
@@ -374,12 +457,18 @@ impl<'a> Iterator for InlayChunks<'a> {
let (chunk, remainder) = inlay_chunk.split_at(split_index);
*inlay_chunk = remainder;
let mask = 1u128.unbounded_shl(split_index as u32).wrapping_sub(1);
let new_chars = *chars & mask;
let new_tabs = *tabs & mask;
*chars = chars.unbounded_shr(split_index as u32);
*tabs = tabs.unbounded_shr(split_index as u32);
let (chars, tabs) = if split_index == 128 {
let output = (*chars, *tabs);
*chars = 0;
*tabs = 0;
output
} else {
let mask = (1 << split_index as u32) - 1;
let output = (*chars & mask, *tabs & mask);
*chars = *chars >> split_index;
*tabs = *tabs >> split_index;
output
};
if inlay_chunk.is_empty() {
self.inlay_chunk = None;
@@ -390,8 +479,8 @@ impl<'a> Iterator for InlayChunks<'a> {
InlayChunk {
chunk: Chunk {
text: chunk,
chars: new_chars,
tabs: new_tabs,
chars,
tabs,
highlight_style,
is_inlay: true,
..Chunk::default()
@@ -672,7 +761,7 @@ impl InlayMap {
#[cfg(test)]
pub(crate) fn randomly_mutate(
&mut self,
next_inlay_id: &mut usize,
next_inlay_id: &mut u32,
rng: &mut rand::rngs::StdRng,
) -> (InlaySnapshot, Vec<InlayEdit>) {
use rand::prelude::*;
@@ -1167,18 +1256,17 @@ const fn is_utf8_char_boundary(byte: u8) -> bool {
mod tests {
use super::*;
use crate::{
MultiBuffer,
InlayId, MultiBuffer,
display_map::{HighlightKey, InlayHighlights, TextHighlights},
hover_links::InlayHighlight,
};
use gpui::{App, HighlightStyle};
use multi_buffer::Anchor;
use project::{InlayHint, InlayHintLabel, ResolveState};
use rand::prelude::*;
use settings::SettingsStore;
use std::{any::TypeId, cmp::Reverse, env, sync::Arc};
use sum_tree::TreeMap;
use text::{Patch, Rope};
use text::Patch;
use util::RandomCharIter;
use util::post_inc;
@@ -1186,7 +1274,7 @@ mod tests {
fn test_inlay_properties_label_padding() {
assert_eq!(
Inlay::hint(
InlayId::Hint(0),
0,
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String("a".to_string()),
@@ -1206,7 +1294,7 @@ mod tests {
assert_eq!(
Inlay::hint(
InlayId::Hint(0),
0,
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String("a".to_string()),
@@ -1226,7 +1314,7 @@ mod tests {
assert_eq!(
Inlay::hint(
InlayId::Hint(0),
0,
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String(" a ".to_string()),
@@ -1246,7 +1334,7 @@ mod tests {
assert_eq!(
Inlay::hint(
InlayId::Hint(0),
0,
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String(" a ".to_string()),
@@ -1269,7 +1357,7 @@ mod tests {
fn test_inlay_hint_padding_with_multibyte_chars() {
assert_eq!(
Inlay::hint(
InlayId::Hint(0),
0,
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String("🎨".to_string()),

View File

@@ -11,7 +11,7 @@ use sum_tree::Bias;
const MAX_EXPANSION_COLUMN: u32 = 256;
// Handles a tab width <= 128
const SPACES: &[u8; rope::Chunk::MASK_BITS] = &[b' '; _];
const SPACES: &[u8; u128::BITS as usize] = &[b' '; _];
const MAX_TABS: NonZeroU32 = NonZeroU32::new(SPACES.len() as u32).unwrap();
/// Keeps track of hard tabs in a text buffer.
@@ -569,47 +569,56 @@ impl<'a> Iterator for TabChunks<'a> {
//todo(improve performance by using tab cursor)
for (ix, c) in self.chunk.text.char_indices() {
match c {
'\t' if ix > 0 => {
let (prefix, suffix) = self.chunk.text.split_at(ix);
let mask = 1u128.unbounded_shl(ix as u32).wrapping_sub(1);
let chars = self.chunk.chars & mask;
let tabs = self.chunk.tabs & mask;
self.chunk.tabs = self.chunk.tabs.unbounded_shr(ix as u32);
self.chunk.chars = self.chunk.chars.unbounded_shr(ix as u32);
self.chunk.text = suffix;
return Some(Chunk {
text: prefix,
chars,
tabs,
..self.chunk.clone()
});
}
'\t' => {
self.chunk.text = &self.chunk.text[1..];
self.chunk.tabs >>= 1;
self.chunk.chars >>= 1;
let tab_size = if self.input_column < self.max_expansion_column {
self.tab_size.get()
if ix > 0 {
let (prefix, suffix) = self.chunk.text.split_at(ix);
let (chars, tabs) = if ix == 128 {
let output = (self.chunk.chars, self.chunk.tabs);
self.chunk.chars = 0;
self.chunk.tabs = 0;
output
} else {
let mask = (1 << ix) - 1;
let output = (self.chunk.chars & mask, self.chunk.tabs & mask);
self.chunk.chars = self.chunk.chars >> ix;
self.chunk.tabs = self.chunk.tabs >> ix;
output
};
self.chunk.text = suffix;
return Some(Chunk {
text: prefix,
chars,
tabs,
..self.chunk.clone()
});
} else {
1
};
let mut len = tab_size - self.column % tab_size;
let next_output_position = cmp::min(
self.output_position + Point::new(0, len),
self.max_output_position,
);
len = next_output_position.column - self.output_position.column;
self.column += len;
self.input_column += 1;
self.output_position = next_output_position;
return Some(Chunk {
text: unsafe { std::str::from_utf8_unchecked(&SPACES[..len as usize]) },
is_tab: true,
chars: 1u128.unbounded_shl(len) - 1,
tabs: 0,
..self.chunk.clone()
});
self.chunk.text = &self.chunk.text[1..];
self.chunk.tabs >>= 1;
self.chunk.chars >>= 1;
let tab_size = if self.input_column < self.max_expansion_column {
self.tab_size.get()
} else {
1
};
let mut len = tab_size - self.column % tab_size;
let next_output_position = cmp::min(
self.output_position + Point::new(0, len),
self.max_output_position,
);
len = next_output_position.column - self.output_position.column;
self.column += len;
self.input_column += 1;
self.output_position = next_output_position;
return Some(Chunk {
text: unsafe { std::str::from_utf8_unchecked(&SPACES[..len as usize]) },
is_tab: true,
chars: 1u128.unbounded_shl(len) - 1,
tabs: 0,
..self.chunk.clone()
});
}
}
'\n' => {
self.column = 0;

View File

@@ -972,11 +972,18 @@ impl<'a> Iterator for WrapChunks<'a> {
let (prefix, suffix) = self.input_chunk.text.split_at(input_len);
let mask = 1u128.unbounded_shl(input_len as u32).wrapping_sub(1);
let chars = self.input_chunk.chars & mask;
let tabs = self.input_chunk.tabs & mask;
self.input_chunk.tabs = self.input_chunk.tabs.unbounded_shr(input_len as u32);
self.input_chunk.chars = self.input_chunk.chars.unbounded_shr(input_len as u32);
let (chars, tabs) = if input_len == 128 {
let output = (self.input_chunk.chars, self.input_chunk.tabs);
self.input_chunk.chars = 0;
self.input_chunk.tabs = 0;
output
} else {
let mask = (1 << input_len) - 1;
let output = (self.input_chunk.chars & mask, self.input_chunk.tabs & mask);
self.input_chunk.chars = self.input_chunk.chars >> input_len;
self.input_chunk.tabs = self.input_chunk.tabs >> input_len;
output
};
self.input_chunk.text = suffix;
Some(Chunk {

View File

@@ -7,6 +7,7 @@
//! * [`element`] — the place where all rendering happens
//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
//!
//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
//!
@@ -23,7 +24,7 @@ mod highlight_matching_bracket;
mod hover_links;
pub mod hover_popover;
mod indent_guides;
mod inlays;
mod inlay_hint_cache;
pub mod items;
mod jsx_tag_auto_close;
mod linked_editing_ranges;
@@ -60,7 +61,6 @@ pub use element::{
};
pub use git::blame::BlameRenderer;
pub use hover_popover::hover_markdown_style;
pub use inlays::Inlay;
pub use items::MAX_TAB_TITLE_LEN;
pub use lsp::CompletionContext;
pub use lsp_ext::lsp_tasks;
@@ -112,10 +112,10 @@ use gpui::{
div, point, prelude::*, pulsating_between, px, relative, size,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_links::{HoverLink, HoveredLinkState, find_file};
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
use hover_popover::{HoverState, hide_hover};
use indent_guides::ActiveIndentGuidesState;
use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
use itertools::{Either, Itertools};
use language::{
AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
@@ -124,8 +124,8 @@ use language::{
IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
TextObject, TransactionId, TreeSitterOptions, WordsQuery,
language_settings::{
self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
language_settings,
self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
all_language_settings, language_settings,
},
point_from_lsp, point_to_lsp, text_diff_with_options,
};
@@ -146,9 +146,9 @@ use parking_lot::Mutex;
use persistence::DB;
use project::{
BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
ProjectPath, ProjectTransaction, TaskSourceKind,
CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
ProjectTransaction, TaskSourceKind,
debugger::{
breakpoint_store::{
Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
@@ -157,10 +157,7 @@ use project::{
session::{Session, SessionEvent},
},
git_store::{GitStoreEvent, RepositoryEvent},
lsp_store::{
CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
OpenLspBufferHandle,
},
lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
};
use rand::seq::SliceRandom;
@@ -181,7 +178,7 @@ use std::{
iter::{self, Peekable},
mem,
num::NonZeroU32,
ops::{Deref, DerefMut, Not, Range, RangeInclusive},
ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
@@ -211,10 +208,6 @@ use crate::{
code_context_menus::CompletionsMenuSource,
editor_settings::MultiCursorModifier,
hover_links::{find_url, find_url_from_range},
inlays::{
InlineValueCache,
inlay_hints::{LspInlayHintData, inlay_hint_settings},
},
scroll::{ScrollOffset, ScrollPixelOffset},
signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
};
@@ -268,6 +261,42 @@ impl ReportEditorEvent {
}
}
struct InlineValueCache {
enabled: bool,
inlays: Vec<InlayId>,
refresh_task: Task<Option<()>>,
}
impl InlineValueCache {
fn new(enabled: bool) -> Self {
Self {
enabled,
inlays: Vec::new(),
refresh_task: Task::ready(None),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum InlayId {
EditPrediction(u32),
DebuggerValue(u32),
// LSP
Hint(u32),
Color(u32),
}
impl InlayId {
fn id(&self) -> u32 {
match self {
Self::EditPrediction(id) => *id,
Self::DebuggerValue(id) => *id,
Self::Hint(id) => *id,
Self::Color(id) => *id,
}
}
}
pub enum ActiveDebugLine {}
pub enum DebugStackFrameLine {}
enum DocumentHighlightRead {}
@@ -1095,8 +1124,9 @@ pub struct Editor {
edit_prediction_preview: EditPredictionPreview,
edit_prediction_indent_conflict: bool,
edit_prediction_requires_modifier_in_indent_conflict: bool,
next_inlay_id: usize,
next_color_inlay_id: usize,
inlay_hint_cache: InlayHintCache,
next_inlay_id: u32,
next_color_inlay_id: u32,
_subscriptions: Vec<Subscription>,
pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
gutter_dimensions: GutterDimensions,
@@ -1163,19 +1193,10 @@ pub struct Editor {
colors: Option<LspColorData>,
post_scroll_update: Task<()>,
refresh_colors_task: Task<()>,
inlay_hints: Option<LspInlayHintData>,
folding_newlines: Task<()>,
pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
}
fn debounce_value(debounce_ms: u64) -> Option<Duration> {
if debounce_ms > 0 {
Some(Duration::from_millis(debounce_ms))
} else {
None
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
enum NextScrollCursorCenterTopBottom {
#[default]
@@ -1600,6 +1621,31 @@ pub enum GotoDefinitionKind {
Implementation,
}
#[derive(Debug, Clone)]
enum InlayHintRefreshReason {
ModifiersChanged(bool),
Toggle(bool),
SettingsChange(InlayHintSettings),
NewLinesShown,
BufferEdited(HashSet<Arc<Language>>),
RefreshRequested,
ExcerptsRemoved(Vec<ExcerptId>),
}
impl InlayHintRefreshReason {
fn description(&self) -> &'static str {
match self {
Self::ModifiersChanged(_) => "modifiers changed",
Self::Toggle(_) => "toggle",
Self::SettingsChange(_) => "settings change",
Self::NewLinesShown => "new lines shown",
Self::BufferEdited(_) => "buffer edited",
Self::RefreshRequested => "refresh requested",
Self::ExcerptsRemoved(_) => "excerpts removed",
}
}
}
pub enum FormatTarget {
Buffers(HashSet<Entity<Buffer>>),
Ranges(Vec<Range<MultiBufferPoint>>),
@@ -1835,11 +1881,8 @@ impl Editor {
project::Event::RefreshCodeLens => {
// we always query lens with actions, without storing them, always refreshing them
}
project::Event::RefreshInlayHints(server_id) => {
editor.refresh_inlay_hints(
InlayHintRefreshReason::RefreshRequested(*server_id),
cx,
);
project::Event::RefreshInlayHints => {
editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
}
project::Event::LanguageServerRemoved(..) => {
if editor.tasks_update_task.is_none() {
@@ -1876,12 +1919,17 @@ impl Editor {
project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
let buffer_id = *buffer_id;
if editor.buffer().read(cx).buffer(buffer_id).is_some() {
editor.register_buffer(buffer_id, cx);
editor.update_lsp_data(Some(buffer_id), window, cx);
editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
refresh_linked_ranges(editor, window, cx);
editor.refresh_code_actions(window, cx);
editor.refresh_document_highlights(cx);
let registered = editor.register_buffer(buffer_id, cx);
if registered {
editor.update_lsp_data(Some(buffer_id), window, cx);
editor.refresh_inlay_hints(
InlayHintRefreshReason::RefreshRequested,
cx,
);
refresh_linked_ranges(editor, window, cx);
editor.refresh_code_actions(window, cx);
editor.refresh_document_highlights(cx);
}
}
}
@@ -2152,6 +2200,7 @@ impl Editor {
diagnostics_enabled: full_mode,
word_completions_enabled: full_mode,
inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
gutter_hovered: false,
pixel_position_of_newest_cursor: None,
last_bounds: None,
@@ -2217,7 +2266,6 @@ impl Editor {
pull_diagnostics_task: Task::ready(()),
colors: None,
refresh_colors_task: Task::ready(()),
inlay_hints: None,
next_color_inlay_id: 0,
post_scroll_update: Task::ready(()),
linked_edit_ranges: Default::default(),
@@ -2355,15 +2403,13 @@ impl Editor {
editor.go_to_active_debug_line(window, cx);
editor.minimap =
editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
editor.colors = Some(LspColorData::new(cx));
editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
editor.register_buffer(buffer.read(cx).remote_id(), cx);
}
editor.update_lsp_data(None, window, cx);
editor.minimap =
editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
editor.colors = Some(LspColorData::new(cx));
editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
}
@@ -5152,8 +5198,179 @@ impl Editor {
}
}
pub fn toggle_inline_values(
&mut self,
_: &ToggleInlineValues,
_: &mut Window,
cx: &mut Context<Self>,
) {
self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
self.refresh_inline_values(cx);
}
pub fn toggle_inlay_hints(
&mut self,
_: &ToggleInlayHints,
_: &mut Window,
cx: &mut Context<Self>,
) {
self.refresh_inlay_hints(
InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
cx,
);
}
pub fn inlay_hints_enabled(&self) -> bool {
self.inlay_hint_cache.enabled
}
pub fn inline_values_enabled(&self) -> bool {
self.inline_value_cache.enabled
}
#[cfg(any(test, feature = "test-support"))]
pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
self.display_map
.read(cx)
.current_inlays()
.filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
.cloned()
.collect()
}
#[cfg(any(test, feature = "test-support"))]
pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
self.display_map
.read(cx)
.current_inlays()
.cloned()
.collect()
}
fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
if self.semantics_provider.is_none() || !self.mode.is_full() {
return;
}
let reason_description = reason.description();
let ignore_debounce = matches!(
reason,
InlayHintRefreshReason::SettingsChange(_)
| InlayHintRefreshReason::Toggle(_)
| InlayHintRefreshReason::ExcerptsRemoved(_)
| InlayHintRefreshReason::ModifiersChanged(_)
);
let (invalidate_cache, required_languages) = match reason {
InlayHintRefreshReason::ModifiersChanged(enabled) => {
match self.inlay_hint_cache.modifiers_override(enabled) {
Some(enabled) => {
if enabled {
(InvalidationStrategy::RefreshRequested, None)
} else {
self.clear_inlay_hints(cx);
return;
}
}
None => return,
}
}
InlayHintRefreshReason::Toggle(enabled) => {
if self.inlay_hint_cache.toggle(enabled) {
if enabled {
(InvalidationStrategy::RefreshRequested, None)
} else {
self.clear_inlay_hints(cx);
return;
}
} else {
return;
}
}
InlayHintRefreshReason::SettingsChange(new_settings) => {
match self.inlay_hint_cache.update_settings(
&self.buffer,
new_settings,
self.visible_inlay_hints(cx).cloned().collect::<Vec<_>>(),
cx,
) {
ControlFlow::Break(Some(InlaySplice {
to_remove,
to_insert,
})) => {
self.splice_inlays(&to_remove, to_insert, cx);
return;
}
ControlFlow::Break(None) => return,
ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
}
}
InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
if let Some(InlaySplice {
to_remove,
to_insert,
}) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
{
self.splice_inlays(&to_remove, to_insert, cx);
}
self.display_map.update(cx, |display_map, cx| {
display_map.remove_inlays_for_excerpts(&excerpts_removed, cx)
});
return;
}
InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
InlayHintRefreshReason::BufferEdited(buffer_languages) => {
(InvalidationStrategy::BufferEdited, Some(buffer_languages))
}
InlayHintRefreshReason::RefreshRequested => {
(InvalidationStrategy::RefreshRequested, None)
}
};
let mut visible_excerpts = self.visible_excerpts(required_languages.as_ref(), cx);
visible_excerpts.retain(|_, (buffer, _, _)| {
self.registered_buffers
.contains_key(&buffer.read(cx).remote_id())
});
if let Some(InlaySplice {
to_remove,
to_insert,
}) = self.inlay_hint_cache.spawn_hint_refresh(
reason_description,
visible_excerpts,
invalidate_cache,
ignore_debounce,
cx,
) {
self.splice_inlays(&to_remove, to_insert, cx);
}
}
pub fn clear_inlay_hints(&self, cx: &mut Context<Editor>) {
self.splice_inlays(
&self
.visible_inlay_hints(cx)
.map(|inlay| inlay.id)
.collect::<Vec<_>>(),
Vec::new(),
cx,
);
}
fn visible_inlay_hints<'a>(
&'a self,
cx: &'a Context<Editor>,
) -> impl Iterator<Item = &'a Inlay> {
self.display_map
.read(cx)
.current_inlays()
.filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
}
pub fn visible_excerpts(
&self,
restrict_to_languages: Option<&HashSet<Arc<Language>>>,
cx: &mut Context<Editor>,
) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
let Some(project) = self.project() else {
@@ -5172,8 +5389,9 @@ impl Editor {
+ Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
Bias::Left,
);
let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
multi_buffer_snapshot
.range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
.range_to_buffer_ranges(multi_buffer_visible_range)
.into_iter()
.filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
.filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
@@ -5183,17 +5401,23 @@ impl Editor {
.read(cx)
.entry_for_id(buffer_file.project_entry_id()?)?;
if worktree_entry.is_ignored {
None
} else {
Some((
excerpt_id,
(
multi_buffer.buffer(buffer.remote_id()).unwrap(),
buffer.version().clone(),
excerpt_visible_range,
),
))
return None;
}
let language = buffer.language()?;
if let Some(restrict_to_languages) = restrict_to_languages
&& !restrict_to_languages.contains(language)
{
return None;
}
Some((
excerpt_id,
(
multi_buffer.buffer(buffer.remote_id()).unwrap(),
buffer.version().clone(),
excerpt_visible_range,
),
))
})
.collect()
}
@@ -5209,6 +5433,18 @@ impl Editor {
}
}
pub fn splice_inlays(
&self,
to_remove: &[InlayId],
to_insert: Vec<Inlay>,
cx: &mut Context<Self>,
) {
self.display_map.update(cx, |display_map, cx| {
display_map.splice_inlays(to_remove, to_insert, cx)
});
cx.notify();
}
fn trigger_on_type_formatting(
&self,
input: String,
@@ -6389,7 +6625,7 @@ impl Editor {
.when(show_tooltip, |this| {
this.tooltip({
let focus_handle = self.focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Toggle Code Actions",
&ToggleCodeActions {
@@ -6397,6 +6633,7 @@ impl Editor {
quick_launch: false,
},
&focus_handle,
window,
cx,
)
}
@@ -8261,12 +8498,13 @@ impl Editor {
cx,
);
}))
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta_in(
primary_action_text,
Some(&ToggleBreakpoint),
meta.clone(),
&focus_handle,
window,
cx,
)
})
@@ -17380,9 +17618,9 @@ impl Editor {
HashSet::default(),
cx,
);
cx.emit(project::Event::RefreshInlayHints);
});
});
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
}
}
@@ -20570,6 +20808,18 @@ impl Editor {
cx.notify();
}
pub(crate) fn highlight_inlays<T: 'static>(
&mut self,
highlights: Vec<InlayHighlight>,
style: HighlightStyle,
cx: &mut Context<Self>,
) {
self.display_map.update(cx, |map, _| {
map.highlight_inlays(TypeId::of::<T>(), highlights, style)
});
cx.notify();
}
pub fn text_highlights<'a, T: 'static>(
&'a self,
cx: &'a App,
@@ -20720,19 +20970,38 @@ impl Editor {
self.update_visible_edit_prediction(window, cx);
}
if let Some(buffer) = edited_buffer {
if buffer.read(cx).file().is_none() {
if let Some(edited_buffer) = edited_buffer {
if edited_buffer.read(cx).file().is_none() {
cx.emit(EditorEvent::TitleChanged);
}
if self.project.is_some() {
let buffer_id = buffer.read(cx).remote_id();
let buffer_id = edited_buffer.read(cx).remote_id();
if let Some(project) = self.project.clone() {
self.register_buffer(buffer_id, cx);
self.update_lsp_data(Some(buffer_id), window, cx);
self.refresh_inlay_hints(
InlayHintRefreshReason::BufferEdited(buffer_id),
cx,
);
#[allow(clippy::mutable_key_type)]
let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
multibuffer
.all_buffers()
.into_iter()
.filter_map(|buffer| {
buffer.update(cx, |buffer, cx| {
let language = buffer.language()?;
let should_discard = project.update(cx, |project, cx| {
project.is_local()
&& !project.has_language_servers_for(buffer, cx)
});
should_discard.not().then_some(language.clone())
})
})
.collect::<HashSet<_>>()
});
if !languages_affected.is_empty() {
self.refresh_inlay_hints(
InlayHintRefreshReason::BufferEdited(languages_affected),
cx,
);
}
}
}
@@ -20779,9 +21048,6 @@ impl Editor {
ids,
removed_buffer_ids,
} => {
if let Some(inlay_hints) = &mut self.inlay_hints {
inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
}
self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
for buffer_id in removed_buffer_ids {
self.registered_buffers.remove(buffer_id);
@@ -20956,7 +21222,7 @@ impl Editor {
if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
}) {
if !inlay_splice.is_empty() {
if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
}
self.refresh_colors_for_visible_range(None, window, cx);
@@ -21418,6 +21684,10 @@ impl Editor {
mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
}
pub fn inlay_hint_cache(&self) -> &InlayHintCache {
&self.inlay_hint_cache
}
pub fn replay_insert_event(
&mut self,
text: &str,
@@ -21456,6 +21726,21 @@ impl Editor {
self.handle_input(text, window, cx);
}
pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
let Some(provider) = self.semantics_provider.as_ref() else {
return false;
};
let mut supports = false;
self.buffer().update(cx, |this, cx| {
this.for_each_buffer(|buffer| {
supports |= provider.supports_inlay_hints(buffer, cx);
});
});
supports
}
pub fn is_focused(&self, window: &Window) -> bool {
self.focus_handle.is_focused(window)
}
@@ -21871,12 +22156,12 @@ impl Editor {
if self.ignore_lsp_data() {
return;
}
for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
for (_, (visible_buffer, _, _)) in self.visible_excerpts(None, cx) {
self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
}
}
fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) -> bool {
if !self.registered_buffers.contains_key(&buffer_id)
&& let Some(project) = self.project.as_ref()
{
@@ -21887,10 +22172,13 @@ impl Editor {
project.register_buffer_with_language_servers(&buffer, cx),
);
});
return true;
} else {
self.registered_buffers.remove(&buffer_id);
}
}
false
}
fn ignore_lsp_data(&self) -> bool {
@@ -22598,23 +22886,20 @@ pub trait SemanticsProvider {
cx: &mut App,
) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
fn applicable_inlay_chunks(
&self,
buffer_id: BufferId,
ranges: &[Range<text::Anchor>],
cx: &App,
) -> Vec<Range<BufferRow>>;
fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
fn inlay_hints(
&self,
invalidate: InvalidationStrategy,
buffer: Entity<Buffer>,
ranges: Vec<Range<text::Anchor>>,
known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
buffer_handle: Entity<Buffer>,
range: Range<text::Anchor>,
cx: &mut App,
) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
fn resolve_inlay_hint(
&self,
hint: InlayHint,
buffer_handle: Entity<Buffer>,
server_id: LanguageServerId,
cx: &mut App,
) -> Option<Task<anyhow::Result<InlayHint>>>;
fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
@@ -23107,34 +23392,26 @@ impl SemanticsProvider for Entity<Project> {
})
}
fn applicable_inlay_chunks(
&self,
buffer_id: BufferId,
ranges: &[Range<text::Anchor>],
cx: &App,
) -> Vec<Range<BufferRow>> {
self.read(cx)
.lsp_store()
.read(cx)
.applicable_inlay_chunks(buffer_id, ranges)
}
fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
self.read(cx).lsp_store().update(cx, |lsp_store, _| {
lsp_store.invalidate_inlay_hints(for_buffers)
});
}
fn inlay_hints(
&self,
invalidate: InvalidationStrategy,
buffer: Entity<Buffer>,
ranges: Vec<Range<text::Anchor>>,
known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
buffer_handle: Entity<Buffer>,
range: Range<text::Anchor>,
cx: &mut App,
) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
Some(self.update(cx, |project, cx| {
project.inlay_hints(buffer_handle, range, cx)
}))
}
fn resolve_inlay_hint(
&self,
hint: InlayHint,
buffer_handle: Entity<Buffer>,
server_id: LanguageServerId,
cx: &mut App,
) -> Option<Task<anyhow::Result<InlayHint>>> {
Some(self.update(cx, |project, cx| {
project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
}))
}
@@ -23183,6 +23460,16 @@ impl SemanticsProvider for Entity<Project> {
}
}
fn inlay_hint_settings(
location: Anchor,
snapshot: &MultiBufferSnapshot,
cx: &mut Context<Editor>,
) -> InlayHintSettings {
let file = snapshot.file_at(location);
let language = snapshot.language_at(location).map(|l| l.name());
language_settings(language, file, cx).inlay_hints
}
fn consume_contiguous_rows(
contiguous_row_selections: &mut Vec<Selection<Point>>,
selection: &Selection<Point>,
@@ -24586,11 +24873,12 @@ fn render_diff_hunk_controls(
.alpha(if status.is_pending() { 0.66 } else { 1.0 })
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Stage Hunk",
&::git::ToggleStaged,
&focus_handle,
window,
cx,
)
}
@@ -24612,11 +24900,12 @@ fn render_diff_hunk_controls(
.alpha(if status.is_pending() { 0.66 } else { 1.0 })
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Unstage Hunk",
&::git::ToggleStaged,
&focus_handle,
window,
cx,
)
}
@@ -24638,8 +24927,14 @@ fn render_diff_hunk_controls(
Button::new(("restore", row as u64), "Restore")
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |_window, cx| {
Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in(
"Restore Hunk",
&::git::Restore,
&focus_handle,
window,
cx,
)
}
})
.on_click({
@@ -24664,8 +24959,14 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |_window, cx| {
Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in(
"Next Hunk",
&GoToHunk,
&focus_handle,
window,
cx,
)
}
})
.on_click({
@@ -24694,11 +24995,12 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Previous Hunk",
&GoToPreviousHunk,
&focus_handle,
window,
cx,
)
}

View File

@@ -31,7 +31,6 @@ use language::{
tree_sitter_python,
};
use language_settings::Formatter;
use languages::rust_lang;
use lsp::CompletionParams;
use multi_buffer::{IndentGuide, PathKey};
use parking_lot::Mutex;
@@ -51,7 +50,7 @@ use std::{
iter,
sync::atomic::{self, AtomicUsize},
};
use test::build_editor_with_project;
use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
use text::ToPoint as _;
use unindent::Unindent;
use util::{
@@ -63,7 +62,7 @@ use util::{
use workspace::{
CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
OpenOptions, ViewId,
invalid_item_view::InvalidItemView,
invalid_buffer_view::InvalidBufferView,
item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
register_project_item,
};
@@ -12641,7 +12640,6 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
);
}
});
cx.run_until_parked();
// Handle formatting requests to the language server.
cx.lsp
@@ -26253,7 +26251,7 @@ async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
assert_eq!(
handle.to_any().entity_type(),
TypeId::of::<InvalidItemView>()
TypeId::of::<InvalidBufferView>()
);
}

View File

@@ -1422,11 +1422,7 @@ impl EditorElement {
layouts.push(layout);
}
let mut player = editor.current_user_player_color(cx);
if !editor.is_focused(window) {
const UNFOCUS_EDITOR_SELECTION_OPACITY: f32 = 0.5;
player.selection = player.selection.opacity(UNFOCUS_EDITOR_SELECTION_OPACITY);
}
let player = editor.current_user_player_color(cx);
selections.push((player, layouts));
if let SelectionDragState::Dragging {
@@ -3914,7 +3910,7 @@ impl EditorElement {
.children(toggle_chevron_icon)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::with_meta_in(
"Toggle Excerpt Fold",
Some(&ToggleFold),
@@ -3927,6 +3923,7 @@ impl EditorElement {
)
),
&focus_handle,
window,
cx,
)
}
@@ -4027,11 +4024,15 @@ impl EditorElement {
.id("jump-to-file-button")
.gap_2p5()
.child(Label::new("Jump To File"))
.child(KeyBinding::for_action_in(
&OpenExcerpts,
&focus_handle,
cx,
)),
.children(
KeyBinding::for_action_in(
&OpenExcerpts,
&focus_handle,
window,
cx,
)
.map(|binding| binding.into_any_element()),
),
)
},
)

View File

@@ -1,14 +1,19 @@
use crate::{
Anchor, Editor, EditorSettings, EditorSnapshot, FindAllReferences, GoToDefinition,
GoToDefinitionSplit, GoToTypeDefinition, GoToTypeDefinitionSplit, GotoDefinitionKind,
Navigated, PointForPosition, SelectPhase, editor_settings::GoToDefinitionFallback,
GoToDefinitionSplit, GoToTypeDefinition, GoToTypeDefinitionSplit, GotoDefinitionKind, InlayId,
Navigated, PointForPosition, SelectPhase,
editor_settings::GoToDefinitionFallback,
hover_popover::{self, InlayHover},
scroll::ScrollAmount,
};
use gpui::{App, AsyncWindowContext, Context, Entity, Modifiers, Task, Window, px};
use language::{Bias, ToOffset};
use linkify::{LinkFinder, LinkKind};
use lsp::LanguageServerId;
use project::{InlayId, LocationLink, Project, ResolvedPath};
use project::{
HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink, Project,
ResolveState, ResolvedPath,
};
use settings::Settings;
use std::ops::Range;
use theme::ActiveTheme as _;
@@ -133,9 +138,10 @@ impl Editor {
show_link_definition(modifiers.shift, self, trigger_point, snapshot, window, cx);
}
None => {
self.update_inlay_link_and_hover_points(
update_inlay_link_and_hover_points(
snapshot,
point_for_position,
self,
hovered_link_modifier,
modifiers.shift,
window,
@@ -277,6 +283,182 @@ impl Editor {
}
}
pub fn update_inlay_link_and_hover_points(
snapshot: &EditorSnapshot,
point_for_position: PointForPosition,
editor: &mut Editor,
secondary_held: bool,
shift_held: bool,
window: &mut Window,
cx: &mut Context<Editor>,
) {
let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
} else {
None
};
let mut go_to_definition_updated = false;
let mut hover_updated = false;
if let Some(hovered_offset) = hovered_offset {
let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
let previous_valid_anchor =
buffer_snapshot.anchor_before(point_for_position.previous_valid.to_point(snapshot));
let next_valid_anchor =
buffer_snapshot.anchor_after(point_for_position.next_valid.to_point(snapshot));
if let Some(hovered_hint) = editor
.visible_inlay_hints(cx)
.skip_while(|hint| {
hint.position
.cmp(&previous_valid_anchor, &buffer_snapshot)
.is_lt()
})
.take_while(|hint| {
hint.position
.cmp(&next_valid_anchor, &buffer_snapshot)
.is_le()
})
.max_by_key(|hint| hint.id)
{
let inlay_hint_cache = editor.inlay_hint_cache();
let excerpt_id = previous_valid_anchor.excerpt_id;
if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
match cached_hint.resolve_state {
ResolveState::CanResolve(_, _) => {
if let Some(buffer_id) = snapshot
.buffer_snapshot()
.buffer_id_for_anchor(previous_valid_anchor)
{
inlay_hint_cache.spawn_hint_resolve(
buffer_id,
excerpt_id,
hovered_hint.id,
window,
cx,
);
}
}
ResolveState::Resolved => {
let mut extra_shift_left = 0;
let mut extra_shift_right = 0;
if cached_hint.padding_left {
extra_shift_left += 1;
extra_shift_right += 1;
}
if cached_hint.padding_right {
extra_shift_right += 1;
}
match cached_hint.label {
project::InlayHintLabel::String(_) => {
if let Some(tooltip) = cached_hint.tooltip {
hover_popover::hover_at_inlay(
editor,
InlayHover {
tooltip: match tooltip {
InlayHintTooltip::String(text) => HoverBlock {
text,
kind: HoverBlockKind::PlainText,
},
InlayHintTooltip::MarkupContent(content) => {
HoverBlock {
text: content.value,
kind: content.kind,
}
}
},
range: InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: extra_shift_left
..hovered_hint.text().len() + extra_shift_right,
},
},
window,
cx,
);
hover_updated = true;
}
}
project::InlayHintLabel::LabelParts(label_parts) => {
let hint_start =
snapshot.anchor_to_inlay_offset(hovered_hint.position);
if let Some((hovered_hint_part, part_range)) =
hover_popover::find_hovered_hint_part(
label_parts,
hint_start,
hovered_offset,
)
{
let highlight_start =
(part_range.start - hint_start).0 + extra_shift_left;
let highlight_end =
(part_range.end - hint_start).0 + extra_shift_right;
let highlight = InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: highlight_start..highlight_end,
};
if let Some(tooltip) = hovered_hint_part.tooltip {
hover_popover::hover_at_inlay(
editor,
InlayHover {
tooltip: match tooltip {
InlayHintLabelPartTooltip::String(text) => {
HoverBlock {
text,
kind: HoverBlockKind::PlainText,
}
}
InlayHintLabelPartTooltip::MarkupContent(
content,
) => HoverBlock {
text: content.value,
kind: content.kind,
},
},
range: highlight.clone(),
},
window,
cx,
);
hover_updated = true;
}
if let Some((language_server_id, location)) =
hovered_hint_part.location
&& secondary_held
&& !editor.has_pending_nonempty_selection()
{
go_to_definition_updated = true;
show_link_definition(
shift_held,
editor,
TriggerPoint::InlayHint(
highlight,
location,
language_server_id,
),
snapshot,
window,
cx,
);
}
}
}
};
}
ResolveState::Resolving => {}
}
}
}
}
if !go_to_definition_updated {
editor.hide_hovered_link(cx)
}
if !hover_updated {
hover_popover::hover_at(editor, None, window, cx);
}
}
pub fn show_link_definition(
shift_held: bool,
editor: &mut Editor,
@@ -730,7 +912,7 @@ mod tests {
DisplayPoint,
display_map::ToDisplayPoint,
editor_tests::init_test,
inlays::inlay_hints::tests::{cached_hint_labels, visible_hint_labels},
inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
test::editor_lsp_test_context::EditorLspTestContext,
};
use futures::StreamExt;
@@ -1161,7 +1343,7 @@ mod tests {
cx.background_executor.run_until_parked();
cx.update_editor(|editor, _window, cx| {
let expected_layers = vec![hint_label.to_string()];
assert_eq!(expected_layers, cached_hint_labels(editor, cx));
assert_eq!(expected_layers, cached_hint_labels(editor));
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
});

View File

@@ -986,17 +986,17 @@ impl DiagnosticPopover {
mod tests {
use super::*;
use crate::{
PointForPosition,
InlayId, PointForPosition,
actions::ConfirmCompletion,
editor_tests::{handle_completion_request, init_test},
inlays::inlay_hints::tests::{cached_hint_labels, visible_hint_labels},
hover_links::update_inlay_link_and_hover_points,
inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
test::editor_lsp_test_context::EditorLspTestContext,
};
use collections::BTreeSet;
use gpui::App;
use indoc::indoc;
use markdown::parser::MarkdownEvent;
use project::InlayId;
use settings::InlayHintSettingsContent;
use smol::stream::StreamExt;
use std::sync::atomic;
@@ -1648,7 +1648,7 @@ mod tests {
cx.background_executor.run_until_parked();
cx.update_editor(|editor, _, cx| {
let expected_layers = vec![entire_hint_label.to_string()];
assert_eq!(expected_layers, cached_hint_labels(editor, cx));
assert_eq!(expected_layers, cached_hint_labels(editor));
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
});
@@ -1687,9 +1687,10 @@ mod tests {
}
});
cx.update_editor(|editor, window, cx| {
editor.update_inlay_link_and_hover_points(
update_inlay_link_and_hover_points(
&editor.snapshot(window, cx),
new_type_hint_part_hover_position,
editor,
true,
false,
window,
@@ -1757,9 +1758,10 @@ mod tests {
cx.background_executor.run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.update_inlay_link_and_hover_points(
update_inlay_link_and_hover_points(
&editor.snapshot(window, cx),
new_type_hint_part_hover_position,
editor,
true,
false,
window,
@@ -1811,9 +1813,10 @@ mod tests {
}
});
cx.update_editor(|editor, window, cx| {
editor.update_inlay_link_and_hover_points(
update_inlay_link_and_hover_points(
&editor.snapshot(window, cx),
struct_hint_part_hover_position,
editor,
true,
false,
window,

View File

@@ -1,193 +0,0 @@
//! The logic, responsible for managing [`Inlay`]s in the editor.
//!
//! Inlays are "not real" text that gets mixed into the "real" buffer's text.
//! They are attached to a certain [`Anchor`], and display certain contents (usually, strings)
//! between real text around that anchor.
//!
//! Inlay examples in Zed:
//! * inlay hints, received from LSP
//! * inline values, shown in the debugger
//! * inline predictions, showing the Zeta/Copilot/etc. predictions
//! * document color values, if configured to be displayed as inlays
//! * ... anything else, potentially.
//!
//! Editor uses [`crate::DisplayMap`] and [`crate::display_map::InlayMap`] to manage what's rendered inside the editor, using
//! [`InlaySplice`] to update this state.
/// Logic, related to managing LSP inlay hint inlays.
pub mod inlay_hints;
use std::{any::TypeId, sync::OnceLock};
use gpui::{Context, HighlightStyle, Hsla, Rgba, Task};
use multi_buffer::Anchor;
use project::{InlayHint, InlayId};
use text::Rope;
use crate::{Editor, hover_links::InlayHighlight};
/// A splice to send into the `inlay_map` for updating the visible inlays on the screen.
/// "Visible" inlays may not be displayed in the buffer right away, but those are ready to be displayed on further buffer scroll, pane item activations, etc. right away without additional LSP queries or settings changes.
/// The data in the cache is never used directly for displaying inlays on the screen, to avoid races with updates from LSP queries and sync overhead.
/// Splice is picked to help avoid extra hint flickering and "jumps" on the screen.
#[derive(Debug, Default)]
pub struct InlaySplice {
pub to_remove: Vec<InlayId>,
pub to_insert: Vec<Inlay>,
}
impl InlaySplice {
pub fn is_empty(&self) -> bool {
self.to_remove.is_empty() && self.to_insert.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct Inlay {
pub id: InlayId,
pub position: Anchor,
pub content: InlayContent,
}
#[derive(Debug, Clone)]
pub enum InlayContent {
Text(text::Rope),
Color(Hsla),
}
impl Inlay {
pub fn hint(id: InlayId, position: Anchor, hint: &InlayHint) -> Self {
let mut text = hint.text();
if hint.padding_right && text.reversed_chars_at(text.len()).next() != Some(' ') {
text.push(" ");
}
if hint.padding_left && text.chars_at(0).next() != Some(' ') {
text.push_front(" ");
}
Self {
id,
position,
content: InlayContent::Text(text),
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn mock_hint(id: usize, position: Anchor, text: impl Into<Rope>) -> Self {
Self {
id: InlayId::Hint(id),
position,
content: InlayContent::Text(text.into()),
}
}
pub fn color(id: usize, position: Anchor, color: Rgba) -> Self {
Self {
id: InlayId::Color(id),
position,
content: InlayContent::Color(color.into()),
}
}
pub fn edit_prediction<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
Self {
id: InlayId::EditPrediction(id),
position,
content: InlayContent::Text(text.into()),
}
}
pub fn debugger<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
Self {
id: InlayId::DebuggerValue(id),
position,
content: InlayContent::Text(text.into()),
}
}
pub fn text(&self) -> &Rope {
static COLOR_TEXT: OnceLock<Rope> = OnceLock::new();
match &self.content {
InlayContent::Text(text) => text,
InlayContent::Color(_) => COLOR_TEXT.get_or_init(|| Rope::from("")),
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn get_color(&self) -> Option<Hsla> {
match self.content {
InlayContent::Color(color) => Some(color),
_ => None,
}
}
}
pub struct InlineValueCache {
pub enabled: bool,
pub inlays: Vec<InlayId>,
pub refresh_task: Task<Option<()>>,
}
impl InlineValueCache {
pub fn new(enabled: bool) -> Self {
Self {
enabled,
inlays: Vec::new(),
refresh_task: Task::ready(None),
}
}
}
impl Editor {
/// Modify which hints are displayed in the editor.
pub fn splice_inlays(
&mut self,
to_remove: &[InlayId],
to_insert: Vec<Inlay>,
cx: &mut Context<Self>,
) {
if let Some(inlay_hints) = &mut self.inlay_hints {
for id_to_remove in to_remove {
inlay_hints.added_hints.remove(id_to_remove);
}
}
self.display_map.update(cx, |display_map, cx| {
display_map.splice_inlays(to_remove, to_insert, cx)
});
cx.notify();
}
pub(crate) fn highlight_inlays<T: 'static>(
&mut self,
highlights: Vec<InlayHighlight>,
style: HighlightStyle,
cx: &mut Context<Self>,
) {
self.display_map.update(cx, |map, _| {
map.highlight_inlays(TypeId::of::<T>(), highlights, style)
});
cx.notify();
}
pub fn inline_values_enabled(&self) -> bool {
self.inline_value_cache.enabled
}
#[cfg(any(test, feature = "test-support"))]
pub fn inline_value_inlays(&self, cx: &gpui::App) -> Vec<Inlay> {
self.display_map
.read(cx)
.current_inlays()
.filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
.cloned()
.collect()
}
#[cfg(any(test, feature = "test-support"))]
pub fn all_inlays(&self, cx: &gpui::App) -> Vec<Inlay> {
self.display_map
.read(cx)
.current_inlays()
.cloned()
.collect()
}
}

View File

@@ -42,7 +42,7 @@ use ui::{IconDecorationKind, prelude::*};
use util::{ResultExt, TryFutureExt, paths::PathExt};
use workspace::{
CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
invalid_item_view::InvalidItemView,
invalid_buffer_view::InvalidBufferView,
item::{FollowableItem, Item, ItemBufferKind, ItemEvent, ProjectItem, SaveOptions},
searchable::{
Direction, FilteredSearchRange, SearchEvent, SearchableItem, SearchableItemHandle,
@@ -226,7 +226,7 @@ impl FollowableItem for Editor {
Some(proto::view::Variant::Editor(proto::view::Editor {
singleton: buffer.is_singleton(),
title: buffer.explicit_title().map(ToOwned::to_owned),
title: (!buffer.is_singleton()).then(|| buffer.title(cx).into()),
excerpts,
scroll_top_anchor: Some(serialize_anchor(&scroll_anchor.anchor, &snapshot)),
scroll_x: scroll_anchor.offset.x,
@@ -1392,8 +1392,8 @@ impl ProjectItem for Editor {
e: &anyhow::Error,
window: &mut Window,
cx: &mut App,
) -> Option<InvalidItemView> {
Some(InvalidItemView::new(abs_path, is_local, e, window, cx))
) -> Option<InvalidBufferView> {
Some(InvalidBufferView::new(abs_path, is_local, e, window, cx))
}
}

View File

@@ -6,15 +6,15 @@ use gpui::{Hsla, Rgba, Task};
use itertools::Itertools;
use language::point_from_lsp;
use multi_buffer::Anchor;
use project::{DocumentColor, InlayId};
use project::DocumentColor;
use settings::Settings as _;
use text::{Bias, BufferId, OffsetRangeExt as _};
use ui::{App, Context, Window};
use util::post_inc;
use crate::{
DisplayPoint, Editor, EditorSettings, EditorSnapshot, FETCH_COLORS_DEBOUNCE_TIMEOUT,
InlaySplice, RangeToAnchorExt, editor_settings::DocumentColorsRenderMode, inlays::Inlay,
DisplayPoint, Editor, EditorSettings, EditorSnapshot, FETCH_COLORS_DEBOUNCE_TIMEOUT, InlayId,
InlaySplice, RangeToAnchorExt, display_map::Inlay, editor_settings::DocumentColorsRenderMode,
};
#[derive(Debug)]
@@ -164,7 +164,7 @@ impl Editor {
}
let visible_buffers = self
.visible_excerpts(cx)
.visible_excerpts(None, cx)
.into_values()
.map(|(buffer, ..)| buffer)
.filter(|editor_buffer| {
@@ -400,7 +400,8 @@ impl Editor {
}
if colors.render_mode == DocumentColorsRenderMode::Inlay
&& !colors_splice.is_empty()
&& (!colors_splice.to_insert.is_empty()
|| !colors_splice.to_remove.is_empty())
{
editor.splice_inlays(&colors_splice.to_remove, colors_splice.to_insert, cx);
updated = true;

View File

@@ -11,7 +11,6 @@ use gpui::{Context, DismissEvent, Entity, Focusable as _, Pixels, Point, Subscri
use std::ops::Range;
use text::PointUtf16;
use workspace::OpenInTerminal;
use zed_actions::agent::AddSelectionToThread;
#[derive(Debug)]
pub enum MenuPosition {
@@ -234,7 +233,6 @@ pub fn deploy_context_menu(
quick_launch: false,
}),
)
.action("Add to Agent Thread", Box::new(AddSelectionToThread))
.separator()
.action("Cut", Box::new(Cut))
.action("Copy", Box::new(Copy))

View File

@@ -872,7 +872,7 @@ mod tests {
use super::*;
use crate::{
Buffer, DisplayMap, DisplayRow, ExcerptRange, FoldPlaceholder, MultiBuffer,
inlays::Inlay,
display_map::Inlay,
test::{editor_test_context::EditorTestContext, marked_display_snapshot},
};
use gpui::{AppContext as _, font, px};

View File

@@ -1,14 +1,14 @@
use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SelectionEffects, SemanticsProvider};
use buffer_diff::BufferDiff;
use collections::{HashMap, HashSet};
use collections::HashSet;
use futures::{channel::mpsc, future::join_all};
use gpui::{App, Entity, EventEmitter, Focusable, Render, Subscription, Task};
use language::{Buffer, BufferEvent, BufferRow, Capability};
use language::{Buffer, BufferEvent, Capability};
use multi_buffer::{ExcerptRange, MultiBuffer};
use project::{InvalidationStrategy, Project, lsp_store::CacheInlayHints};
use project::Project;
use smol::stream::StreamExt;
use std::{any::TypeId, ops::Range, rc::Rc, time::Duration};
use text::{BufferId, ToOffset};
use text::ToOffset;
use ui::{ButtonLike, KeyBinding, prelude::*};
use workspace::{
Item, ItemHandle as _, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
@@ -370,15 +370,17 @@ impl ProposedChangesEditorToolbar {
}
impl Render for ProposedChangesEditorToolbar {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let button_like = ButtonLike::new("apply-changes").child(Label::new("Apply All"));
match &self.current_editor {
Some(editor) => {
let focus_handle = editor.focus_handle(cx);
let keybinding = KeyBinding::for_action_in(&ApplyAllDiffHunks, &focus_handle, cx);
let keybinding =
KeyBinding::for_action_in(&ApplyAllDiffHunks, &focus_handle, window, cx)
.map(|binding| binding.into_any_element());
button_like.child(keybinding).on_click({
button_like.children(keybinding).on_click({
move |_event, window, cx| {
focus_handle.dispatch_action(&ApplyAllDiffHunks, window, cx)
}
@@ -434,34 +436,14 @@ impl SemanticsProvider for BranchBufferSemanticsProvider {
self.0.hover(&buffer, position, cx)
}
fn applicable_inlay_chunks(
&self,
buffer_id: BufferId,
ranges: &[Range<text::Anchor>],
cx: &App,
) -> Vec<Range<BufferRow>> {
self.0.applicable_inlay_chunks(buffer_id, ranges, cx)
}
fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
self.0.invalidate_inlay_hints(for_buffers, cx);
}
fn inlay_hints(
&self,
invalidate: InvalidationStrategy,
buffer: Entity<Buffer>,
ranges: Vec<Range<text::Anchor>>,
known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
range: Range<text::Anchor>,
cx: &mut App,
) -> Option<HashMap<Range<BufferRow>, Task<anyhow::Result<CacheInlayHints>>>> {
let positions = ranges
.iter()
.flat_map(|range| [range.start, range.end])
.collect::<Vec<_>>();
let buffer = self.to_base(&buffer, &positions, cx)?;
self.0
.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
) -> Option<Task<anyhow::Result<Vec<project::InlayHint>>>> {
let buffer = self.to_base(&buffer, &[range.start, range.end], cx)?;
self.0.inlay_hints(buffer, range, cx)
}
fn inline_values(
@@ -473,6 +455,17 @@ impl SemanticsProvider for BranchBufferSemanticsProvider {
None
}
fn resolve_inlay_hint(
&self,
hint: project::InlayHint,
buffer: Entity<Buffer>,
server_id: lsp::LanguageServerId,
cx: &mut App,
) -> Option<Task<anyhow::Result<project::InlayHint>>> {
let buffer = self.to_base(&buffer, &[], cx)?;
self.0.resolve_inlay_hint(hint, buffer, server_id, cx)
}
fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
if let Some(buffer) = self.to_base(buffer, &[], cx) {
self.0.supports_inlay_hints(&buffer, cx)

View File

@@ -396,8 +396,13 @@ impl SignatureHelpPopover {
.shape(IconButtonShape::Square)
.style(ButtonStyle::Subtle)
.icon_size(IconSize::Small)
.tooltip(move |_window, cx| {
ui::Tooltip::for_action("Previous Signature", &crate::SignatureHelpPrevious, cx)
.tooltip(move |window, cx| {
ui::Tooltip::for_action(
"Previous Signature",
&crate::SignatureHelpPrevious,
window,
cx,
)
})
.on_click(cx.listener(|editor, _, window, cx| {
editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
@@ -407,8 +412,8 @@ impl SignatureHelpPopover {
.shape(IconButtonShape::Square)
.style(ButtonStyle::Subtle)
.icon_size(IconSize::Small)
.tooltip(move |_window, cx| {
ui::Tooltip::for_action("Next Signature", &crate::SignatureHelpNext, cx)
.tooltip(move |window, cx| {
ui::Tooltip::for_action("Next Signature", &crate::SignatureHelpNext, window, cx)
})
.on_click(cx.listener(|editor, _, window, cx| {
editor.signature_help_next(&crate::SignatureHelpNext, window, cx);

View File

@@ -6,7 +6,6 @@ use std::{
};
use anyhow::Result;
use language::rust_lang;
use serde_json::json;
use crate::{Editor, ToPoint};
@@ -33,6 +32,55 @@ pub struct EditorLspTestContext {
pub buffer_lsp_url: lsp::Uri,
}
pub(crate) fn rust_lang() -> Arc<Language> {
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_queries(LanguageQueries {
indents: Some(Cow::from(indoc! {r#"
[
((where_clause) _ @end)
(field_expression)
(call_expression)
(assignment_expression)
(let_declaration)
(let_chain)
(await_expression)
] @indent
(_ "[" "]" @end) @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent"#})),
brackets: Some(Cow::from(indoc! {r#"
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)
(closure_parameters "|" @open "|" @close)"#})),
text_objects: Some(Cow::from(indoc! {r#"
(function_item
body: (_
"{"
(_)* @function.inside
"}" )) @function.around
"#})),
..Default::default()
})
.expect("Could not parse queries");
Arc::new(language)
}
#[cfg(test)]
pub(crate) fn git_commit_lang() -> Arc<Language> {
Arc::new(Language::new(

View File

@@ -19,7 +19,7 @@ path = "src/explorer.rs"
[dependencies]
acp_thread.workspace = true
agent = { workspace = true, features = ["eval"] }
agent.workspace = true
agent-client-protocol.workspace = true
agent_settings.workspace = true
agent_ui.workspace = true
@@ -29,6 +29,7 @@ buffer_diff.workspace = true
chrono.workspace = true
clap.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
debug_adapter_extension.workspace = true
dirs.workspace = true
@@ -53,13 +54,13 @@ pretty_assertions.workspace = true
project.workspace = true
prompt_store.workspace = true
regex.workspace = true
rand.workspace = true
release_channel.workspace = true
reqwest_client.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
shellexpand.workspace = true
smol.workspace = true
telemetry.workspace = true
terminal_view.workspace = true
toml.workspace = true

View File

@@ -1,5 +1,6 @@
{
"agent": {
"always_allow_tool_actions": true
"assistant": {
"always_allow_tool_actions": true,
"version": "2"
}
}

View File

@@ -61,22 +61,9 @@ struct Args {
/// Maximum number of examples to run concurrently.
#[arg(long, default_value = "4")]
concurrency: usize,
/// Output current environment variables as JSON to stdout
#[arg(long, hide = true)]
printenv: bool,
}
fn main() {
let args = Args::parse();
// This prevents errors showing up in the logs, because
// project::environment::load_shell_environment() calls
// std::env::current_exe().unwrap() --printenv
if args.printenv {
util::shell_env::print_env();
return;
}
dotenvy::from_filename(CARGO_MANIFEST_DIR.join(".env")).ok();
env_logger::init();
@@ -112,6 +99,7 @@ fn main() {
let zed_commit_sha = commit_sha_for_path(&root_dir);
let zed_branch_name = git_branch_for_path(&root_dir);
let args = Args::parse();
let languages: HashSet<String> = args.languages.into_iter().collect();
let http_client = Arc::new(ReqwestClient::new());
@@ -138,20 +126,19 @@ fn main() {
let mut cumulative_tool_metrics = ToolMetrics::default();
let tasks = LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.providers().iter().map(|p| p.authenticate(cx)).collect::<Vec<_>>()
let agent_model = load_model(&args.model, cx).unwrap();
let judge_model = load_model(&args.judge_model, cx).unwrap();
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.set_default_model(Some(agent_model.clone()), cx);
});
let auth1 = agent_model.provider.authenticate(cx);
let auth2 = judge_model.provider.authenticate(cx);
cx.spawn(async move |cx| {
future::join_all(tasks).await;
let judge_model = cx.update(|cx| {
let agent_model = load_model(&args.model, cx).unwrap();
let judge_model = load_model(&args.judge_model, cx).unwrap();
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.set_default_model(Some(agent_model.clone()), cx);
});
judge_model
})?;
auth1.await?;
auth2.await?;
let mut examples = Vec::new();
@@ -281,6 +268,7 @@ fn main() {
future::join_all((0..args.concurrency).map(|_| {
let app_state = app_state.clone();
let model = agent_model.model.clone();
let judge_model = judge_model.model.clone();
let zed_commit_sha = zed_commit_sha.clone();
let zed_branch_name = zed_branch_name.clone();
@@ -295,7 +283,7 @@ fn main() {
let result = async {
example.setup().await?;
let run_output = cx
.update(|cx| example.run(app_state.clone(), cx))?
.update(|cx| example.run(model.clone(), app_state.clone(), cx))?
.await?;
let judge_output = judge_example(
example.clone(),
@@ -536,6 +524,7 @@ async fn judge_example(
diff_evaluation = judge_output.diff.clone(),
thread_evaluation = judge_output.thread,
tool_metrics = run_output.tool_metrics,
response_count = run_output.response_count,
token_usage = run_output.token_usage,
model = model.telemetry_id(),
model_provider = model.provider_id().to_string(),

View File

@@ -3,7 +3,6 @@ use std::{
fmt::{self, Debug},
sync::{Arc, Mutex},
time::Duration,
u32,
};
use crate::{
@@ -17,10 +16,11 @@ use agent_settings::AgentProfileId;
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use buffer_diff::DiffHunkStatus;
use cloud_llm_client::CompletionIntent;
use collections::HashMap;
use futures::{FutureExt as _, StreamExt, select_biased};
use futures::{FutureExt as _, StreamExt, channel::mpsc, select_biased};
use gpui::{App, AppContext, AsyncApp, Entity};
use language_model::Role;
use language_model::{LanguageModel, Role, StopReason};
use util::rel_path::RelPath;
pub const THREAD_EVENT_TIMEOUT: Duration = Duration::from_secs(60 * 2);
@@ -93,6 +93,7 @@ pub struct ExampleContext {
log_prefix: String,
agent_thread: Entity<agent::Thread>,
app: AsyncApp,
model: Arc<dyn LanguageModel>,
pub assertions: AssertionsReport,
pub tool_metrics: Arc<Mutex<ToolMetrics>>,
}
@@ -102,6 +103,7 @@ impl ExampleContext {
meta: ExampleMetadata,
log_prefix: String,
agent_thread: Entity<Thread>,
model: Arc<dyn LanguageModel>,
app: AsyncApp,
) -> Self {
let assertions = AssertionsReport::new(meta.max_assertions);
@@ -111,11 +113,26 @@ impl ExampleContext {
log_prefix,
agent_thread,
assertions,
model,
app,
tool_metrics: Arc::new(Mutex::new(ToolMetrics::default())),
}
}
pub fn push_user_message(&mut self, text: impl ToString) {
self.app
.update_entity(&self.agent_thread, |thread, cx| {
thread.insert_user_message(
text.to_string(),
ContextLoadResult::default(),
None,
Vec::new(),
cx,
);
})
.unwrap();
}
pub fn assert(&mut self, expected: bool, message: impl ToString) -> Result<()> {
let message = message.to_string();
self.log_assertion(
@@ -187,174 +204,156 @@ impl ExampleContext {
result
}
pub async fn prompt(&mut self, prompt: impl Into<String>) -> Result<Response> {
self.prompt_with_max_turns(prompt, u32::MAX).await
pub async fn run_to_end(&mut self) -> Result<Response> {
self.run_turns(u32::MAX).await
}
pub async fn prompt_with_max_turns(
&mut self,
prompt: impl Into<String>,
max_turns: u32,
) -> Result<Response> {
let content = vec![UserMessageContent::Text(prompt.into())];
self.run_turns(Some(content), max_turns).await
pub async fn run_turn(&mut self) -> Result<Response> {
self.run_turns(1).await
}
pub async fn proceed_with_max_turns(&mut self, max_turns: u32) -> Result<Response> {
self.run_turns(None, max_turns).await
}
pub async fn run_turns(&mut self, iterations: u32) -> Result<Response> {
let (mut tx, mut rx) = mpsc::channel(1);
async fn run_turns(
&mut self,
prompt: Option<Vec<UserMessageContent>>,
max_turns: u32,
) -> Result<Response> {
let tool_metrics = self.tool_metrics.clone();
let log_prefix = self.log_prefix.clone();
let mut remaining_turns = max_turns;
let mut event_stream = self.agent_thread.update(&mut self.app, |thread, cx| {
if let Some(prompt) = prompt {
let id = UserMessageId::new();
thread.send(id, prompt, cx)
} else {
thread.proceed(cx)
}
})??;
let task = self.app.background_spawn(async move {
let mut messages = Vec::new();
let mut tool_uses_by_id = HashMap::default();
while let Some(event) = event_stream.next().await {
match event? {
ThreadEvent::UserMessage(user_message) => {
messages.push(Message {
role: Role::User,
text: user_message.to_markdown(),
tool_use: Vec::new(),
});
let _subscription = self.app.subscribe(
&self.agent_thread,
move |thread, event: &ThreadEvent, cx| match event {
ThreadEvent::ShowError(thread_error) => {
tx.try_send(Err(anyhow!(thread_error.clone()))).ok();
}
ThreadEvent::Stopped(reason) => match reason {
Ok(StopReason::EndTurn) => {
tx.close_channel();
}
ThreadEvent::AgentThinking(text) | ThreadEvent::AgentText(text) => {
if matches!(
messages.last(),
Some(Message {
role: Role::Assistant,
..
})
) {
messages.last_mut().unwrap().text.push_str(&text);
} else {
messages.push(Message {
role: Role::Assistant,
text,
tool_use: Vec::new(),
});
Ok(StopReason::ToolUse) => {
if thread.read(cx).remaining_turns() == 0 {
tx.close_channel();
}
}
ThreadEvent::ToolCall(tool_call) => {
let meta = tool_call.meta.expect("Missing meta field in tool_call");
let tool_name = meta
.get("tool_name")
.expect("Missing tool_name field in meta")
.as_str()
.expect("Unknown tool_name content in meta");
tool_uses_by_id.insert(
tool_call.id,
ToolUse {
name: tool_name.to_string(),
value: tool_call.raw_input.unwrap_or_default(),
},
);
if matches!(
tool_call.status,
acp::ToolCallStatus::Completed | acp::ToolCallStatus::Failed
) {
panic!("Tool call completed without update");
}
Ok(StopReason::MaxTokens) => {
tx.try_send(Err(anyhow!("Exceeded maximum tokens"))).ok();
}
ThreadEvent::ToolCallUpdate(tool_call_update) => {
if let acp_thread::ToolCallUpdate::UpdateFields(update) = tool_call_update {
if let Some(raw_input) = update.fields.raw_input {
if let Some(tool_use) = tool_uses_by_id.get_mut(&update.id) {
tool_use.value = raw_input;
}
}
if matches!(
update.fields.status,
Some(acp::ToolCallStatus::Completed | acp::ToolCallStatus::Failed)
) {
let succeeded =
update.fields.status == Some(acp::ToolCallStatus::Completed);
let tool_use = tool_uses_by_id
.remove(&update.id)
.expect("Unrecognized tool call completed");
let log_message = if succeeded {
format!("✔︎ {}", tool_use.name)
} else {
Ok(StopReason::Refusal) => {
tx.try_send(Err(anyhow!("Model refused to generate content")))
.ok();
}
Err(err) => {
tx.try_send(Err(anyhow!(err.clone()))).ok();
}
},
ThreadEvent::NewRequest
| ThreadEvent::StreamedAssistantText(_, _)
| ThreadEvent::StreamedAssistantThinking(_, _)
| ThreadEvent::UsePendingTools { .. }
| ThreadEvent::CompletionCanceled => {}
ThreadEvent::ToolUseLimitReached => {}
ThreadEvent::ToolFinished {
tool_use_id,
pending_tool_use,
..
} => {
thread.update(cx, |thread, _cx| {
if let Some(tool_use) = pending_tool_use {
let mut tool_metrics = tool_metrics.lock().unwrap();
if let Some(tool_result) = thread.tool_result(tool_use_id) {
let message = if tool_result.is_error {
format!("✖︎ {}", tool_use.name)
};
println!("{log_prefix}{log_message}");
tool_metrics
.lock()
.unwrap()
.insert(tool_use.name.clone().into(), succeeded);
if let Some(message) = messages.last_mut() {
message.tool_use.push(tool_use);
} else {
messages.push(Message {
role: Role::Assistant,
text: "".to_string(),
tool_use: vec![tool_use],
});
}
remaining_turns -= 1;
if remaining_turns == 0 {
return Ok(messages);
}
format!("✔︎ {}", tool_use.name)
};
println!("{log_prefix}{message}");
tool_metrics
.insert(tool_result.tool_name.clone(), !tool_result.is_error);
} else {
let message =
format!("TOOL FINISHED WITHOUT RESULT: {}", tool_use.name);
println!("{log_prefix}{message}");
tool_metrics.insert(tool_use.name.clone(), true);
}
}
}
ThreadEvent::ToolCallAuthorization(_) => panic!(
});
}
ThreadEvent::InvalidToolInput { .. } => {
println!("{log_prefix} invalid tool input");
}
ThreadEvent::MissingToolUse {
tool_use_id: _,
ui_text,
} => {
println!("{log_prefix} {ui_text}");
}
ThreadEvent::ToolConfirmationNeeded => {
panic!(
"{}Bug: Tool confirmation should not be required in eval",
log_prefix
),
ThreadEvent::Retry(status) => {
println!("{log_prefix} Got retry: {status:?}");
);
}
ThreadEvent::StreamedCompletion
| ThreadEvent::MessageAdded(_)
| ThreadEvent::MessageEdited(_)
| ThreadEvent::MessageDeleted(_)
| ThreadEvent::SummaryChanged
| ThreadEvent::SummaryGenerated
| ThreadEvent::ProfileChanged
| ThreadEvent::ReceivedTextChunk
| ThreadEvent::StreamedToolUse { .. }
| ThreadEvent::CheckpointChanged
| ThreadEvent::CancelEditing => {
tx.try_send(Ok(())).ok();
if std::env::var("ZED_EVAL_DEBUG").is_ok() {
println!("{}Event: {:#?}", log_prefix, event);
}
ThreadEvent::Stop(stop_reason) => match stop_reason {
acp::StopReason::EndTurn => {}
acp::StopReason::MaxTokens => {
return Err(anyhow!("Exceeded maximum tokens"));
}
acp::StopReason::MaxTurnRequests => {
return Err(anyhow!("Exceeded maximum turn requests"));
}
acp::StopReason::Refusal => {
return Err(anyhow!("Refusal"));
}
acp::StopReason::Cancelled => return Err(anyhow!("Cancelled")),
},
}
},
);
let model = self.model.clone();
let message_count_before = self.app.update_entity(&self.agent_thread, |thread, cx| {
thread.set_remaining_turns(iterations);
thread.send_to_model(model, CompletionIntent::UserPrompt, None, cx);
thread.messages().len()
})?;
loop {
select_biased! {
result = rx.next() => {
if let Some(result) = result {
result?;
} else {
break;
}
}
_ = self.app.background_executor().timer(THREAD_EVENT_TIMEOUT).fuse() => {
anyhow::bail!("Agentic loop stalled - waited {THREAD_EVENT_TIMEOUT:?} without any events");
}
}
Ok(messages)
});
select_biased! {
result = task.fuse() => {
Ok(Response::new(result?))
}
_ = self.app.background_executor().timer(THREAD_EVENT_TIMEOUT).fuse() => {
anyhow::bail!("Agentic loop stalled - waited {THREAD_EVENT_TIMEOUT:?} without any events");
}
}
let messages = self.app.read_entity(&self.agent_thread, |thread, cx| {
let mut messages = Vec::new();
for message in thread.messages().skip(message_count_before) {
messages.push(Message {
_role: message.role,
text: message.to_message_content(),
tool_use: thread
.tool_uses_for_message(message.id, cx)
.into_iter()
.map(|tool_use| ToolUse {
name: tool_use.name.to_string(),
value: tool_use.input,
})
.collect(),
});
}
messages
})?;
let response = Response::new(messages);
Ok(response)
}
pub fn edits(&self) -> HashMap<Arc<RelPath>, FileEdits> {
@@ -489,7 +488,7 @@ impl Response {
Self { messages }
}
pub fn expect_tool_call(
pub fn expect_tool(
&self,
tool_name: &'static str,
cx: &mut ExampleContext,
@@ -506,7 +505,8 @@ impl Response {
})
}
pub fn tool_calls(&self) -> impl Iterator<Item = &ToolUse> {
#[allow(dead_code)]
pub fn tool_uses(&self) -> impl Iterator<Item = &ToolUse> {
self.messages.iter().flat_map(|msg| &msg.tool_use)
}
@@ -517,7 +517,7 @@ impl Response {
#[derive(Debug)]
pub struct Message {
role: Role,
_role: Role,
text: String,
tool_use: Vec<ToolUse>,
}

View File

@@ -27,12 +27,14 @@ impl Example for AddArgToTraitMethod {
async fn conversation(&self, cx: &mut ExampleContext) -> Result<()> {
const FILENAME: &str = "assistant_tool.rs";
let _ = cx.prompt(format!(
cx.push_user_message(format!(
r#"
Add a `window: Option<gpui::AnyWindowHandle>` argument to the `Tool::run` trait method in {FILENAME},
and update all the implementations of the trait and call sites accordingly.
"#
)).await?;
));
let _ = cx.run_to_end().await?;
// Adds ignored argument to all but `batch_tool`

View File

@@ -29,19 +29,16 @@ impl Example for CodeBlockCitations {
async fn conversation(&self, cx: &mut ExampleContext) -> Result<()> {
const FILENAME: &str = "assistant_tool.rs";
cx.push_user_message(format!(
r#"
Show me the method bodies of all the methods of the `Tool` trait in {FILENAME}.
Please show each method in a separate code snippet.
"#
));
// Verify that the messages all have the correct formatting.
let texts: Vec<String> = cx
.prompt(format!(
r#"
Show me the method bodies of all the methods of the `Tool` trait in {FILENAME}.
Please show each method in a separate code snippet.
"#
))
.await?
.texts()
.collect();
let texts: Vec<String> = cx.run_to_end().await?.texts().collect();
let closing_fence = format!("\n{FENCE}");
for text in texts.iter() {

View File

@@ -22,26 +22,30 @@ impl Example for CommentTranslation {
}
async fn conversation(&self, cx: &mut ExampleContext) -> Result<()> {
let response = cx.prompt(
r#"
Edit the following files and translate all their comments to italian, in this exact order:
cx.push_user_message(r#"
Edit the following files and translate all their comments to italian, in this exact order:
- font-kit/src/family.rs
- font-kit/src/canvas.rs
- font-kit/src/error.rs
"#
).await?;
- font-kit/src/family.rs
- font-kit/src/canvas.rs
- font-kit/src/error.rs
"#);
cx.run_to_end().await?;
let mut create_or_overwrite_count = 0;
for tool_call in response.tool_calls() {
if tool_call.name == "edit_file" {
let input = tool_call.parse_input::<EditFileToolInput>()?;
if !matches!(input.mode, EditFileMode::Edit) {
create_or_overwrite_count += 1;
cx.agent_thread().read_with(cx, |thread, cx| {
for message in thread.messages() {
for tool_use in thread.tool_uses_for_message(message.id, cx) {
if tool_use.name == "edit_file" {
let input: EditFileToolInput = serde_json::from_value(tool_use.input)?;
if !matches!(input.mode, EditFileMode::Edit) {
create_or_overwrite_count += 1;
}
}
}
}
}
anyhow::Ok(())
})??;
cx.assert_eq(create_or_overwrite_count, 0, "no_creation_or_overwrite")?;
Ok(())

View File

@@ -48,8 +48,8 @@ impl Example for FileChangeNotificationExample {
})?;
// Start conversation (specific message is not important)
cx.prompt_with_max_turns("Find all files in this repo", 1)
.await?;
cx.push_user_message("Find all files in this repo");
cx.run_turn().await?;
// Edit the README buffer - the model should get a notification on next turn
buffer.update(cx, |buffer, cx| {
@@ -58,7 +58,7 @@ impl Example for FileChangeNotificationExample {
// Run for some more turns.
// The model shouldn't thank us for letting it know about the file change.
cx.proceed_with_max_turns(3).await?;
cx.run_turns(3).await?;
Ok(())
}

View File

@@ -25,19 +25,18 @@ impl Example for FileSearchExample {
async fn conversation(&self, cx: &mut ExampleContext) -> Result<()> {
const FILENAME: &str = "find_replace_file_tool.rs";
let prompt = format!(
r#"
cx.push_user_message(format!(
r#"
Look at the `{FILENAME}`. I want to implement a card for it. The card should implement the `Render` trait.
The card should show a diff. It should be a beautifully presented diff. The card "box" should look like what we show for
markdown codeblocks (look at `MarkdownElement`). I want to see a red background for lines that were deleted and a green
background for lines that were added. We should have a div per diff line.
"#
);
));
let response = cx.prompt_with_max_turns(prompt, 1).await?;
let tool_use = response.expect_tool_call("find_path", cx)?;
let response = cx.run_turn().await?;
let tool_use = response.expect_tool("find_path", cx)?;
let input = tool_use.parse_input::<FindPathToolInput>()?;
let glob = input.glob;

View File

@@ -1,4 +1,3 @@
use agent::GrepToolInput;
use agent_settings::AgentProfileId;
use anyhow::Result;
use async_trait::async_trait;
@@ -36,9 +35,9 @@ impl Example for GrepParamsEscapementExample {
}
async fn conversation(&self, cx: &mut ExampleContext) -> Result<()> {
let response = cx
.prompt_with_max_turns("Search for files containing the characters `>` or `<`", 2)
.await?;
// cx.push_user_message("How does the precedence/specificity work with Keymap contexts? I am seeing that `MessageEditor > Editor` is lower precendence than `Editor` which is surprising to me, but might be how it works");
cx.push_user_message("Search for files containing the characters `>` or `<`");
let response = cx.run_turns(2).await?;
let grep_input = response
.find_tool_call("grep")
.and_then(|tool_use| tool_use.parse_input::<GrepToolInput>().ok());

View File

@@ -144,8 +144,9 @@ impl Example for DeclarativeExample {
}
async fn conversation(&self, cx: &mut ExampleContext) -> Result<()> {
cx.push_user_message(&self.prompt);
let max_turns = self.metadata.max_turns.unwrap_or(1000);
let _ = cx.prompt_with_max_turns(&self.prompt, max_turns).await;
let _ = cx.run_turns(max_turns).await;
Ok(())
}

View File

@@ -1,4 +1,3 @@
use agent::{EditFileMode, EditFileToolInput};
use agent_settings::AgentProfileId;
use anyhow::Result;
use async_trait::async_trait;
@@ -36,14 +35,17 @@ impl Example for FileOverwriteExample {
}
async fn conversation(&self, cx: &mut ExampleContext) -> Result<()> {
let response = cx.proceed_with_max_turns(1).await?;
let tool_use = response.expect_tool_call("edit_file", cx)?;
let input = tool_use.parse_input::<EditFileToolInput>()?;
let file_overwritten = match input.mode {
EditFileMode::Edit => false,
EditFileMode::Create | EditFileMode::Overwrite => {
input.path.ends_with("src/language_model_selector.rs")
let response = cx.run_turns(1).await?;
let file_overwritten = if let Some(tool_use) = response.find_tool_call("edit_file") {
let input = tool_use.parse_input::<EditFileToolInput>()?;
match input.mode {
EditFileMode::Edit => false,
EditFileMode::Create | EditFileMode::Overwrite => {
input.path.ends_with("src/language_model_selector.rs")
}
}
} else {
false
};
cx.assert(!file_overwritten, "File should be edited, not overwritten")

View File

@@ -23,19 +23,20 @@ impl Example for Planets {
}
async fn conversation(&self, cx: &mut ExampleContext) -> Result<()> {
let response = cx
.prompt(
r#"
cx.push_user_message(
r#"
Make a plain JavaScript web page which renders an animated 3D solar system.
Let me drag to rotate the camera around.
Do not use npm.
"#,
)
.await?;
"#
.to_string(),
);
let response = cx.run_to_end().await?;
let mut open_tool_uses = 0;
let mut terminal_tool_uses = 0;
for tool_use in response.tool_calls() {
for tool_use in response.tool_uses() {
if tool_use.name == OpenTool::name() {
open_tool_uses += 1;
} else if tool_use.name == TerminalTool::name() {

View File

@@ -1,38 +1,36 @@
use agent::ContextServerRegistry;
use agent_client_protocol as acp;
use agent::Message;
use anyhow::{Context as _, Result, anyhow, bail};
use client::proto::LspWorkProgress;
use futures::channel::mpsc;
use futures::future::Shared;
use futures::{FutureExt as _, StreamExt as _, future};
use gpui::{App, AppContext as _, AsyncApp, Entity, Task};
use handlebars::Handlebars;
use language::{Buffer, DiagnosticSeverity, OffsetRangeExt as _};
use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelToolResultContent, MessageContent, Role, TokenUsage,
LanguageModel, LanguageModelCompletionEvent, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelToolResultContent, MessageContent, Role, TokenUsage,
};
use project::{DiagnosticSummary, Project, ProjectPath, lsp_store::OpenLspBufferHandle};
use prompt_store::{ProjectContext, WorktreeContext};
use rand::{distr, prelude::*};
use project::lsp_store::OpenLspBufferHandle;
use project::{DiagnosticSummary, Project, ProjectPath};
use serde::{Deserialize, Serialize};
use std::{
fmt::Write as _,
fs::{self, File},
io::Write as _,
path::{Path, PathBuf},
rc::Rc,
sync::{Arc, Mutex},
time::Duration,
};
use std::cell::RefCell;
use std::fmt::Write as _;
use std::fs;
use std::fs::File;
use std::io::Write as _;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration;
use unindent::Unindent as _;
use util::{ResultExt as _, command::new_smol_command, markdown::MarkdownCodeBlock};
use util::ResultExt as _;
use util::command::new_smol_command;
use util::markdown::MarkdownCodeBlock;
use crate::{
AgentAppState, ToolMetrics,
assertions::{AssertionsReport, RanAssertion, RanAssertionResult},
example::{Example, ExampleContext, FailedAssertion, JudgeAssertion},
};
use crate::assertions::{AssertionsReport, RanAssertion, RanAssertionResult};
use crate::example::{Example, ExampleContext, FailedAssertion, JudgeAssertion};
use crate::{AgentAppState, ToolMetrics};
pub const ZED_REPO_URL: &str = "https://github.com/zed-industries/zed.git";
@@ -58,9 +56,10 @@ pub struct RunOutput {
pub diagnostic_summary_after: DiagnosticSummary,
pub diagnostics_before: Option<String>,
pub diagnostics_after: Option<String>,
pub response_count: usize,
pub token_usage: TokenUsage,
pub tool_metrics: ToolMetrics,
pub thread_markdown: String,
pub all_messages: String,
pub programmatic_assertions: AssertionsReport,
}
@@ -194,7 +193,12 @@ impl ExampleInstance {
.join(self.thread.meta().repo_name())
}
pub fn run(&self, app_state: Arc<AgentAppState>, cx: &mut App) -> Task<Result<RunOutput>> {
pub fn run(
&self,
model: Arc<dyn LanguageModel>,
app_state: Arc<AgentAppState>,
cx: &mut App,
) -> Task<Result<RunOutput>> {
let project = Project::local(
app_state.client.clone(),
app_state.node_runtime.clone(),
@@ -209,6 +213,15 @@ impl ExampleInstance {
project.create_worktree(self.worktree_path(), true, cx)
});
let tools = cx.new(|_| ToolWorkingSet::default());
let prompt_store = None;
let thread_store = ThreadStore::load(
project.clone(),
tools,
prompt_store,
app_state.prompt_builder.clone(),
cx,
);
let meta = self.thread.meta();
let this = self.clone();
@@ -287,62 +300,74 @@ impl ExampleInstance {
// history using undo/redo.
std::fs::write(&last_diff_file_path, "")?;
let thread = cx.update(|cx| {
//todo: Do we want to load rules files here?
let worktrees = project.read(cx).visible_worktrees(cx).map(|worktree| {
let root_name = worktree.read(cx).root_name_str().into();
let abs_path = worktree.read(cx).abs_path();
let thread_store = thread_store.await?;
WorktreeContext {
root_name,
abs_path,
rules_file: None,
let thread =
thread_store.update(cx, |thread_store, cx| {
let thread = if let Some(json) = &meta.existing_thread_json {
let serialized = SerializedThread::from_json(json.as_bytes()).expect("Can't read serialized thread");
thread_store.create_thread_from_serialized(serialized, cx)
} else {
thread_store.create_thread(cx)
};
thread.update(cx, |thread, cx| {
thread.set_profile(meta.profile_id.clone(), cx);
});
thread
})?;
thread.update(cx, |thread, _cx| {
let mut request_count = 0;
let previous_diff = Rc::new(RefCell::new("".to_string()));
let example_output_dir = this.run_directory.clone();
let last_diff_file_path = last_diff_file_path.clone();
let messages_json_file_path = example_output_dir.join("last.messages.json");
let this = this.clone();
thread.set_request_callback(move |request, response_events| {
request_count += 1;
let messages_file_path = example_output_dir.join(format!("{request_count}.messages.md"));
let diff_file_path = example_output_dir.join(format!("{request_count}.diff"));
let last_messages_file_path = example_output_dir.join("last.messages.md");
let request_markdown = RequestMarkdown::new(request);
let response_events_markdown = response_events_to_markdown(response_events);
let dialog = ThreadDialog::new(request, response_events);
let dialog_json = serde_json::to_string_pretty(&dialog.to_combined_request()).unwrap_or_default();
let messages = format!("{}\n\n{}", request_markdown.messages, response_events_markdown);
fs::write(&messages_file_path, messages.clone()).expect("failed to write messages file");
fs::write(&last_messages_file_path, messages).expect("failed to write last messages file");
fs::write(&messages_json_file_path, dialog_json).expect("failed to write last.messages.json");
let diff_result = smol::block_on(this.repository_diff());
match diff_result {
Ok(diff) => {
if diff != previous_diff.borrow().clone() {
fs::write(&diff_file_path, &diff).expect("failed to write diff file");
fs::write(&last_diff_file_path, &diff).expect("failed to write last diff file");
*previous_diff.borrow_mut() = diff;
}
}
Err(err) => {
let error_message = format!("{err:?}");
fs::write(&diff_file_path, &error_message).expect("failed to write diff error to file");
fs::write(&last_diff_file_path, &error_message).expect("failed to write last diff file");
}
}
}).collect::<Vec<_>>();
let project_context = cx.new(|_cx| ProjectContext::new(worktrees, vec![]));
let context_server_registry = cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let thread = if let Some(json) = &meta.existing_thread_json {
let session_id = acp::SessionId(
rand::rng()
.sample_iter(&distr::Alphanumeric)
.take(7)
.map(char::from)
.collect::<String>()
.into(),
);
let db_thread = agent::DbThread::from_json(json.as_bytes()).expect("Can't read serialized thread");
cx.new(|cx| agent::Thread::from_db(session_id, db_thread, project.clone(), project_context, context_server_registry, agent::Templates::new(), cx))
} else {
cx.new(|cx| agent::Thread::new(project.clone(), project_context, context_server_registry, agent::Templates::new(), None, cx))
};
thread.update(cx, |thread, cx| {
thread.add_default_tools(Rc::new(EvalThreadEnvironment {
project: project.clone(),
}), cx);
thread.set_profile(meta.profile_id.clone());
thread.set_model(
LanguageModelInterceptor::new(
LanguageModelRegistry::read_global(cx).default_model().expect("Missing model").model.clone(),
this.run_directory.clone(),
last_diff_file_path.clone(),
this.run_directory.join("last.messages.json"),
this.worktree_path(),
this.repo_url(),
),
cx,
);
if request_count == 1 {
let tools_file_path = example_output_dir.join("tools.md");
fs::write(tools_file_path, request_markdown.tools).expect("failed to write tools file");
}
});
thread
}).unwrap();
})?;
let mut example_cx = ExampleContext::new(
meta.clone(),
this.log_prefix.clone(),
thread.clone(),
model.clone(),
cx.clone(),
);
let result = this.thread.conversation(&mut example_cx).await;
@@ -355,7 +380,7 @@ impl ExampleInstance {
println!("{}Stopped", this.log_prefix);
println!("{}Getting repository diff", this.log_prefix);
let repository_diff = Self::repository_diff(this.worktree_path(), &this.repo_url()).await?;
let repository_diff = this.repository_diff().await?;
std::fs::write(last_diff_file_path, &repository_diff)?;
@@ -390,28 +415,34 @@ impl ExampleInstance {
}
thread.update(cx, |thread, _cx| {
let response_count = thread
.messages()
.filter(|message| message.role == language_model::Role::Assistant)
.count();
RunOutput {
repository_diff,
diagnostic_summary_before,
diagnostic_summary_after,
diagnostics_before,
diagnostics_after,
token_usage: thread.latest_request_token_usage().unwrap(),
response_count,
token_usage: thread.cumulative_token_usage(),
tool_metrics: example_cx.tool_metrics.lock().unwrap().clone(),
thread_markdown: thread.to_markdown(),
all_messages: messages_to_markdown(thread.messages()),
programmatic_assertions: example_cx.assertions,
}
})
})
}
async fn repository_diff(repository_path: PathBuf, repository_url: &str) -> Result<String> {
run_git(&repository_path, &["add", "."]).await?;
async fn repository_diff(&self) -> Result<String> {
let worktree_path = self.worktree_path();
run_git(&worktree_path, &["add", "."]).await?;
let mut diff_args = vec!["diff", "--staged"];
if repository_url == ZED_REPO_URL {
if self.thread.meta().url == ZED_REPO_URL {
diff_args.push(":(exclude).rules");
}
run_git(&repository_path, &diff_args).await
run_git(&worktree_path, &diff_args).await
}
pub async fn judge(
@@ -511,7 +542,7 @@ impl ExampleInstance {
hbs.register_template_string(judge_thread_prompt_name, judge_thread_prompt)
.unwrap();
let complete_messages = &run_output.thread_markdown;
let complete_messages = &run_output.all_messages;
let to_prompt = |assertion: String| {
hbs.render(
judge_thread_prompt_name,
@@ -603,273 +634,6 @@ impl ExampleInstance {
}
}
struct EvalThreadEnvironment {
project: Entity<Project>,
}
struct EvalTerminalHandle {
terminal: Entity<acp_thread::Terminal>,
}
impl agent::TerminalHandle for EvalTerminalHandle {
fn id(&self, cx: &AsyncApp) -> Result<acp::TerminalId> {
self.terminal.read_with(cx, |term, _cx| term.id().clone())
}
fn wait_for_exit(&self, cx: &AsyncApp) -> Result<Shared<Task<acp::TerminalExitStatus>>> {
self.terminal
.read_with(cx, |term, _cx| term.wait_for_exit())
}
fn current_output(&self, cx: &AsyncApp) -> Result<acp::TerminalOutputResponse> {
self.terminal
.read_with(cx, |term, cx| term.current_output(cx))
}
}
impl agent::ThreadEnvironment for EvalThreadEnvironment {
fn create_terminal(
&self,
command: String,
cwd: Option<PathBuf>,
output_byte_limit: Option<u64>,
cx: &mut AsyncApp,
) -> Task<Result<Rc<dyn agent::TerminalHandle>>> {
let project = self.project.clone();
cx.spawn(async move |cx| {
let language_registry =
project.read_with(cx, |project, _cx| project.languages().clone())?;
let id = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
let terminal =
acp_thread::create_terminal_entity(command, &[], vec![], cwd.clone(), &project, cx)
.await?;
let terminal = cx.new(|cx| {
acp_thread::Terminal::new(
id,
"",
cwd,
output_byte_limit.map(|limit| limit as usize),
terminal,
language_registry,
cx,
)
})?;
Ok(Rc::new(EvalTerminalHandle { terminal }) as Rc<dyn agent::TerminalHandle>)
})
}
}
struct LanguageModelInterceptor {
model: Arc<dyn LanguageModel>,
request_count: Arc<Mutex<usize>>,
previous_diff: Arc<Mutex<String>>,
example_output_dir: PathBuf,
last_diff_file_path: PathBuf,
messages_json_file_path: PathBuf,
repository_path: PathBuf,
repository_url: String,
}
impl LanguageModelInterceptor {
fn new(
model: Arc<dyn LanguageModel>,
example_output_dir: PathBuf,
last_diff_file_path: PathBuf,
messages_json_file_path: PathBuf,
repository_path: PathBuf,
repository_url: String,
) -> Arc<Self> {
Arc::new(Self {
model,
request_count: Arc::new(Mutex::new(0)),
previous_diff: Arc::new(Mutex::new("".to_string())),
example_output_dir,
last_diff_file_path,
messages_json_file_path,
repository_path,
repository_url,
})
}
}
impl language_model::LanguageModel for LanguageModelInterceptor {
fn id(&self) -> language_model::LanguageModelId {
self.model.id()
}
fn name(&self) -> language_model::LanguageModelName {
self.model.name()
}
fn provider_id(&self) -> language_model::LanguageModelProviderId {
self.model.provider_id()
}
fn provider_name(&self) -> language_model::LanguageModelProviderName {
self.model.provider_name()
}
fn telemetry_id(&self) -> String {
self.model.telemetry_id()
}
fn supports_images(&self) -> bool {
self.model.supports_images()
}
fn supports_tools(&self) -> bool {
self.model.supports_tools()
}
fn supports_tool_choice(&self, choice: language_model::LanguageModelToolChoice) -> bool {
self.model.supports_tool_choice(choice)
}
fn max_token_count(&self) -> u64 {
self.model.max_token_count()
}
fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &App,
) -> future::BoxFuture<'static, Result<u64>> {
self.model.count_tokens(request, cx)
}
fn stream_completion(
&self,
request: LanguageModelRequest,
cx: &AsyncApp,
) -> future::BoxFuture<
'static,
Result<
futures::stream::BoxStream<
'static,
Result<LanguageModelCompletionEvent, language_model::LanguageModelCompletionError>,
>,
language_model::LanguageModelCompletionError,
>,
> {
let stream = self.model.stream_completion(request.clone(), cx);
let request_count = self.request_count.clone();
let previous_diff = self.previous_diff.clone();
let example_output_dir = self.example_output_dir.clone();
let last_diff_file_path = self.last_diff_file_path.clone();
let messages_json_file_path = self.messages_json_file_path.clone();
let repository_path = self.repository_path.clone();
let repository_url = self.repository_url.clone();
Box::pin(async move {
let stream = stream.await?;
let response_events = Arc::new(Mutex::new(Vec::new()));
let request_clone = request.clone();
let wrapped_stream = stream.then(move |event| {
let response_events = response_events.clone();
let request = request_clone.clone();
let request_count = request_count.clone();
let previous_diff = previous_diff.clone();
let example_output_dir = example_output_dir.clone();
let last_diff_file_path = last_diff_file_path.clone();
let messages_json_file_path = messages_json_file_path.clone();
let repository_path = repository_path.clone();
let repository_url = repository_url.clone();
async move {
let event_result = match &event {
Ok(ev) => Ok(ev.clone()),
Err(err) => Err(err.to_string()),
};
response_events.lock().unwrap().push(event_result);
let should_execute = matches!(
&event,
Ok(LanguageModelCompletionEvent::Stop { .. }) | Err(_)
);
if should_execute {
let current_request_count = {
let mut count = request_count.lock().unwrap();
*count += 1;
*count
};
let messages_file_path =
example_output_dir.join(format!("{current_request_count}.messages.md"));
let diff_file_path =
example_output_dir.join(format!("{current_request_count}.diff"));
let last_messages_file_path = example_output_dir.join("last.messages.md");
let collected_events = response_events.lock().unwrap().clone();
let request_markdown = RequestMarkdown::new(&request);
let response_events_markdown =
response_events_to_markdown(&collected_events);
let dialog = ThreadDialog::new(&request, &collected_events);
let dialog_json =
serde_json::to_string_pretty(&dialog.to_combined_request())
.unwrap_or_default();
let messages = format!(
"{}\n\n{}",
request_markdown.messages, response_events_markdown
);
fs::write(&messages_file_path, messages.clone())
.expect("failed to write messages file");
fs::write(&last_messages_file_path, messages)
.expect("failed to write last messages file");
fs::write(&messages_json_file_path, dialog_json)
.expect("failed to write last.messages.json");
// Get repository diff
let diff_result =
ExampleInstance::repository_diff(repository_path, &repository_url)
.await;
match diff_result {
Ok(diff) => {
let prev_diff = previous_diff.lock().unwrap().clone();
if diff != prev_diff {
fs::write(&diff_file_path, &diff)
.expect("failed to write diff file");
fs::write(&last_diff_file_path, &diff)
.expect("failed to write last diff file");
*previous_diff.lock().unwrap() = diff;
}
}
Err(err) => {
let error_message = format!("{err:?}");
fs::write(&diff_file_path, &error_message)
.expect("failed to write diff error to file");
fs::write(&last_diff_file_path, &error_message)
.expect("failed to write last diff file");
}
}
if current_request_count == 1 {
let tools_file_path = example_output_dir.join("tools.md");
fs::write(tools_file_path, request_markdown.tools)
.expect("failed to write tools file");
}
}
event
}
});
Ok(Box::pin(wrapped_stream)
as futures::stream::BoxStream<
'static,
Result<
LanguageModelCompletionEvent,
language_model::LanguageModelCompletionError,
>,
>)
})
}
}
pub fn wait_for_lang_server(
project: &Entity<Project>,
buffer: &Entity<Buffer>,
@@ -1061,6 +825,40 @@ pub async fn run_git(repo_path: &Path, args: &[&str]) -> Result<String> {
Ok(String::from_utf8(output.stdout)?.trim().to_string())
}
fn messages_to_markdown<'a>(message_iter: impl IntoIterator<Item = &'a Message>) -> String {
let mut messages = String::new();
let mut assistant_message_number: u32 = 1;
for message in message_iter {
push_role(&message.role, &mut messages, &mut assistant_message_number);
for segment in &message.segments {
match segment {
MessageSegment::Text(text) => {
messages.push_str(text);
messages.push_str("\n\n");
}
MessageSegment::Thinking { text, signature } => {
messages.push_str("**Thinking**:\n\n");
if let Some(sig) = signature {
messages.push_str(&format!("Signature: {}\n\n", sig));
}
messages.push_str(text);
messages.push_str("\n");
}
MessageSegment::RedactedThinking(items) => {
messages.push_str(&format!(
"**Redacted Thinking**: {} item(s)\n\n",
items.len()
));
}
}
}
}
messages
}
fn push_role(role: &Role, buf: &mut String, assistant_message_number: &mut u32) {
match role {
Role::System => buf.push_str("# ⚙️ SYSTEM\n\n"),

View File

@@ -1663,7 +1663,11 @@ impl PickerDelegate for FileFinderDelegate {
)
}
fn render_footer(&self, _: &mut Window, cx: &mut Context<Picker<Self>>) -> Option<AnyElement> {
fn render_footer(
&self,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<AnyElement> {
let focus_handle = self.focus_handle.clone();
Some(
@@ -1692,11 +1696,12 @@ impl PickerDelegate for FileFinderDelegate {
}),
{
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Filter Options",
&ToggleFilterMenu,
&focus_handle,
window,
cx,
)
}
@@ -1746,13 +1751,14 @@ impl PickerDelegate for FileFinderDelegate {
ButtonLike::new("split-trigger")
.child(Label::new("Split…"))
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.child(
.children(
KeyBinding::for_action_in(
&ToggleSplitMenu,
&focus_handle,
window,
cx,
)
.size(rems_from_px(12.)),
.map(|kb| kb.size(rems_from_px(12.))),
),
)
.menu({
@@ -1784,8 +1790,13 @@ impl PickerDelegate for FileFinderDelegate {
.child(
Button::new("open-selection", "Open")
.key_binding(
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.))),
KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(|_, window, cx| {
window.dispatch_action(menu::Confirm.boxed_clone(), cx)

View File

@@ -466,10 +466,11 @@ impl PickerDelegate for BranchListDelegate {
this.delegate.set_selected_index(ix, window, cx);
this.delegate.confirm(true, window, cx);
}))
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
Tooltip::for_action(
format!("Create branch based off default: {default_branch}"),
&menu::SecondaryConfirm,
window,
cx,
)
}),

View File

@@ -327,7 +327,7 @@ impl CommitModal {
.anchor(Corner::TopRight)
}
pub fn render_footer(&self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (
can_commit,
tooltip,
@@ -388,7 +388,7 @@ impl CommitModal {
});
let focus_handle = self.focus_handle(cx);
let close_kb_hint = ui::KeyBinding::for_action(&menu::Cancel, cx).map(|close_kb| {
let close_kb_hint = ui::KeyBinding::for_action(&menu::Cancel, window, cx).map(|close_kb| {
KeybindingHint::new(close_kb, cx.theme().colors().editor_background).suffix("Cancel")
});
@@ -423,7 +423,7 @@ impl CommitModal {
.flex_none()
.px_1()
.gap_4()
.child(close_kb_hint)
.children(close_kb_hint)
.child(SplitButton::new(
ui::ButtonLike::new_rounded_left(ElementId::Name(
format!("split-button-left-{}", commit_label).into(),
@@ -452,7 +452,7 @@ impl CommitModal {
.disabled(!can_commit)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
if can_commit {
Tooltip::with_meta_in(
tooltip,
@@ -467,6 +467,7 @@ impl CommitModal {
if is_signoff_enabled { " --signoff" } else { "" }
),
&focus_handle.clone(),
window,
cx,
)
} else {

View File

@@ -3091,12 +3091,13 @@ impl GitPanel {
IconButton::new("generate-commit-message", IconName::AiEdit)
.shape(ui::IconButtonShape::Square)
.icon_color(Color::Muted)
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
if can_commit {
Tooltip::for_action_in(
"Generate Commit Message",
&git::GenerateCommitMessage,
&editor_focus_handle,
window,
cx,
)
} else {
@@ -3458,11 +3459,12 @@ impl GitPanel {
panel_icon_button("expand-commit-editor", IconName::Maximize)
.icon_size(IconSize::Small)
.size(ui::ButtonSize::Default)
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
Tooltip::for_action_in(
"Open Commit Modal",
&git::ExpandCommitEditor,
&expand_tooltip_focus_handle,
window,
cx,
)
})
@@ -3524,7 +3526,7 @@ impl GitPanel {
.disabled(!can_commit || self.modal_open)
.tooltip({
let handle = commit_tooltip_focus_handle.clone();
move |_window, cx| {
move |window, cx| {
if can_commit {
Tooltip::with_meta_in(
tooltip,
@@ -3535,6 +3537,7 @@ impl GitPanel {
if signoff { " --signoff" } else { "" }
),
&handle.clone(),
window,
cx,
)
} else {
@@ -3637,7 +3640,7 @@ impl GitPanel {
panel_icon_button("undo", IconName::Undo)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
"Uncommit",
Some(&git::Uncommit),
@@ -3646,6 +3649,7 @@ impl GitPanel {
} else {
"git reset HEAD^"
},
window,
cx,
)
})
@@ -4116,13 +4120,13 @@ impl GitPanel {
.ok();
}
})
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
let is_staged = entry_staging.is_fully_staged();
let action = if is_staged { "Unstage" } else { "Stage" };
let tooltip_name = action.to_string();
Tooltip::for_action(tooltip_name, &ToggleStaged, cx)
Tooltip::for_action(tooltip_name, &ToggleStaged, window, cx)
}),
),
)

View File

@@ -435,12 +435,13 @@ mod remote_button {
move |_, window, cx| {
window.dispatch_action(Box::new(git::Fetch), cx);
},
move |_window, cx| {
move |window, cx| {
git_action_tooltip(
"Fetch updates from remote",
&git::Fetch,
"git fetch",
keybinding_target.clone(),
window,
cx,
)
},
@@ -462,12 +463,13 @@ mod remote_button {
move |_, window, cx| {
window.dispatch_action(Box::new(git::Push), cx);
},
move |_window, cx| {
move |window, cx| {
git_action_tooltip(
"Push committed changes to remote",
&git::Push,
"git push",
keybinding_target.clone(),
window,
cx,
)
},
@@ -490,12 +492,13 @@ mod remote_button {
move |_, window, cx| {
window.dispatch_action(Box::new(git::Pull), cx);
},
move |_window, cx| {
move |window, cx| {
git_action_tooltip(
"Pull",
&git::Pull,
"git pull",
keybinding_target.clone(),
window,
cx,
)
},
@@ -516,12 +519,13 @@ mod remote_button {
move |_, window, cx| {
window.dispatch_action(Box::new(git::Push), cx);
},
move |_window, cx| {
move |window, cx| {
git_action_tooltip(
"Publish branch to remote",
&git::Push,
"git push --set-upstream",
keybinding_target.clone(),
window,
cx,
)
},
@@ -542,12 +546,13 @@ mod remote_button {
move |_, window, cx| {
window.dispatch_action(Box::new(git::Push), cx);
},
move |_window, cx| {
move |window, cx| {
git_action_tooltip(
"Re-publish branch to remote",
&git::Push,
"git push --set-upstream",
keybinding_target.clone(),
window,
cx,
)
},
@@ -559,15 +564,16 @@ mod remote_button {
action: &dyn Action,
command: impl Into<SharedString>,
focus_handle: Option<FocusHandle>,
window: &mut Window,
cx: &mut App,
) -> AnyView {
let label = label.into();
let command = command.into();
if let Some(handle) = focus_handle {
Tooltip::with_meta_in(label, Some(action), command, &handle, cx)
Tooltip::with_meta_in(label, Some(action), command, &handle, window, cx)
} else {
Tooltip::with_meta(label, Some(action), command, cx)
Tooltip::with_meta(label, Some(action), command, window, cx)
}
}

View File

@@ -714,7 +714,7 @@ impl Item for ProjectDiff {
}
impl Render for ProjectDiff {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_empty = self.multibuffer.read(cx).is_empty();
div()
@@ -759,6 +759,7 @@ impl Render for ProjectDiff {
.key_binding(KeyBinding::for_action_in(
&CloseActiveItem::default(),
&keybinding_focus_handle,
window,
cx,
))
.on_click(move |_, window, cx| {

View File

@@ -523,7 +523,11 @@ impl PickerDelegate for StashListDelegate {
Some("No stashes found".into())
}
fn render_footer(&self, _: &mut Window, cx: &mut Context<Picker<Self>>) -> Option<AnyElement> {
fn render_footer(
&self,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<AnyElement> {
let focus_handle = self.focus_handle.clone();
Some(
@@ -537,7 +541,7 @@ impl PickerDelegate for StashListDelegate {
.child(
Button::new("apply-stash", "Apply")
.key_binding(
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(|_, window, cx| {
@@ -547,8 +551,13 @@ impl PickerDelegate for StashListDelegate {
.child(
Button::new("pop-stash", "Pop")
.key_binding(
KeyBinding::for_action_in(&menu::SecondaryConfirm, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.))),
KeyBinding::for_action_in(
&menu::SecondaryConfirm,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(|_, window, cx| {
window.dispatch_action(menu::SecondaryConfirm.boxed_clone(), cx)
@@ -560,6 +569,7 @@ impl PickerDelegate for StashListDelegate {
KeyBinding::for_action_in(
&stash_picker::DropStashItem,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),

View File

@@ -238,16 +238,18 @@ impl Render for CursorPosition {
});
}
}))
.tooltip(move |_window, cx| match context.as_ref() {
.tooltip(move |window, cx| match context.as_ref() {
Some(context) => Tooltip::for_action_in(
"Go to Line/Column",
&editor::actions::ToggleGoToLine,
context,
window,
cx,
),
None => Tooltip::for_action(
"Go to Line/Column",
&editor::actions::ToggleGoToLine,
window,
cx,
),
}),

View File

@@ -1,214 +0,0 @@
use gpui::{
App, Application, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString,
Stateful, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
};
actions!(example, [Tab, TabPrev, Quit]);
struct Example {
focus_handle: FocusHandle,
items: Vec<(FocusHandle, &'static str)>,
message: SharedString,
}
impl Example {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let items = vec![
(
cx.focus_handle().tab_index(1).tab_stop(true),
"Button with .focus() - always shows border when focused",
),
(
cx.focus_handle().tab_index(2).tab_stop(true),
"Button with .focus_visible() - only shows border with keyboard",
),
(
cx.focus_handle().tab_index(3).tab_stop(true),
"Button with both .focus() and .focus_visible()",
),
];
let focus_handle = cx.focus_handle();
window.focus(&focus_handle);
Self {
focus_handle,
items,
message: SharedString::from(
"Try clicking vs tabbing! Click shows no border, Tab shows border.",
),
}
}
fn on_tab(&mut self, _: &Tab, window: &mut Window, _: &mut Context<Self>) {
window.focus_next();
self.message = SharedString::from("Pressed Tab - focus-visible border should appear!");
}
fn on_tab_prev(&mut self, _: &TabPrev, window: &mut Window, _: &mut Context<Self>) {
window.focus_prev();
self.message =
SharedString::from("Pressed Shift-Tab - focus-visible border should appear!");
}
fn on_quit(&mut self, _: &Quit, _window: &mut Window, cx: &mut Context<Self>) {
cx.quit();
}
}
impl Render for Example {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn button_base(id: impl Into<ElementId>, label: &'static str) -> Stateful<Div> {
div()
.id(id)
.h_16()
.w_full()
.flex()
.justify_center()
.items_center()
.bg(gpui::rgb(0x2563eb))
.text_color(gpui::white())
.rounded_md()
.cursor_pointer()
.hover(|style| style.bg(gpui::rgb(0x1d4ed8)))
.child(label)
}
div()
.id("app")
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::on_tab))
.on_action(cx.listener(Self::on_tab_prev))
.on_action(cx.listener(Self::on_quit))
.size_full()
.flex()
.flex_col()
.p_8()
.gap_6()
.bg(gpui::rgb(0xf3f4f6))
.child(
div()
.text_2xl()
.font_weight(gpui::FontWeight::BOLD)
.text_color(gpui::rgb(0x111827))
.child("CSS focus-visible Demo"),
)
.child(
div()
.p_4()
.rounded_md()
.bg(gpui::rgb(0xdbeafe))
.text_color(gpui::rgb(0x1e3a8a))
.child(self.message.clone()),
)
.child(
div()
.flex()
.flex_col()
.gap_4()
.child(
div()
.flex()
.flex_col()
.gap_2()
.child(
div()
.text_sm()
.font_weight(gpui::FontWeight::BOLD)
.text_color(gpui::rgb(0x374151))
.child("1. Regular .focus() - always visible:"),
)
.child(
button_base("button1", self.items[0].1)
.track_focus(&self.items[0].0)
.focus(|style| {
style.border_4().border_color(gpui::rgb(0xfbbf24))
})
.on_click(cx.listener(|this, _, _, cx| {
this.message =
"Clicked button 1 - focus border is visible!".into();
cx.notify();
})),
),
)
.child(
div()
.flex()
.flex_col()
.gap_2()
.child(
div()
.text_sm()
.font_weight(gpui::FontWeight::BOLD)
.text_color(gpui::rgb(0x374151))
.child("2. New .focus_visible() - only keyboard:"),
)
.child(
button_base("button2", self.items[1].1)
.track_focus(&self.items[1].0)
.focus_visible(|style| {
style.border_4().border_color(gpui::rgb(0x10b981))
})
.on_click(cx.listener(|this, _, _, cx| {
this.message =
"Clicked button 2 - no border! Try Tab instead.".into();
cx.notify();
})),
),
)
.child(
div()
.flex()
.flex_col()
.gap_2()
.child(
div()
.text_sm()
.font_weight(gpui::FontWeight::BOLD)
.text_color(gpui::rgb(0x374151))
.child(
"3. Both .focus() (yellow) and .focus_visible() (green):",
),
)
.child(
button_base("button3", self.items[2].1)
.track_focus(&self.items[2].0)
.focus(|style| {
style.border_4().border_color(gpui::rgb(0xfbbf24))
})
.focus_visible(|style| {
style.border_4().border_color(gpui::rgb(0x10b981))
})
.on_click(cx.listener(|this, _, _, cx| {
this.message =
"Clicked button 3 - yellow border. Tab shows green!"
.into();
cx.notify();
})),
),
),
)
}
}
fn main() {
Application::new().run(|cx: &mut App| {
cx.bind_keys([
KeyBinding::new("tab", Tab, None),
KeyBinding::new("shift-tab", TabPrev, None),
KeyBinding::new("cmd-q", Quit, None),
]);
let bounds = Bounds::centered(None, size(px(800.), px(600.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|window, cx| cx.new(|cx| Example::new(window, cx)),
)
.unwrap();
cx.activate(true);
});
}

View File

@@ -10,7 +10,6 @@ use std::{
borrow::{Borrow, BorrowMut},
future::Future,
ops,
rc::Rc,
sync::Arc,
};
use util::Deferred;
@@ -95,79 +94,6 @@ impl<'a, T: 'static> Context<'a, T> {
})
}
/// Subscribe to an event type of another entity whenever a new entity of that type is created
pub fn subscribe_in_new<T2, Evt>(
&mut self,
mut on_event: impl Fn(&mut T, &Entity<T2>, &Evt, &mut Window, &mut Context<T>) + 'static,
) -> Subscription
where
T: 'static,
T2: 'static + EventEmitter<Evt>,
Evt: 'static,
{
let this = self.weak_entity();
let on_event = Rc::new(on_event);
self.app.observe_new::<T2>(move |_, window, cx| {
let handle = window.map(|window| window.handle);
cx.app
.subscribe_internal(&cx.entity(), {
let this = this.clone();
let on_event = on_event.clone();
move |e, event, cx| {
if let Some(this) = this.upgrade()
&& let Some(window) = handle
{
window
.update(cx, |_, window, cx| {
this.update(cx, |this, cx| {
on_event(this, &e, event, window, cx)
});
})
.is_ok()
} else {
false
}
}
})
.detach();
})
}
/// Subscribe to an event type of another entity whenever a new entity of that type is created
pub fn subscribe_new<T2, Evt>(
&mut self,
mut on_event: impl Fn(&mut T, Entity<T2>, &Evt, &mut Context<T>) + 'static,
) -> Subscription
where
T: 'static,
T2: 'static + EventEmitter<Evt>,
Evt: 'static,
{
let this = self.weak_entity();
let on_event = Rc::new(on_event);
self.app.observe_new::<T2>(move |_, _, cx| {
cx.app
.subscribe_internal(&cx.entity(), {
let this = this.clone();
let on_event = on_event.clone();
move |e, event, cx| {
if let Some(this) = this.upgrade() {
this.update(cx, |this, cx| on_event(this, e, event, cx));
true
} else {
false
}
}
})
.detach();
})
}
/// Subscribe to an event type from another entity
pub fn subscribe<T2, Evt>(
&mut self,
@@ -327,11 +253,9 @@ impl<'a, T: 'static> Context<'a, T> {
&self,
f: impl Fn(&mut T, &E, &mut Window, &mut Context<T>) + 'static,
) -> impl Fn(&E, &mut Window, &mut App) + 'static {
let entity = self.entity().downgrade();
let view = self.entity().downgrade();
move |e: &E, window: &mut Window, cx: &mut App| {
entity
.update(cx, |entity, cx| f(entity, e, window, cx))
.ok();
view.update(cx, |view, cx| f(view, e, window, cx)).ok();
}
}
@@ -341,9 +265,9 @@ impl<'a, T: 'static> Context<'a, T> {
&self,
f: impl Fn(&mut T, E, &mut Window, &mut Context<T>) -> R + 'static,
) -> impl Fn(E, &mut Window, &mut App) -> R + 'static {
let entity = self.entity();
let view = self.entity();
move |e: E, window: &mut Window, cx: &mut App| {
entity.update(cx, |entity, cx| f(entity, e, window, cx))
view.update(cx, |view, cx| f(view, e, window, cx))
}
}
@@ -372,9 +296,8 @@ impl<'a, T: 'static> Context<'a, T> {
) where
T: 'static,
{
let entity = self.entity();
window
.on_next_frame(move |window, cx| entity.update(cx, |entity, cx| f(entity, window, cx)));
let view = self.entity();
window.on_next_frame(move |window, cx| view.update(cx, |view, cx| f(view, window, cx)));
}
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
@@ -384,9 +307,9 @@ impl<'a, T: 'static> Context<'a, T> {
window: &Window,
f: impl FnOnce(&mut T, &mut Window, &mut Context<T>) + 'static,
) {
let entity = self.entity();
let view = self.entity();
window.defer(self, move |window, cx| {
entity.update(cx, |entity, cx| f(entity, window, cx))
view.update(cx, |view, cx| f(view, window, cx))
});
}

View File

@@ -741,17 +741,7 @@ impl Element for Empty {
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
(
window.request_layout(
Style {
display: crate::Display::None,
..Default::default()
},
None,
cx,
),
(),
)
(window.request_layout(Style::default(), None, cx), ())
}
fn prepaint(

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