Compare commits
15 Commits
revert-377
...
debug-view
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf3c5705e7 | ||
|
|
989ff500d9 | ||
|
|
a5ad9a9615 | ||
|
|
ee4f8e7579 | ||
|
|
4d9c4e187f | ||
|
|
3944fb6ff7 | ||
|
|
49d9280344 | ||
|
|
1cc74ba885 | ||
|
|
ad8bfbdf56 | ||
|
|
439ab2575f | ||
|
|
a4024b495d | ||
|
|
b511aa9274 | ||
|
|
cb0c4bec24 | ||
|
|
d8dd2b2977 | ||
|
|
e19995431b |
@@ -4,8 +4,6 @@ rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"]
|
||||
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
perf-test = ["test", "--profile", "release-fast", "--lib", "--bins", "--tests", "--all-features", "--config", "target.'cfg(true)'.runner='cargo run -p perf --release'", "--config", "target.'cfg(true)'.rustflags=[\"--cfg\", \"perf_enabled\"]"]
|
||||
perf-compare = ["run", "--release", "-p", "perf", "--", "compare"]
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,7 +20,6 @@
|
||||
.venv
|
||||
.vscode
|
||||
.wrangler
|
||||
.perf-runs
|
||||
/assets/*licenses.*
|
||||
/crates/collab/seed.json
|
||||
/crates/theme/schemas/theme.json
|
||||
|
||||
1215
Cargo.lock
generated
1215
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
19
Cargo.toml
19
Cargo.toml
@@ -35,7 +35,6 @@ members = [
|
||||
"crates/cloud_api_client",
|
||||
"crates/cloud_api_types",
|
||||
"crates/cloud_llm_client",
|
||||
"crates/cloud_zeta2_prompt",
|
||||
"crates/collab",
|
||||
"crates/collab_ui",
|
||||
"crates/collections",
|
||||
@@ -90,6 +89,8 @@ members = [
|
||||
"crates/image_viewer",
|
||||
"crates/inspector_ui",
|
||||
"crates/install_cli",
|
||||
"crates/jj",
|
||||
"crates/jj_ui",
|
||||
"crates/journal",
|
||||
"crates/keymap_editor",
|
||||
"crates/language",
|
||||
@@ -149,9 +150,8 @@ members = [
|
||||
"crates/semantic_version",
|
||||
"crates/session",
|
||||
"crates/settings",
|
||||
"crates/settings_macros",
|
||||
"crates/settings_profile_selector",
|
||||
"crates/settings_ui",
|
||||
"crates/settings_ui_macros",
|
||||
"crates/snippet",
|
||||
"crates/snippet_provider",
|
||||
"crates/snippets_ui",
|
||||
@@ -220,7 +220,6 @@ members = [
|
||||
# Tooling
|
||||
#
|
||||
|
||||
"tooling/perf",
|
||||
"tooling/workspace-hack",
|
||||
"tooling/xtask",
|
||||
]
|
||||
@@ -271,7 +270,6 @@ clock = { path = "crates/clock" }
|
||||
cloud_api_client = { path = "crates/cloud_api_client" }
|
||||
cloud_api_types = { path = "crates/cloud_api_types" }
|
||||
cloud_llm_client = { path = "crates/cloud_llm_client" }
|
||||
cloud_zeta2_prompt = { path = "crates/cloud_zeta2_prompt" }
|
||||
collab = { path = "crates/collab" }
|
||||
collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections" }
|
||||
@@ -321,6 +319,8 @@ edit_prediction_context = { path = "crates/edit_prediction_context" }
|
||||
zeta2_tools = { path = "crates/zeta2_tools" }
|
||||
inspector_ui = { path = "crates/inspector_ui" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
jj = { path = "crates/jj" }
|
||||
jj_ui = { path = "crates/jj_ui" }
|
||||
journal = { path = "crates/journal" }
|
||||
keymap_editor = { path = "crates/keymap_editor" }
|
||||
language = { path = "crates/language" }
|
||||
@@ -356,7 +356,6 @@ outline = { path = "crates/outline" }
|
||||
outline_panel = { path = "crates/outline_panel" }
|
||||
panel = { path = "crates/panel" }
|
||||
paths = { path = "crates/paths" }
|
||||
perf = { path = "tooling/perf" }
|
||||
picker = { path = "crates/picker" }
|
||||
plugin = { path = "crates/plugin" }
|
||||
plugin_macros = { path = "crates/plugin_macros" }
|
||||
@@ -385,6 +384,7 @@ semantic_version = { path = "crates/semantic_version" }
|
||||
session = { path = "crates/session" }
|
||||
settings = { path = "crates/settings" }
|
||||
settings_ui = { path = "crates/settings_ui" }
|
||||
settings_ui_macros = { path = "crates/settings_ui_macros" }
|
||||
snippet = { path = "crates/snippet" }
|
||||
snippet_provider = { path = "crates/snippet_provider" }
|
||||
snippets_ui = { path = "crates/snippets_ui" }
|
||||
@@ -440,9 +440,9 @@ zlog_settings = { path = "crates/zlog_settings" }
|
||||
# External crates
|
||||
#
|
||||
|
||||
agent-client-protocol = { version = "0.4.2", features = ["unstable"] }
|
||||
agent-client-protocol = { version = "0.4.0", features = ["unstable"] }
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = "0.25.1-rc1"
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
|
||||
any_vec = "0.14"
|
||||
anyhow = "1.0.86"
|
||||
arrayvec = { version = "0.7.4", features = ["serde"] }
|
||||
@@ -526,6 +526,7 @@ indexmap = { version = "2.7.0", features = ["serde"] }
|
||||
indoc = "2"
|
||||
inventory = "0.3.19"
|
||||
itertools = "0.14.0"
|
||||
jj-lib = { git = "https://github.com/jj-vcs/jj", rev = "e18eb8e05efaa153fad5ef46576af145bba1807f" }
|
||||
json_dotpath = "1.1"
|
||||
jsonschema = "0.30.0"
|
||||
jsonwebtoken = "9.3"
|
||||
@@ -713,7 +714,6 @@ windows-core = "0.61"
|
||||
wit-component = "0.221"
|
||||
workspace-hack = "0.1.0"
|
||||
yawc = "0.2.5"
|
||||
zeroize = "1.8"
|
||||
zstd = "0.11"
|
||||
|
||||
[workspace.dependencies.windows]
|
||||
@@ -740,7 +740,6 @@ features = [
|
||||
"Win32_Networking_WinSock",
|
||||
"Win32_Security",
|
||||
"Win32_Security_Credentials",
|
||||
"Win32_Security_Cryptography",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_Com_StructuredStorage",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.90-bookworm as builder
|
||||
FROM rust:1.89-bookworm as builder
|
||||
WORKDIR app
|
||||
COPY . .
|
||||
|
||||
|
||||
@@ -550,8 +550,6 @@
|
||||
"cmd-ctrl-left": "editor::SelectSmallerSyntaxNode", // Shrink selection
|
||||
"cmd-ctrl-right": "editor::SelectLargerSyntaxNode", // Expand selection
|
||||
"cmd-ctrl-up": "editor::SelectPreviousSyntaxNode", // Move selection up
|
||||
"ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand selection (VSCode version)
|
||||
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink selection (VSCode version)
|
||||
"cmd-ctrl-down": "editor::SelectNextSyntaxNode", // Move selection down
|
||||
"cmd-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand
|
||||
"cmd-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"up": "menu::SelectPrevious",
|
||||
"enter": "menu::Confirm",
|
||||
"ctrl-enter": "menu::SecondaryConfirm",
|
||||
"ctrl-escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"escape": "menu::Cancel",
|
||||
"shift-alt-enter": "menu::Restart",
|
||||
@@ -464,8 +465,8 @@
|
||||
"ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes",
|
||||
"back": "pane::GoBack",
|
||||
"alt--": "pane::GoBack",
|
||||
"forward": "pane::GoForward",
|
||||
"alt-=": "pane::GoForward",
|
||||
"forward": "pane::GoForward",
|
||||
"f3": "search::SelectNextMatch",
|
||||
"shift-f3": "search::SelectPreviousMatch",
|
||||
"ctrl-shift-f": "project_search::ToggleFocus",
|
||||
@@ -496,6 +497,8 @@
|
||||
"shift-alt-down": "editor::DuplicateLineDown",
|
||||
"shift-alt-right": "editor::SelectLargerSyntaxNode", // Expand selection
|
||||
"shift-alt-left": "editor::SelectSmallerSyntaxNode", // Shrink selection
|
||||
"ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand selection (VSCode version)
|
||||
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink selection (VSCode version)
|
||||
"ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
|
||||
"ctrl-f2": "editor::SelectAllMatches", // Select all occurrences of current word
|
||||
"ctrl-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand
|
||||
@@ -1248,8 +1251,8 @@
|
||||
"ctrl-1": "onboarding::ActivateBasicsPage",
|
||||
"ctrl-2": "onboarding::ActivateEditingPage",
|
||||
"ctrl-3": "onboarding::ActivateAISetupPage",
|
||||
"ctrl-enter": "onboarding::Finish",
|
||||
"alt-shift-l": "onboarding::SignIn",
|
||||
"ctrl-escape": "onboarding::Finish",
|
||||
"alt-tab": "onboarding::SignIn",
|
||||
"shift-alt-a": "onboarding::OpenAccount"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,8 +95,8 @@
|
||||
"g g": "vim::StartOfDocument",
|
||||
"g h": "editor::Hover",
|
||||
"g B": "editor::BlameHover",
|
||||
"g t": "vim::GoToTab",
|
||||
"g shift-t": "vim::GoToPreviousTab",
|
||||
"g t": "pane::ActivateNextItem",
|
||||
"g shift-t": "pane::ActivatePreviousItem",
|
||||
"g d": "editor::GoToDefinition",
|
||||
"g shift-d": "editor::GoToDeclaration",
|
||||
"g y": "editor::GoToTypeDefinition",
|
||||
@@ -426,7 +426,6 @@
|
||||
";": "vim::HelixCollapseSelection",
|
||||
":": "command_palette::Toggle",
|
||||
"m": "vim::PushHelixMatch",
|
||||
"s": "vim::HelixSelectRegex",
|
||||
"]": ["vim::PushHelixNext", { "around": true }],
|
||||
"[": ["vim::PushHelixPrevious", { "around": true }],
|
||||
"left": "vim::WrappingLeft",
|
||||
@@ -434,8 +433,6 @@
|
||||
"h": "vim::WrappingLeft",
|
||||
"l": "vim::WrappingRight",
|
||||
"y": "vim::HelixYank",
|
||||
"p": "vim::HelixPaste",
|
||||
"shift-p": ["vim::HelixPaste", { "before": true }],
|
||||
"alt-;": "vim::OtherEnd",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
|
||||
|
||||
@@ -391,6 +391,8 @@
|
||||
"use_system_window_tabs": false,
|
||||
// Titlebar related settings
|
||||
"title_bar": {
|
||||
// When to show the title bar: "always" | "never" | "hide_in_full_screen".
|
||||
"show": "always",
|
||||
// Whether to show the branch icon beside branch switcher in the titlebar.
|
||||
"show_branch_icon": false,
|
||||
// Whether to show the branch name button in the titlebar.
|
||||
@@ -1512,7 +1514,7 @@
|
||||
// }
|
||||
//
|
||||
"file_types": {
|
||||
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json", "tsconfig*.json"],
|
||||
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
|
||||
"Shell Script": [".env.*"]
|
||||
},
|
||||
// Settings for which version of Node.js and NPM to use when installing
|
||||
|
||||
@@ -43,11 +43,7 @@
|
||||
// "args": ["--login"]
|
||||
// }
|
||||
// }
|
||||
"shell": "system",
|
||||
// Whether to show the task line in the output of the spawned task, defaults to `true`.
|
||||
"show_summary": true,
|
||||
// Whether to show the command line in the output of the spawned task, defaults to `true`.
|
||||
"show_command": true
|
||||
"shell": "system"
|
||||
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
|
||||
// "tags": []
|
||||
}
|
||||
|
||||
10
compose.yml
10
compose.yml
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
postgres:
|
||||
image: docker.io/library/postgres:15
|
||||
image: postgres:15
|
||||
container_name: zed_postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
@@ -23,7 +23,7 @@ services:
|
||||
- ./.blob_store:/data
|
||||
|
||||
livekit_server:
|
||||
image: docker.io/livekit/livekit-server
|
||||
image: livekit/livekit-server
|
||||
container_name: livekit_server
|
||||
entrypoint: /livekit-server --config /livekit.yaml
|
||||
ports:
|
||||
@@ -34,7 +34,7 @@ services:
|
||||
- ./livekit.yaml:/livekit.yaml
|
||||
|
||||
postgrest_app:
|
||||
image: docker.io/postgrest/postgrest
|
||||
image: postgrest/postgrest
|
||||
container_name: postgrest_app
|
||||
ports:
|
||||
- 8081:8081
|
||||
@@ -47,7 +47,7 @@ services:
|
||||
- postgres
|
||||
|
||||
postgrest_llm:
|
||||
image: docker.io/postgrest/postgrest
|
||||
image: postgrest/postgrest
|
||||
container_name: postgrest_llm
|
||||
ports:
|
||||
- 8082:8082
|
||||
@@ -60,7 +60,7 @@ services:
|
||||
- postgres
|
||||
|
||||
stripe-mock:
|
||||
image: docker.io/stripe/stripe-mock:v0.178.0
|
||||
image: stripe/stripe-mock:v0.178.0
|
||||
ports:
|
||||
- 12111:12111
|
||||
- 12112:12112
|
||||
|
||||
@@ -68,7 +68,7 @@ pub trait AgentConnection {
|
||||
///
|
||||
/// If the agent does not support model selection, returns [None].
|
||||
/// This allows sharing the selector in UI components.
|
||||
fn model_selector(&self, _session_id: &acp::SessionId) -> Option<Rc<dyn AgentModelSelector>> {
|
||||
fn model_selector(&self) -> Option<Rc<dyn AgentModelSelector>> {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -177,48 +177,61 @@ pub trait AgentModelSelector: 'static {
|
||||
/// If the session doesn't exist or the model is invalid, it returns an error.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `session_id`: The ID of the session (thread) to apply the model to.
|
||||
/// - `model`: The model to select (should be one from [list_models]).
|
||||
/// - `cx`: The GPUI app context.
|
||||
///
|
||||
/// # Returns
|
||||
/// A task resolving to `Ok(())` on success or an error.
|
||||
fn select_model(&self, model_id: acp::ModelId, cx: &mut App) -> Task<Result<()>>;
|
||||
fn select_model(
|
||||
&self,
|
||||
session_id: acp::SessionId,
|
||||
model_id: AgentModelId,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<()>>;
|
||||
|
||||
/// Retrieves the currently selected model for a specific session (thread).
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `session_id`: The ID of the session (thread) to query.
|
||||
/// - `cx`: The GPUI app context.
|
||||
///
|
||||
/// # Returns
|
||||
/// A task resolving to the selected model (always set) or an error (e.g., session not found).
|
||||
fn selected_model(&self, cx: &mut App) -> Task<Result<AgentModelInfo>>;
|
||||
fn selected_model(
|
||||
&self,
|
||||
session_id: &acp::SessionId,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<AgentModelInfo>>;
|
||||
|
||||
/// Whenever the model list is updated the receiver will be notified.
|
||||
/// Optional for agents that don't update their model list.
|
||||
fn watch(&self, _cx: &mut App) -> Option<watch::Receiver<()>> {
|
||||
None
|
||||
fn watch(&self, cx: &mut App) -> watch::Receiver<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct AgentModelId(pub SharedString);
|
||||
|
||||
impl std::ops::Deref for AgentModelId {
|
||||
type Target = SharedString;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AgentModelId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AgentModelInfo {
|
||||
pub id: acp::ModelId,
|
||||
pub id: AgentModelId,
|
||||
pub name: SharedString,
|
||||
pub description: Option<SharedString>,
|
||||
pub icon: Option<IconName>,
|
||||
}
|
||||
|
||||
impl From<acp::ModelInfo> for AgentModelInfo {
|
||||
fn from(info: acp::ModelInfo) -> Self {
|
||||
Self {
|
||||
id: info.model_id,
|
||||
name: info.name.into(),
|
||||
description: info.description.map(|desc| desc.into()),
|
||||
icon: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct AgentModelGroupName(pub SharedString);
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ struct Session {
|
||||
|
||||
pub struct LanguageModels {
|
||||
/// Access language model by ID
|
||||
models: HashMap<acp::ModelId, Arc<dyn LanguageModel>>,
|
||||
models: HashMap<acp_thread::AgentModelId, Arc<dyn LanguageModel>>,
|
||||
/// Cached list for returning language model information
|
||||
model_list: acp_thread::AgentModelList,
|
||||
refresh_models_rx: watch::Receiver<()>,
|
||||
@@ -132,7 +132,10 @@ impl LanguageModels {
|
||||
self.refresh_models_rx.clone()
|
||||
}
|
||||
|
||||
pub fn model_from_id(&self, model_id: &acp::ModelId) -> Option<Arc<dyn LanguageModel>> {
|
||||
pub fn model_from_id(
|
||||
&self,
|
||||
model_id: &acp_thread::AgentModelId,
|
||||
) -> Option<Arc<dyn LanguageModel>> {
|
||||
self.models.get(model_id).cloned()
|
||||
}
|
||||
|
||||
@@ -143,13 +146,12 @@ impl LanguageModels {
|
||||
acp_thread::AgentModelInfo {
|
||||
id: Self::model_id(model),
|
||||
name: model.name().0,
|
||||
description: None,
|
||||
icon: Some(provider.icon()),
|
||||
}
|
||||
}
|
||||
|
||||
fn model_id(model: &Arc<dyn LanguageModel>) -> acp::ModelId {
|
||||
acp::ModelId(format!("{}/{}", model.provider_id().0, model.id().0).into())
|
||||
fn model_id(model: &Arc<dyn LanguageModel>) -> acp_thread::AgentModelId {
|
||||
acp_thread::AgentModelId(format!("{}/{}", model.provider_id().0, model.id().0).into())
|
||||
}
|
||||
|
||||
fn authenticate_all_language_model_providers(cx: &mut App) -> Task<()> {
|
||||
@@ -834,15 +836,10 @@ impl NativeAgentConnection {
|
||||
}
|
||||
}
|
||||
|
||||
struct NativeAgentModelSelector {
|
||||
session_id: acp::SessionId,
|
||||
connection: NativeAgentConnection,
|
||||
}
|
||||
|
||||
impl acp_thread::AgentModelSelector for NativeAgentModelSelector {
|
||||
impl AgentModelSelector for NativeAgentConnection {
|
||||
fn list_models(&self, cx: &mut App) -> Task<Result<acp_thread::AgentModelList>> {
|
||||
log::debug!("NativeAgentConnection::list_models called");
|
||||
let list = self.connection.0.read(cx).models.model_list.clone();
|
||||
let list = self.0.read(cx).models.model_list.clone();
|
||||
Task::ready(if list.is_empty() {
|
||||
Err(anyhow::anyhow!("No models available"))
|
||||
} else {
|
||||
@@ -850,24 +847,24 @@ impl acp_thread::AgentModelSelector for NativeAgentModelSelector {
|
||||
})
|
||||
}
|
||||
|
||||
fn select_model(&self, model_id: acp::ModelId, cx: &mut App) -> Task<Result<()>> {
|
||||
log::debug!(
|
||||
"Setting model for session {}: {}",
|
||||
self.session_id,
|
||||
model_id
|
||||
);
|
||||
fn select_model(
|
||||
&self,
|
||||
session_id: acp::SessionId,
|
||||
model_id: acp_thread::AgentModelId,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<()>> {
|
||||
log::debug!("Setting model for session {}: {}", session_id, model_id);
|
||||
let Some(thread) = self
|
||||
.connection
|
||||
.0
|
||||
.read(cx)
|
||||
.sessions
|
||||
.get(&self.session_id)
|
||||
.get(&session_id)
|
||||
.map(|session| session.thread.clone())
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("Session not found")));
|
||||
};
|
||||
|
||||
let Some(model) = self.connection.0.read(cx).models.model_from_id(&model_id) else {
|
||||
let Some(model) = self.0.read(cx).models.model_from_id(&model_id) else {
|
||||
return Task::ready(Err(anyhow!("Invalid model ID {}", model_id)));
|
||||
};
|
||||
|
||||
@@ -875,32 +872,33 @@ impl acp_thread::AgentModelSelector for NativeAgentModelSelector {
|
||||
thread.set_model(model.clone(), cx);
|
||||
});
|
||||
|
||||
update_settings_file(
|
||||
self.connection.0.read(cx).fs.clone(),
|
||||
cx,
|
||||
move |settings, _cx| {
|
||||
let provider = model.provider_id().0.to_string();
|
||||
let model = model.id().0.to_string();
|
||||
settings
|
||||
.agent
|
||||
.get_or_insert_default()
|
||||
.set_model(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
},
|
||||
);
|
||||
update_settings_file(self.0.read(cx).fs.clone(), cx, move |settings, _cx| {
|
||||
let provider = model.provider_id().0.to_string();
|
||||
let model = model.id().0.to_string();
|
||||
settings
|
||||
.agent
|
||||
.get_or_insert_default()
|
||||
.set_model(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
});
|
||||
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn selected_model(&self, cx: &mut App) -> Task<Result<acp_thread::AgentModelInfo>> {
|
||||
fn selected_model(
|
||||
&self,
|
||||
session_id: &acp::SessionId,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<acp_thread::AgentModelInfo>> {
|
||||
let session_id = session_id.clone();
|
||||
|
||||
let Some(thread) = self
|
||||
.connection
|
||||
.0
|
||||
.read(cx)
|
||||
.sessions
|
||||
.get(&self.session_id)
|
||||
.get(&session_id)
|
||||
.map(|session| session.thread.clone())
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("Session not found")));
|
||||
@@ -917,8 +915,8 @@ impl acp_thread::AgentModelSelector for NativeAgentModelSelector {
|
||||
)))
|
||||
}
|
||||
|
||||
fn watch(&self, cx: &mut App) -> Option<watch::Receiver<()>> {
|
||||
Some(self.connection.0.read(cx).models.watch())
|
||||
fn watch(&self, cx: &mut App) -> watch::Receiver<()> {
|
||||
self.0.read(cx).models.watch()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -974,11 +972,8 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn model_selector(&self, session_id: &acp::SessionId) -> Option<Rc<dyn AgentModelSelector>> {
|
||||
Some(Rc::new(NativeAgentModelSelector {
|
||||
session_id: session_id.clone(),
|
||||
connection: self.clone(),
|
||||
}) as Rc<dyn AgentModelSelector>)
|
||||
fn model_selector(&self) -> Option<Rc<dyn AgentModelSelector>> {
|
||||
Some(Rc::new(self.clone()) as Rc<dyn AgentModelSelector>)
|
||||
}
|
||||
|
||||
fn prompt(
|
||||
@@ -1201,7 +1196,9 @@ mod tests {
|
||||
use crate::HistoryEntryId;
|
||||
|
||||
use super::*;
|
||||
use acp_thread::{AgentConnection, AgentModelGroupName, AgentModelInfo, MentionUri};
|
||||
use acp_thread::{
|
||||
AgentConnection, AgentModelGroupName, AgentModelId, AgentModelInfo, MentionUri,
|
||||
};
|
||||
use fs::FakeFs;
|
||||
use gpui::TestAppContext;
|
||||
use indoc::indoc;
|
||||
@@ -1295,25 +1292,7 @@ mod tests {
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
// Create a thread/session
|
||||
let acp_thread = cx
|
||||
.update(|cx| {
|
||||
Rc::new(connection.clone()).new_thread(project.clone(), Path::new("/a"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let session_id = cx.update(|cx| acp_thread.read(cx).session_id().clone());
|
||||
|
||||
let models = cx
|
||||
.update(|cx| {
|
||||
connection
|
||||
.model_selector(&session_id)
|
||||
.unwrap()
|
||||
.list_models(cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let models = cx.update(|cx| connection.list_models(cx)).await.unwrap();
|
||||
|
||||
let acp_thread::AgentModelList::Grouped(models) = models else {
|
||||
panic!("Unexpected model group");
|
||||
@@ -1323,9 +1302,8 @@ mod tests {
|
||||
IndexMap::from_iter([(
|
||||
AgentModelGroupName("Fake".into()),
|
||||
vec![AgentModelInfo {
|
||||
id: acp::ModelId("fake/fake".into()),
|
||||
id: AgentModelId("fake/fake".into()),
|
||||
name: "Fake".into(),
|
||||
description: None,
|
||||
icon: Some(ui::IconName::ZedAssistant),
|
||||
}]
|
||||
)])
|
||||
@@ -1382,9 +1360,8 @@ mod tests {
|
||||
let session_id = cx.update(|cx| acp_thread.read(cx).session_id().clone());
|
||||
|
||||
// Select a model
|
||||
let selector = connection.model_selector(&session_id).unwrap();
|
||||
let model_id = acp::ModelId("fake/fake".into());
|
||||
cx.update(|cx| selector.select_model(model_id.clone(), cx))
|
||||
let model_id = AgentModelId("fake/fake".into());
|
||||
cx.update(|cx| connection.select_model(session_id.clone(), model_id.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -48,15 +48,16 @@ The one exception to this is if the user references something you don't know abo
|
||||
## Code Block Formatting
|
||||
|
||||
Whenever you mention a code block, you MUST use ONLY use the following format:
|
||||
|
||||
```path/to/Something.blah#L123-456
|
||||
(code goes here)
|
||||
```
|
||||
|
||||
The `#L123-456` means the line number range 123 through 456, and the path/to/Something.blah is a path in the project. (If there is no valid path in the project, then you can use /dev/null/path.extension for its path.) This is the ONLY valid way to format code blocks, because the Markdown parser does not understand the more common ```language syntax, or bare ``` blocks. It only understands this path-based syntax, and if the path is missing, then it will error and you will have to do it over again.
|
||||
The `#L123-456` means the line number range 123 through 456, and the path/to/Something.blah
|
||||
is a path in the project. (If there is no valid path in the project, then you can use
|
||||
/dev/null/path.extension for its path.) This is the ONLY valid way to format code blocks, because the Markdown parser
|
||||
does not understand the more common ```language syntax, or bare ``` blocks. It only
|
||||
understands this path-based syntax, and if the path is missing, then it will error and you will have to do it over again.
|
||||
Just to be really clear about this, if you ever find yourself writing three backticks followed by a language name, STOP!
|
||||
You have made a mistake. You can only ever put paths after triple backticks!
|
||||
|
||||
<example>
|
||||
Based on all the information I've gathered, here's a summary of how this system works:
|
||||
1. The README file is loaded into the system.
|
||||
@@ -73,7 +74,6 @@ This is the last header in the README.
|
||||
```
|
||||
4. Finally, it passes this information on to the next process.
|
||||
</example>
|
||||
|
||||
<example>
|
||||
In Markdown, hash marks signify headings. For example:
|
||||
```/dev/null/example.md#L1-3
|
||||
@@ -82,7 +82,6 @@ In Markdown, hash marks signify headings. For example:
|
||||
### Level 3 heading
|
||||
```
|
||||
</example>
|
||||
|
||||
Here are examples of ways you must never render code blocks:
|
||||
<bad_example_do_not_do_this>
|
||||
In Markdown, hash marks signify headings. For example:
|
||||
@@ -92,9 +91,7 @@ In Markdown, hash marks signify headings. For example:
|
||||
### Level 3 heading
|
||||
```
|
||||
</bad_example_do_not_do_this>
|
||||
|
||||
This example is unacceptable because it does not include the path.
|
||||
|
||||
<bad_example_do_not_do_this>
|
||||
In Markdown, hash marks signify headings. For example:
|
||||
```markdown
|
||||
@@ -104,15 +101,14 @@ In Markdown, hash marks signify headings. For example:
|
||||
```
|
||||
</bad_example_do_not_do_this>
|
||||
This example is unacceptable because it has the language instead of the path.
|
||||
|
||||
<bad_example_do_not_do_this>
|
||||
In Markdown, hash marks signify headings. For example:
|
||||
# Level 1 heading
|
||||
## Level 2 heading
|
||||
### Level 3 heading
|
||||
</bad_example_do_not_do_this>
|
||||
This example is unacceptable because it uses indentation to mark the code block instead of backticks with a path.
|
||||
|
||||
This example is unacceptable because it uses indentation to mark the code block
|
||||
instead of backticks with a path.
|
||||
<bad_example_do_not_do_this>
|
||||
In Markdown, hash marks signify headings. For example:
|
||||
```markdown
|
||||
|
||||
@@ -1850,18 +1850,8 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
|
||||
.unwrap();
|
||||
let connection = NativeAgentConnection(agent.clone());
|
||||
|
||||
// Create a thread using new_thread
|
||||
let connection_rc = Rc::new(connection.clone());
|
||||
let acp_thread = cx
|
||||
.update(|cx| connection_rc.new_thread(project, cwd, cx))
|
||||
.await
|
||||
.expect("new_thread should succeed");
|
||||
|
||||
// Get the session_id from the AcpThread
|
||||
let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone());
|
||||
|
||||
// Test model_selector returns Some
|
||||
let selector_opt = connection.model_selector(&session_id);
|
||||
let selector_opt = connection.model_selector();
|
||||
assert!(
|
||||
selector_opt.is_some(),
|
||||
"agent2 should always support ModelSelector"
|
||||
@@ -1878,16 +1868,23 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
|
||||
};
|
||||
assert!(!listed_models.is_empty(), "should have at least one model");
|
||||
assert_eq!(
|
||||
listed_models[&AgentModelGroupName("Fake".into())][0]
|
||||
.id
|
||||
.0
|
||||
.as_ref(),
|
||||
listed_models[&AgentModelGroupName("Fake".into())][0].id.0,
|
||||
"fake/fake"
|
||||
);
|
||||
|
||||
// Create a thread using new_thread
|
||||
let connection_rc = Rc::new(connection.clone());
|
||||
let acp_thread = cx
|
||||
.update(|cx| connection_rc.new_thread(project, cwd, cx))
|
||||
.await
|
||||
.expect("new_thread should succeed");
|
||||
|
||||
// Get the session_id from the AcpThread
|
||||
let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone());
|
||||
|
||||
// Test selected_model returns the default
|
||||
let model = cx
|
||||
.update(|cx| selector.selected_model(cx))
|
||||
.update(|cx| selector.selected_model(&session_id, cx))
|
||||
.await
|
||||
.expect("selected_model should succeed");
|
||||
let model = cx
|
||||
|
||||
@@ -9,14 +9,14 @@ use std::sync::Arc;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
/// Copies a file or directory in the project, and returns confirmation that the copy succeeded.
|
||||
/// Directory contents will be copied recursively.
|
||||
/// Directory contents will be copied recursively (like `cp -r`).
|
||||
///
|
||||
/// This tool should be used when it's desirable to create a copy of a file or directory without modifying the original.
|
||||
/// It's much more efficient than doing this by separately reading and then writing the file or directory's contents, so this tool should be preferred over that approach whenever copying is the goal.
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct CopyPathToolInput {
|
||||
/// The source path of the file or directory to copy.
|
||||
/// If a directory is specified, its contents will be copied recursively.
|
||||
/// If a directory is specified, its contents will be copied recursively (like `cp -r`).
|
||||
///
|
||||
/// <example>
|
||||
/// If the project has the following files:
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{AgentTool, ToolCallEventStream};
|
||||
|
||||
/// Creates a new directory at the specified path within the project. Returns confirmation that the directory was created.
|
||||
///
|
||||
/// This tool creates a directory and all necessary parent directories. It should be used whenever you need to create new directories within the project.
|
||||
/// This tool creates a directory and all necessary parent directories (similar to `mkdir -p`). It should be used whenever you need to create new directories within the project.
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct CreateDirectoryToolInput {
|
||||
/// The path of the new directory.
|
||||
|
||||
@@ -834,14 +834,11 @@ mod tests {
|
||||
"**/.secretdir".to_string(),
|
||||
"**/.mymetadata".to_string(),
|
||||
]);
|
||||
settings.project.worktree.private_files = Some(
|
||||
vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
settings.project.worktree.private_files = Some(vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1067,8 +1064,7 @@ mod tests {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||
settings.project.worktree.private_files =
|
||||
Some(vec!["**/.env".to_string()].into());
|
||||
settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -427,14 +427,11 @@ mod tests {
|
||||
"**/.mymetadata".to_string(),
|
||||
"**/.hidden_subdir".to_string(),
|
||||
]);
|
||||
settings.project.worktree.private_files = Some(
|
||||
vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
settings.project.worktree.private_files = Some(vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -571,8 +568,7 @@ mod tests {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||
settings.project.worktree.private_files =
|
||||
Some(vec!["**/.env".to_string()].into());
|
||||
settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -593,14 +593,11 @@ mod test {
|
||||
"**/.secretdir".to_string(),
|
||||
"**/.mymetadata".to_string(),
|
||||
]);
|
||||
settings.project.worktree.private_files = Some(
|
||||
vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
settings.project.worktree.private_files = Some(vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -807,8 +804,7 @@ mod test {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||
settings.project.worktree.private_files =
|
||||
Some(vec!["**/.env".to_string()].into());
|
||||
settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ impl AgentTool for TerminalTool {
|
||||
.into(),
|
||||
}
|
||||
} else {
|
||||
"".into()
|
||||
"Run terminal command".into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ pub struct AcpConnection {
|
||||
pub struct AcpSession {
|
||||
thread: WeakEntity<AcpThread>,
|
||||
suppress_abort_err: bool,
|
||||
models: Option<Rc<RefCell<acp::SessionModelState>>>,
|
||||
session_modes: Option<Rc<RefCell<acp::SessionModeState>>>,
|
||||
}
|
||||
|
||||
@@ -265,7 +264,6 @@ impl AgentConnection for AcpConnection {
|
||||
})?;
|
||||
|
||||
let modes = response.modes.map(|modes| Rc::new(RefCell::new(modes)));
|
||||
let models = response.models.map(|models| Rc::new(RefCell::new(models)));
|
||||
|
||||
if let Some(default_mode) = default_mode {
|
||||
if let Some(modes) = modes.as_ref() {
|
||||
@@ -328,12 +326,10 @@ impl AgentConnection for AcpConnection {
|
||||
)
|
||||
})?;
|
||||
|
||||
|
||||
let session = AcpSession {
|
||||
thread: thread.downgrade(),
|
||||
suppress_abort_err: false,
|
||||
session_modes: modes,
|
||||
models,
|
||||
session_modes: modes
|
||||
};
|
||||
sessions.borrow_mut().insert(session_id, session);
|
||||
|
||||
@@ -454,27 +450,6 @@ impl AgentConnection for AcpConnection {
|
||||
}
|
||||
}
|
||||
|
||||
fn model_selector(
|
||||
&self,
|
||||
session_id: &acp::SessionId,
|
||||
) -> Option<Rc<dyn acp_thread::AgentModelSelector>> {
|
||||
let sessions = self.sessions.clone();
|
||||
let sessions_ref = sessions.borrow();
|
||||
let Some(session) = sessions_ref.get(session_id) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if let Some(models) = session.models.as_ref() {
|
||||
Some(Rc::new(AcpModelSelector::new(
|
||||
session_id.clone(),
|
||||
self.connection.clone(),
|
||||
models.clone(),
|
||||
)) as _)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
self
|
||||
}
|
||||
@@ -525,82 +500,6 @@ impl acp_thread::AgentSessionModes for AcpSessionModes {
|
||||
}
|
||||
}
|
||||
|
||||
struct AcpModelSelector {
|
||||
session_id: acp::SessionId,
|
||||
connection: Rc<acp::ClientSideConnection>,
|
||||
state: Rc<RefCell<acp::SessionModelState>>,
|
||||
}
|
||||
|
||||
impl AcpModelSelector {
|
||||
fn new(
|
||||
session_id: acp::SessionId,
|
||||
connection: Rc<acp::ClientSideConnection>,
|
||||
state: Rc<RefCell<acp::SessionModelState>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
session_id,
|
||||
connection,
|
||||
state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl acp_thread::AgentModelSelector for AcpModelSelector {
|
||||
fn list_models(&self, _cx: &mut App) -> Task<Result<acp_thread::AgentModelList>> {
|
||||
Task::ready(Ok(acp_thread::AgentModelList::Flat(
|
||||
self.state
|
||||
.borrow()
|
||||
.available_models
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(acp_thread::AgentModelInfo::from)
|
||||
.collect(),
|
||||
)))
|
||||
}
|
||||
|
||||
fn select_model(&self, model_id: acp::ModelId, cx: &mut App) -> Task<Result<()>> {
|
||||
let connection = self.connection.clone();
|
||||
let session_id = self.session_id.clone();
|
||||
let old_model_id;
|
||||
{
|
||||
let mut state = self.state.borrow_mut();
|
||||
old_model_id = state.current_model_id.clone();
|
||||
state.current_model_id = model_id.clone();
|
||||
};
|
||||
let state = self.state.clone();
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let result = connection
|
||||
.set_session_model(acp::SetSessionModelRequest {
|
||||
session_id,
|
||||
model_id,
|
||||
meta: None,
|
||||
})
|
||||
.await;
|
||||
|
||||
if result.is_err() {
|
||||
state.borrow_mut().current_model_id = old_model_id;
|
||||
}
|
||||
|
||||
result?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn selected_model(&self, _cx: &mut App) -> Task<Result<acp_thread::AgentModelInfo>> {
|
||||
let state = self.state.borrow();
|
||||
Task::ready(
|
||||
state
|
||||
.available_models
|
||||
.iter()
|
||||
.find(|m| m.model_id == state.current_model_id)
|
||||
.cloned()
|
||||
.map(acp_thread::AgentModelInfo::from)
|
||||
.ok_or_else(|| anyhow::anyhow!("Model not found")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct ClientDelegate {
|
||||
sessions: Rc<RefCell<HashMap<acp::SessionId, AcpSession>>>,
|
||||
cx: AsyncApp,
|
||||
|
||||
@@ -19,7 +19,6 @@ convert_case.workspace = true
|
||||
fs.workspace = true
|
||||
gpui.workspace = true
|
||||
language_model.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
|
||||
@@ -5,13 +5,13 @@ use std::sync::Arc;
|
||||
use collections::IndexMap;
|
||||
use gpui::{App, Pixels, px};
|
||||
use language_model::LanguageModel;
|
||||
use project::DisableAiSettings;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{
|
||||
DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection,
|
||||
NotifyWhenAgentWaiting, Settings, SettingsContent,
|
||||
};
|
||||
use util::MergeFrom;
|
||||
|
||||
pub use crate::agent_profile::*;
|
||||
|
||||
@@ -54,10 +54,6 @@ pub struct AgentSettings {
|
||||
}
|
||||
|
||||
impl AgentSettings {
|
||||
pub fn enabled(&self, cx: &App) -> bool {
|
||||
self.enabled && !DisableAiSettings::get_global(cx).disable_ai
|
||||
}
|
||||
|
||||
pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
|
||||
let settings = Self::get_global(cx);
|
||||
for setting in settings.model_parameters.iter().rev() {
|
||||
@@ -151,7 +147,7 @@ impl Default for AgentProfileId {
|
||||
}
|
||||
|
||||
impl Settings for AgentSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let agent = content.agent.clone().unwrap();
|
||||
Self {
|
||||
enabled: agent.enabled.unwrap(),
|
||||
@@ -187,6 +183,66 @@ impl Settings for AgentSettings {
|
||||
}
|
||||
}
|
||||
|
||||
fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) {
|
||||
let Some(value) = &content.agent else { return };
|
||||
self.enabled.merge_from(&value.enabled);
|
||||
self.button.merge_from(&value.button);
|
||||
self.dock.merge_from(&value.dock);
|
||||
self.default_width
|
||||
.merge_from(&value.default_width.map(Into::into));
|
||||
self.default_height
|
||||
.merge_from(&value.default_height.map(Into::into));
|
||||
self.default_model = value.default_model.clone().or(self.default_model.take());
|
||||
|
||||
self.inline_assistant_model = value
|
||||
.inline_assistant_model
|
||||
.clone()
|
||||
.or(self.inline_assistant_model.take());
|
||||
self.commit_message_model = value
|
||||
.clone()
|
||||
.commit_message_model
|
||||
.or(self.commit_message_model.take());
|
||||
self.thread_summary_model = value
|
||||
.clone()
|
||||
.thread_summary_model
|
||||
.or(self.thread_summary_model.take());
|
||||
self.inline_alternatives
|
||||
.merge_from(&value.inline_alternatives.clone());
|
||||
self.default_profile
|
||||
.merge_from(&value.default_profile.clone().map(AgentProfileId));
|
||||
self.default_view.merge_from(&value.default_view);
|
||||
self.always_allow_tool_actions
|
||||
.merge_from(&value.always_allow_tool_actions);
|
||||
self.notify_when_agent_waiting
|
||||
.merge_from(&value.notify_when_agent_waiting);
|
||||
self.play_sound_when_agent_done
|
||||
.merge_from(&value.play_sound_when_agent_done);
|
||||
self.stream_edits.merge_from(&value.stream_edits);
|
||||
self.single_file_review
|
||||
.merge_from(&value.single_file_review);
|
||||
self.preferred_completion_mode
|
||||
.merge_from(&value.preferred_completion_mode.map(Into::into));
|
||||
self.enable_feedback.merge_from(&value.enable_feedback);
|
||||
self.expand_edit_card.merge_from(&value.expand_edit_card);
|
||||
self.expand_terminal_card
|
||||
.merge_from(&value.expand_terminal_card);
|
||||
self.use_modifier_to_send
|
||||
.merge_from(&value.use_modifier_to_send);
|
||||
|
||||
self.model_parameters
|
||||
.extend_from_slice(&value.model_parameters);
|
||||
self.message_editor_min_lines
|
||||
.merge_from(&value.message_editor_min_lines);
|
||||
|
||||
if let Some(profiles) = value.profiles.as_ref() {
|
||||
self.profiles.extend(
|
||||
profiles
|
||||
.into_iter()
|
||||
.map(|(id, profile)| (AgentProfileId(id.clone()), profile.clone().into())),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
|
||||
if let Some(b) = vscode
|
||||
.read_value("chat.agent.enabled")
|
||||
|
||||
@@ -80,7 +80,6 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
settings.workspace = true
|
||||
shlex.workspace = true
|
||||
smol.workspace = true
|
||||
streaming_diff.workspace = true
|
||||
task.workspace = true
|
||||
|
||||
@@ -47,7 +47,12 @@ use std::{
|
||||
};
|
||||
use text::OffsetRangeExt;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{ButtonLike, TintColor, Toggleable, prelude::*};
|
||||
use ui::{
|
||||
ActiveTheme, AnyElement, App, ButtonCommon, ButtonLike, ButtonStyle, Color, Element as _,
|
||||
FluentBuilder as _, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label,
|
||||
LabelCommon, LabelSize, ParentElement, Render, SelectableButton, Styled, TextSize, TintColor,
|
||||
Toggleable, Window, div, h_flex,
|
||||
};
|
||||
use util::{ResultExt, debug_panic};
|
||||
use workspace::{Workspace, notifications::NotifyResultExt as _};
|
||||
use zed_actions::agent::Chat;
|
||||
@@ -359,7 +364,7 @@ impl MessageEditor {
|
||||
|
||||
let task = match mention_uri.clone() {
|
||||
MentionUri::Fetch { url } => self.confirm_mention_for_fetch(url, cx),
|
||||
MentionUri::Directory { .. } => Task::ready(Ok(Mention::UriOnly)),
|
||||
MentionUri::Directory { abs_path } => self.confirm_mention_for_directory(abs_path, cx),
|
||||
MentionUri::Thread { id, .. } => self.confirm_mention_for_thread(id, cx),
|
||||
MentionUri::TextThread { path, .. } => self.confirm_mention_for_text_thread(path, cx),
|
||||
MentionUri::File { abs_path } => self.confirm_mention_for_file(abs_path, cx),
|
||||
@@ -463,6 +468,97 @@ impl MessageEditor {
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm_mention_for_directory(
|
||||
&mut self,
|
||||
abs_path: PathBuf,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Mention>> {
|
||||
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc<Path>, PathBuf)> {
|
||||
let mut files = Vec::new();
|
||||
|
||||
for entry in worktree.child_entries(path) {
|
||||
if entry.is_dir() {
|
||||
files.extend(collect_files_in_path(worktree, &entry.path));
|
||||
} else if entry.is_file() {
|
||||
files.push((entry.path.clone(), worktree.full_path(&entry.path)));
|
||||
}
|
||||
}
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
let Some(project_path) = self
|
||||
.project
|
||||
.read(cx)
|
||||
.project_path_for_absolute_path(&abs_path, cx)
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("project path not found")));
|
||||
};
|
||||
let Some(entry) = self.project.read(cx).entry_for_path(&project_path, cx) else {
|
||||
return Task::ready(Err(anyhow!("project entry not found")));
|
||||
};
|
||||
let directory_path = entry.path.clone();
|
||||
let worktree_id = project_path.worktree_id;
|
||||
let Some(worktree) = self.project.read(cx).worktree_for_id(worktree_id, cx) else {
|
||||
return Task::ready(Err(anyhow!("worktree not found")));
|
||||
};
|
||||
let project = self.project.clone();
|
||||
cx.spawn(async move |_, cx| {
|
||||
let file_paths = worktree.read_with(cx, |worktree, _cx| {
|
||||
collect_files_in_path(worktree, &directory_path)
|
||||
})?;
|
||||
let descendants_future = cx.update(|cx| {
|
||||
join_all(file_paths.into_iter().map(|(worktree_path, full_path)| {
|
||||
let rel_path = worktree_path
|
||||
.strip_prefix(&directory_path)
|
||||
.log_err()
|
||||
.map_or_else(|| worktree_path.clone(), |rel_path| rel_path.into());
|
||||
|
||||
let open_task = project.update(cx, |project, cx| {
|
||||
project.buffer_store().update(cx, |buffer_store, cx| {
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: worktree_path,
|
||||
};
|
||||
buffer_store.open_buffer(project_path, cx)
|
||||
})
|
||||
});
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let buffer = open_task.await.log_err()?;
|
||||
let buffer_content = outline::get_buffer_content_or_outline(
|
||||
buffer.clone(),
|
||||
Some(&full_path),
|
||||
&cx,
|
||||
)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
Some((rel_path, full_path, buffer_content.text, buffer))
|
||||
})
|
||||
}))
|
||||
})?;
|
||||
|
||||
let contents = cx
|
||||
.background_spawn(async move {
|
||||
let (contents, tracked_buffers) = descendants_future
|
||||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|(rel_path, full_path, rope, buffer)| {
|
||||
((rel_path, full_path, rope), buffer)
|
||||
})
|
||||
.unzip();
|
||||
Mention::Text {
|
||||
content: render_directory_contents(contents),
|
||||
tracked_buffers,
|
||||
}
|
||||
})
|
||||
.await;
|
||||
anyhow::Ok(contents)
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm_mention_for_fetch(
|
||||
&mut self,
|
||||
url: url::Url,
|
||||
@@ -680,7 +776,6 @@ impl MessageEditor {
|
||||
|
||||
pub fn contents(
|
||||
&self,
|
||||
full_mention_content: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<(Vec<acp::ContentBlock>, Vec<Entity<Buffer>>)>> {
|
||||
// Check for unsupported slash commands before spawning async task
|
||||
@@ -692,12 +787,9 @@ impl MessageEditor {
|
||||
return Task::ready(Err(err));
|
||||
}
|
||||
|
||||
let contents = self.mention_set.contents(
|
||||
&self.prompt_capabilities.borrow(),
|
||||
full_mention_content,
|
||||
self.project.clone(),
|
||||
cx,
|
||||
);
|
||||
let contents = self
|
||||
.mention_set
|
||||
.contents(&self.prompt_capabilities.borrow(), cx);
|
||||
let editor = self.editor.clone();
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
@@ -1171,96 +1263,6 @@ impl MessageEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn full_mention_for_directory(
|
||||
project: &Entity<Project>,
|
||||
abs_path: &Path,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Mention>> {
|
||||
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc<Path>, PathBuf)> {
|
||||
let mut files = Vec::new();
|
||||
|
||||
for entry in worktree.child_entries(path) {
|
||||
if entry.is_dir() {
|
||||
files.extend(collect_files_in_path(worktree, &entry.path));
|
||||
} else if entry.is_file() {
|
||||
files.push((entry.path.clone(), worktree.full_path(&entry.path)));
|
||||
}
|
||||
}
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
let Some(project_path) = project
|
||||
.read(cx)
|
||||
.project_path_for_absolute_path(&abs_path, cx)
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("project path not found")));
|
||||
};
|
||||
let Some(entry) = project.read(cx).entry_for_path(&project_path, cx) else {
|
||||
return Task::ready(Err(anyhow!("project entry not found")));
|
||||
};
|
||||
let directory_path = entry.path.clone();
|
||||
let worktree_id = project_path.worktree_id;
|
||||
let Some(worktree) = project.read(cx).worktree_for_id(worktree_id, cx) else {
|
||||
return Task::ready(Err(anyhow!("worktree not found")));
|
||||
};
|
||||
let project = project.clone();
|
||||
cx.spawn(async move |cx| {
|
||||
let file_paths = worktree.read_with(cx, |worktree, _cx| {
|
||||
collect_files_in_path(worktree, &directory_path)
|
||||
})?;
|
||||
let descendants_future = cx.update(|cx| {
|
||||
join_all(file_paths.into_iter().map(|(worktree_path, full_path)| {
|
||||
let rel_path = worktree_path
|
||||
.strip_prefix(&directory_path)
|
||||
.log_err()
|
||||
.map_or_else(|| worktree_path.clone(), |rel_path| rel_path.into());
|
||||
|
||||
let open_task = project.update(cx, |project, cx| {
|
||||
project.buffer_store().update(cx, |buffer_store, cx| {
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: worktree_path,
|
||||
};
|
||||
buffer_store.open_buffer(project_path, cx)
|
||||
})
|
||||
});
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let buffer = open_task.await.log_err()?;
|
||||
let buffer_content = outline::get_buffer_content_or_outline(
|
||||
buffer.clone(),
|
||||
Some(&full_path),
|
||||
&cx,
|
||||
)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
Some((rel_path, full_path, buffer_content.text, buffer))
|
||||
})
|
||||
}))
|
||||
})?;
|
||||
|
||||
let contents = cx
|
||||
.background_spawn(async move {
|
||||
let (contents, tracked_buffers) = descendants_future
|
||||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|(rel_path, full_path, rope, buffer)| {
|
||||
((rel_path, full_path, rope), buffer)
|
||||
})
|
||||
.unzip();
|
||||
Mention::Text {
|
||||
content: render_directory_contents(contents),
|
||||
tracked_buffers,
|
||||
}
|
||||
})
|
||||
.await;
|
||||
anyhow::Ok(contents)
|
||||
})
|
||||
}
|
||||
|
||||
fn render_directory_contents(entries: Vec<(Arc<Path>, PathBuf, String)>) -> String {
|
||||
let mut output = String::new();
|
||||
for (_relative_path, full_path, content) in entries {
|
||||
@@ -1286,14 +1288,18 @@ impl Render for MessageEditor {
|
||||
.flex_1()
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let font_size = TextSize::Small
|
||||
.rems(cx)
|
||||
.to_pixels(settings.agent_font_size(cx));
|
||||
let line_height = settings.buffer_line_height.value() * font_size;
|
||||
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_features: settings.buffer_font.features.clone(),
|
||||
font_size: settings.buffer_font_size(cx).into(),
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
font_size: font_size.into(),
|
||||
line_height: line_height.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -1508,8 +1514,6 @@ impl MentionSet {
|
||||
fn contents(
|
||||
&self,
|
||||
prompt_capabilities: &acp::PromptCapabilities,
|
||||
full_mention_content: bool,
|
||||
project: Entity<Project>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<HashMap<CreaseId, (MentionUri, Mention)>>> {
|
||||
if !prompt_capabilities.embedded_context {
|
||||
@@ -1523,19 +1527,13 @@ impl MentionSet {
|
||||
}
|
||||
|
||||
let mentions = self.mentions.clone();
|
||||
cx.spawn(async move |cx| {
|
||||
cx.spawn(async move |_cx| {
|
||||
let mut contents = HashMap::default();
|
||||
for (crease_id, (mention_uri, task)) in mentions {
|
||||
let content = if full_mention_content
|
||||
&& let MentionUri::Directory { abs_path } = &mention_uri
|
||||
{
|
||||
cx.update(|cx| full_mention_for_directory(&project, abs_path, cx))?
|
||||
.await?
|
||||
} else {
|
||||
task.await.map_err(|e| anyhow!("{e}"))?
|
||||
};
|
||||
|
||||
contents.insert(crease_id, (mention_uri, content));
|
||||
contents.insert(
|
||||
crease_id,
|
||||
(mention_uri, task.await.map_err(|e| anyhow!("{e}"))?),
|
||||
);
|
||||
}
|
||||
Ok(contents)
|
||||
})
|
||||
@@ -1696,7 +1694,7 @@ mod tests {
|
||||
});
|
||||
|
||||
let (content, _) = message_editor
|
||||
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||
.update(cx, |message_editor, cx| message_editor.contents(cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1759,7 +1757,7 @@ mod tests {
|
||||
});
|
||||
|
||||
let contents_result = message_editor
|
||||
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||
.update(cx, |message_editor, cx| message_editor.contents(cx))
|
||||
.await;
|
||||
|
||||
// Should fail because available_commands is empty (no commands supported)
|
||||
@@ -1782,7 +1780,7 @@ mod tests {
|
||||
});
|
||||
|
||||
let contents_result = message_editor
|
||||
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||
.update(cx, |message_editor, cx| message_editor.contents(cx))
|
||||
.await;
|
||||
|
||||
assert!(contents_result.is_err());
|
||||
@@ -1797,7 +1795,7 @@ mod tests {
|
||||
});
|
||||
|
||||
let contents_result = message_editor
|
||||
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||
.update(cx, |message_editor, cx| message_editor.contents(cx))
|
||||
.await;
|
||||
|
||||
// Should succeed because /help is in available_commands
|
||||
@@ -1809,7 +1807,7 @@ mod tests {
|
||||
});
|
||||
|
||||
let (content, _) = message_editor
|
||||
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||
.update(cx, |message_editor, cx| message_editor.contents(cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1827,7 +1825,7 @@ mod tests {
|
||||
|
||||
// The @ mention functionality should not be affected
|
||||
let (content, _) = message_editor
|
||||
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||
.update(cx, |message_editor, cx| message_editor.contents(cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -2273,12 +2271,9 @@ mod tests {
|
||||
|
||||
let contents = message_editor
|
||||
.update(&mut cx, |message_editor, cx| {
|
||||
message_editor.mention_set().contents(
|
||||
&all_prompt_capabilities,
|
||||
false,
|
||||
project.clone(),
|
||||
cx,
|
||||
)
|
||||
message_editor
|
||||
.mention_set()
|
||||
.contents(&all_prompt_capabilities, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -2295,12 +2290,9 @@ mod tests {
|
||||
|
||||
let contents = message_editor
|
||||
.update(&mut cx, |message_editor, cx| {
|
||||
message_editor.mention_set().contents(
|
||||
&acp::PromptCapabilities::default(),
|
||||
false,
|
||||
project.clone(),
|
||||
cx,
|
||||
)
|
||||
message_editor
|
||||
.mention_set()
|
||||
.contents(&acp::PromptCapabilities::default(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -2349,12 +2341,9 @@ mod tests {
|
||||
|
||||
let contents = message_editor
|
||||
.update(&mut cx, |message_editor, cx| {
|
||||
message_editor.mention_set().contents(
|
||||
&all_prompt_capabilities,
|
||||
false,
|
||||
project.clone(),
|
||||
cx,
|
||||
)
|
||||
message_editor
|
||||
.mention_set()
|
||||
.contents(&all_prompt_capabilities, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -2462,12 +2451,9 @@ mod tests {
|
||||
|
||||
let contents = message_editor
|
||||
.update(&mut cx, |message_editor, cx| {
|
||||
message_editor.mention_set().contents(
|
||||
&all_prompt_capabilities,
|
||||
false,
|
||||
project.clone(),
|
||||
cx,
|
||||
)
|
||||
message_editor
|
||||
.mention_set()
|
||||
.contents(&all_prompt_capabilities, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -2515,12 +2501,9 @@ mod tests {
|
||||
// Getting the message contents fails
|
||||
message_editor
|
||||
.update(&mut cx, |message_editor, cx| {
|
||||
message_editor.mention_set().contents(
|
||||
&all_prompt_capabilities,
|
||||
false,
|
||||
project.clone(),
|
||||
cx,
|
||||
)
|
||||
message_editor
|
||||
.mention_set()
|
||||
.contents(&all_prompt_capabilities, cx)
|
||||
})
|
||||
.await
|
||||
.expect_err("Should fail to load x.png");
|
||||
@@ -2565,12 +2548,9 @@ mod tests {
|
||||
// Now getting the contents succeeds, because the invalid mention was removed
|
||||
let contents = message_editor
|
||||
.update(&mut cx, |message_editor, cx| {
|
||||
message_editor.mention_set().contents(
|
||||
&all_prompt_capabilities,
|
||||
false,
|
||||
project.clone(),
|
||||
cx,
|
||||
)
|
||||
message_editor
|
||||
.mention_set()
|
||||
.contents(&all_prompt_capabilities, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{cmp::Reverse, rc::Rc, sync::Arc};
|
||||
|
||||
use acp_thread::{AgentModelInfo, AgentModelList, AgentModelSelector};
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::Result;
|
||||
use collections::IndexMap;
|
||||
use futures::FutureExt;
|
||||
@@ -9,19 +10,20 @@ use gpui::{Action, AsyncWindowContext, BackgroundExecutor, DismissEvent, Task, W
|
||||
use ordered_float::OrderedFloat;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use ui::{
|
||||
AnyElement, App, Context, DocumentationAside, DocumentationEdge, DocumentationSide,
|
||||
IntoElement, ListItem, ListItemSpacing, SharedString, Window, prelude::*, rems,
|
||||
AnyElement, App, Context, IntoElement, ListItem, ListItemSpacing, SharedString, Window,
|
||||
prelude::*, rems,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
pub type AcpModelSelector = Picker<AcpModelPickerDelegate>;
|
||||
|
||||
pub fn acp_model_selector(
|
||||
session_id: acp::SessionId,
|
||||
selector: Rc<dyn AgentModelSelector>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<AcpModelSelector>,
|
||||
) -> AcpModelSelector {
|
||||
let delegate = AcpModelPickerDelegate::new(selector, window, cx);
|
||||
let delegate = AcpModelPickerDelegate::new(session_id, selector, window, cx);
|
||||
Picker::list(delegate, window, cx)
|
||||
.show_scrollbar(true)
|
||||
.width(rems(20.))
|
||||
@@ -34,63 +36,61 @@ enum AcpModelPickerEntry {
|
||||
}
|
||||
|
||||
pub struct AcpModelPickerDelegate {
|
||||
session_id: acp::SessionId,
|
||||
selector: Rc<dyn AgentModelSelector>,
|
||||
filtered_entries: Vec<AcpModelPickerEntry>,
|
||||
models: Option<AgentModelList>,
|
||||
selected_index: usize,
|
||||
selected_description: Option<(usize, SharedString)>,
|
||||
selected_model: Option<AgentModelInfo>,
|
||||
_refresh_models_task: Task<()>,
|
||||
}
|
||||
|
||||
impl AcpModelPickerDelegate {
|
||||
fn new(
|
||||
session_id: acp::SessionId,
|
||||
selector: Rc<dyn AgentModelSelector>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<AcpModelSelector>,
|
||||
) -> Self {
|
||||
let rx = selector.watch(cx);
|
||||
let refresh_models_task = {
|
||||
cx.spawn_in(window, {
|
||||
async move |this, cx| {
|
||||
async fn refresh(
|
||||
this: &WeakEntity<Picker<AcpModelPickerDelegate>>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Result<()> {
|
||||
let (models_task, selected_model_task) = this.update(cx, |this, cx| {
|
||||
(
|
||||
this.delegate.selector.list_models(cx),
|
||||
this.delegate.selector.selected_model(cx),
|
||||
)
|
||||
})?;
|
||||
let mut rx = selector.watch(cx);
|
||||
let refresh_models_task = cx.spawn_in(window, {
|
||||
let session_id = session_id.clone();
|
||||
async move |this, cx| {
|
||||
async fn refresh(
|
||||
this: &WeakEntity<Picker<AcpModelPickerDelegate>>,
|
||||
session_id: &acp::SessionId,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Result<()> {
|
||||
let (models_task, selected_model_task) = this.update(cx, |this, cx| {
|
||||
(
|
||||
this.delegate.selector.list_models(cx),
|
||||
this.delegate.selector.selected_model(session_id, cx),
|
||||
)
|
||||
})?;
|
||||
|
||||
let (models, selected_model) =
|
||||
futures::join!(models_task, selected_model_task);
|
||||
let (models, selected_model) = futures::join!(models_task, selected_model_task);
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.delegate.models = models.ok();
|
||||
this.delegate.selected_model = selected_model.ok();
|
||||
this.refresh(window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
refresh(&this, cx).await.log_err();
|
||||
if let Some(mut rx) = rx {
|
||||
while let Ok(()) = rx.recv().await {
|
||||
refresh(&this, cx).await.log_err();
|
||||
}
|
||||
}
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.delegate.models = models.ok();
|
||||
this.delegate.selected_model = selected_model.ok();
|
||||
this.refresh(window, cx)
|
||||
})
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
refresh(&this, &session_id, cx).await.log_err();
|
||||
while let Ok(()) = rx.recv().await {
|
||||
refresh(&this, &session_id, cx).await.log_err();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
session_id,
|
||||
selector,
|
||||
filtered_entries: Vec::new(),
|
||||
models: None,
|
||||
selected_model: None,
|
||||
selected_index: 0,
|
||||
selected_description: None,
|
||||
_refresh_models_task: refresh_models_task,
|
||||
}
|
||||
}
|
||||
@@ -182,7 +182,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
||||
self.filtered_entries.get(self.selected_index)
|
||||
{
|
||||
self.selector
|
||||
.select_model(model_info.id.clone(), cx)
|
||||
.select_model(self.session_id.clone(), model_info.id.clone(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
self.selected_model = Some(model_info.clone());
|
||||
let current_index = self.selected_index;
|
||||
@@ -233,46 +233,31 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
||||
};
|
||||
|
||||
Some(
|
||||
div()
|
||||
.id(("model-picker-menu-child", ix))
|
||||
.when_some(model_info.description.clone(), |this, description| {
|
||||
this
|
||||
.on_hover(cx.listener(move |menu, hovered, _, cx| {
|
||||
if *hovered {
|
||||
menu.delegate.selected_description = Some((ix, description.clone()));
|
||||
} else if matches!(menu.delegate.selected_description, Some((id, _)) if id == ix) {
|
||||
menu.delegate.selected_description = None;
|
||||
}
|
||||
cx.notify();
|
||||
}))
|
||||
})
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.start_slot::<Icon>(model_info.icon.map(|icon| {
|
||||
Icon::new(icon)
|
||||
.color(model_icon_color)
|
||||
.size(IconSize::Small)
|
||||
}))
|
||||
.child(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.start_slot::<Icon>(model_info.icon.map(|icon| {
|
||||
Icon::new(icon)
|
||||
.color(model_icon_color)
|
||||
.size(IconSize::Small)
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.pl_0p5()
|
||||
.gap_1p5()
|
||||
.w(px(240.))
|
||||
.child(Label::new(model_info.name.clone()).truncate()),
|
||||
)
|
||||
.end_slot(div().pr_3().when(is_selected, |this| {
|
||||
this.child(
|
||||
Icon::new(IconName::Check)
|
||||
.color(Color::Accent)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
})),
|
||||
h_flex()
|
||||
.w_full()
|
||||
.pl_0p5()
|
||||
.gap_1p5()
|
||||
.w(px(240.))
|
||||
.child(Label::new(model_info.name.clone()).truncate()),
|
||||
)
|
||||
.into_any_element()
|
||||
.end_slot(div().pr_3().when(is_selected, |this| {
|
||||
this.child(
|
||||
Icon::new(IconName::Check)
|
||||
.color(Color::Accent)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
}))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -307,21 +292,6 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
||||
.into_any(),
|
||||
)
|
||||
}
|
||||
|
||||
fn documentation_aside(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<ui::DocumentationAside> {
|
||||
self.selected_description.as_ref().map(|(_, description)| {
|
||||
let description = description.clone();
|
||||
DocumentationAside::new(
|
||||
DocumentationSide::Left,
|
||||
DocumentationEdge::Bottom,
|
||||
Rc::new(move |_| Label::new(description.clone()).into_any_element()),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn info_list_to_picker_entries(
|
||||
@@ -401,7 +371,6 @@ async fn fuzzy_search(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use agent_client_protocol as acp;
|
||||
use gpui::TestAppContext;
|
||||
|
||||
use super::*;
|
||||
@@ -414,9 +383,8 @@ mod tests {
|
||||
models
|
||||
.into_iter()
|
||||
.map(|model| acp_thread::AgentModelInfo {
|
||||
id: acp::ModelId(model.to_string().into()),
|
||||
id: acp_thread::AgentModelId(model.to_string().into()),
|
||||
name: model.to_string().into(),
|
||||
description: None,
|
||||
icon: None,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use acp_thread::AgentModelSelector;
|
||||
use agent_client_protocol as acp;
|
||||
use gpui::{Entity, FocusHandle};
|
||||
use picker::popover_menu::PickerPopoverMenu;
|
||||
use ui::{
|
||||
@@ -19,6 +20,7 @@ pub struct AcpModelSelectorPopover {
|
||||
|
||||
impl AcpModelSelectorPopover {
|
||||
pub(crate) fn new(
|
||||
session_id: acp::SessionId,
|
||||
selector: Rc<dyn AgentModelSelector>,
|
||||
menu_handle: PopoverMenuHandle<AcpModelSelector>,
|
||||
focus_handle: FocusHandle,
|
||||
@@ -26,7 +28,7 @@ impl AcpModelSelectorPopover {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
selector: cx.new(move |cx| acp_model_selector(selector, window, cx)),
|
||||
selector: cx.new(move |cx| acp_model_selector(session_id, selector, window, cx)),
|
||||
menu_handle,
|
||||
focus_handle,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use agent_client_protocol::{self as acp, PromptCapabilities};
|
||||
use agent_servers::{AgentServer, AgentServerDelegate};
|
||||
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
|
||||
use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore, NativeAgentServer};
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use arrayvec::ArrayVec;
|
||||
use audio::{Audio, Sound};
|
||||
use buffer_diff::BufferDiff;
|
||||
@@ -577,21 +577,23 @@ impl AcpThreadView {
|
||||
|
||||
AgentDiff::set_active_thread(&workspace, thread.clone(), window, cx);
|
||||
|
||||
this.model_selector = thread
|
||||
.read(cx)
|
||||
.connection()
|
||||
.model_selector(thread.read(cx).session_id())
|
||||
.map(|selector| {
|
||||
cx.new(|cx| {
|
||||
AcpModelSelectorPopover::new(
|
||||
selector,
|
||||
PopoverMenuHandle::default(),
|
||||
this.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
this.model_selector =
|
||||
thread
|
||||
.read(cx)
|
||||
.connection()
|
||||
.model_selector()
|
||||
.map(|selector| {
|
||||
cx.new(|cx| {
|
||||
AcpModelSelectorPopover::new(
|
||||
thread.read(cx).session_id().clone(),
|
||||
selector,
|
||||
PopoverMenuHandle::default(),
|
||||
this.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
let mode_selector = thread
|
||||
.read(cx)
|
||||
@@ -1038,7 +1040,10 @@ impl AcpThreadView {
|
||||
return;
|
||||
}
|
||||
|
||||
self.send_impl(self.message_editor.clone(), window, cx)
|
||||
let contents = self
|
||||
.message_editor
|
||||
.update(cx, |message_editor, cx| message_editor.contents(cx));
|
||||
self.send_impl(contents, window, cx)
|
||||
}
|
||||
|
||||
fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -1048,11 +1053,15 @@ impl AcpThreadView {
|
||||
|
||||
let cancelled = thread.update(cx, |thread, cx| thread.cancel(cx));
|
||||
|
||||
let contents = self
|
||||
.message_editor
|
||||
.update(cx, |message_editor, cx| message_editor.contents(cx));
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
cancelled.await;
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.send_impl(this.message_editor.clone(), window, cx);
|
||||
this.send_impl(contents, window, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
@@ -1061,23 +1070,10 @@ impl AcpThreadView {
|
||||
|
||||
fn send_impl(
|
||||
&mut self,
|
||||
message_editor: Entity<MessageEditor>,
|
||||
contents: Task<Result<(Vec<acp::ContentBlock>, Vec<Entity<Buffer>>)>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let full_mention_content = self.as_native_thread(cx).is_some_and(|thread| {
|
||||
// Include full contents when using minimal profile
|
||||
let thread = thread.read(cx);
|
||||
AgentSettings::get_global(cx)
|
||||
.profiles
|
||||
.get(thread.profile())
|
||||
.is_some_and(|profile| profile.tools.is_empty())
|
||||
});
|
||||
|
||||
let contents = message_editor.update(cx, |message_editor, cx| {
|
||||
message_editor.contents(full_mention_content, cx)
|
||||
});
|
||||
|
||||
let agent_telemetry_id = self.agent.telemetry_id();
|
||||
|
||||
self.thread_error.take();
|
||||
@@ -1206,8 +1202,10 @@ impl AcpThreadView {
|
||||
thread
|
||||
.update(cx, |thread, cx| thread.rewind(user_message_id, cx))?
|
||||
.await?;
|
||||
let contents =
|
||||
message_editor.update(cx, |message_editor, cx| message_editor.contents(cx))?;
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.send_impl(message_editor, window, cx);
|
||||
this.send_impl(contents, window, cx);
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
@@ -1584,19 +1582,6 @@ impl AcpThreadView {
|
||||
|
||||
window.spawn(cx, async move |cx| {
|
||||
let mut task = login.clone();
|
||||
task.command = task
|
||||
.command
|
||||
.map(|command| anyhow::Ok(shlex::try_quote(&command)?.to_string()))
|
||||
.transpose()?;
|
||||
task.args = task
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
Ok(shlex::try_quote(arg)
|
||||
.context("Failed to quote argument")?
|
||||
.to_string())
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
task.full_label = task.label.clone();
|
||||
task.id = task::TaskId(format!("external-agent-{}-login", task.label));
|
||||
task.command_label = task.label.clone();
|
||||
@@ -1606,7 +1591,7 @@ impl AcpThreadView {
|
||||
task.shell = shell;
|
||||
|
||||
let terminal = terminal_panel.update_in(cx, |terminal_panel, window, cx| {
|
||||
terminal_panel.spawn_task(&task, window, cx)
|
||||
terminal_panel.spawn_task(&login, window, cx)
|
||||
})?;
|
||||
|
||||
let terminal = terminal.await?;
|
||||
@@ -2079,6 +2064,27 @@ impl AcpThreadView {
|
||||
let has_location = tool_call.locations.len() == 1;
|
||||
let card_header_id = SharedString::from("inner-tool-call-header");
|
||||
|
||||
let tool_icon = if tool_call.kind == acp::ToolKind::Edit && has_location {
|
||||
FileIcons::get_icon(&tool_call.locations[0].path, cx)
|
||||
.map(Icon::from_path)
|
||||
.unwrap_or(Icon::new(IconName::ToolPencil))
|
||||
} else {
|
||||
Icon::new(match tool_call.kind {
|
||||
acp::ToolKind::Read => IconName::ToolSearch,
|
||||
acp::ToolKind::Edit => IconName::ToolPencil,
|
||||
acp::ToolKind::Delete => IconName::ToolDeleteFile,
|
||||
acp::ToolKind::Move => IconName::ArrowRightLeft,
|
||||
acp::ToolKind::Search => IconName::ToolSearch,
|
||||
acp::ToolKind::Execute => IconName::ToolTerminal,
|
||||
acp::ToolKind::Think => IconName::ToolThink,
|
||||
acp::ToolKind::Fetch => IconName::ToolWeb,
|
||||
acp::ToolKind::SwitchMode => IconName::ArrowRightLeft,
|
||||
acp::ToolKind::Other => IconName::ToolHammer,
|
||||
})
|
||||
}
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted);
|
||||
|
||||
let failed_or_canceled = match &tool_call.status {
|
||||
ToolCallStatus::Rejected | ToolCallStatus::Canceled | ToolCallStatus::Failed => true,
|
||||
_ => false,
|
||||
@@ -2088,16 +2094,41 @@ impl AcpThreadView {
|
||||
tool_call.status,
|
||||
ToolCallStatus::WaitingForConfirmation { .. }
|
||||
);
|
||||
let is_terminal_tool = matches!(tool_call.kind, acp::ToolKind::Execute);
|
||||
let is_edit =
|
||||
matches!(tool_call.kind, acp::ToolKind::Edit) || tool_call.diffs().next().is_some();
|
||||
|
||||
let use_card_layout = needs_confirmation || is_edit || is_terminal_tool;
|
||||
let use_card_layout = needs_confirmation || is_edit;
|
||||
|
||||
let is_collapsible = !tool_call.content.is_empty() && !needs_confirmation;
|
||||
|
||||
let is_open = needs_confirmation || self.expanded_tool_calls.contains(&tool_call.id);
|
||||
|
||||
let gradient_overlay = {
|
||||
div()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.right_0()
|
||||
.w_12()
|
||||
.h_full()
|
||||
.map(|this| {
|
||||
if use_card_layout {
|
||||
this.bg(linear_gradient(
|
||||
90.,
|
||||
linear_color_stop(self.tool_card_header_bg(cx), 1.),
|
||||
linear_color_stop(self.tool_card_header_bg(cx).opacity(0.2), 0.),
|
||||
))
|
||||
} else {
|
||||
this.bg(linear_gradient(
|
||||
90.,
|
||||
linear_color_stop(cx.theme().colors().panel_background, 1.),
|
||||
linear_color_stop(
|
||||
cx.theme().colors().panel_background.opacity(0.2),
|
||||
0.,
|
||||
),
|
||||
))
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let tool_output_display =
|
||||
if is_open {
|
||||
match &tool_call.status {
|
||||
@@ -2182,202 +2213,104 @@ impl AcpThreadView {
|
||||
}
|
||||
})
|
||||
.mr_5()
|
||||
.map(|this| {
|
||||
if is_terminal_tool {
|
||||
this.child(
|
||||
v_flex()
|
||||
.p_1p5()
|
||||
.gap_0p5()
|
||||
.text_ui_sm(cx)
|
||||
.child(
|
||||
h_flex()
|
||||
.group(&card_header_id)
|
||||
.relative()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.when(use_card_layout, |this| {
|
||||
this.p_0p5()
|
||||
.rounded_t(rems_from_px(5.))
|
||||
.bg(self.tool_card_header_bg(cx))
|
||||
.child(
|
||||
Label::new("Run Command")
|
||||
.buffer_font(cx)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
MarkdownElement::new(
|
||||
tool_call.label.clone(),
|
||||
terminal_command_markdown_style(window, cx),
|
||||
)
|
||||
.code_block_renderer(
|
||||
markdown::CodeBlockRenderer::Default {
|
||||
copy_button: false,
|
||||
copy_button_on_hover: false,
|
||||
border: false,
|
||||
},
|
||||
)
|
||||
),
|
||||
)
|
||||
} else {
|
||||
this.child(
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.group(&card_header_id)
|
||||
.relative()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.when(use_card_layout, |this| {
|
||||
this.p_0p5()
|
||||
.rounded_t(rems_from_px(5.))
|
||||
.bg(self.tool_card_header_bg(cx))
|
||||
.h(window.line_height() - px(2.))
|
||||
.text_size(self.tool_name_font_size())
|
||||
.gap_1p5()
|
||||
.when(has_location || use_card_layout, |this| this.px_1())
|
||||
.when(has_location, |this| {
|
||||
this.cursor(CursorStyle::PointingHand)
|
||||
.rounded(rems_from_px(3.)) // Concentric border radius
|
||||
.hover(|s| s.bg(cx.theme().colors().element_hover.opacity(0.5)))
|
||||
})
|
||||
.child(self.render_tool_call_label(
|
||||
entry_ix,
|
||||
tool_call,
|
||||
is_edit,
|
||||
use_card_layout,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.when(is_collapsible || failed_or_canceled, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.px_1()
|
||||
.gap_px()
|
||||
.when(is_collapsible, |this| {
|
||||
this.child(
|
||||
Disclosure::new(("expand", entry_ix), is_open)
|
||||
.opened_icon(IconName::ChevronUp)
|
||||
.closed_icon(IconName::ChevronDown)
|
||||
.visible_on_hover(&card_header_id)
|
||||
.on_click(cx.listener({
|
||||
let id = tool_call.id.clone();
|
||||
move |this: &mut Self, _, _, cx: &mut Context<Self>| {
|
||||
if is_open {
|
||||
this.expanded_tool_calls.remove(&id);
|
||||
} else {
|
||||
this.expanded_tool_calls.insert(id.clone());
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
})),
|
||||
)
|
||||
})
|
||||
.when(failed_or_canceled, |this| {
|
||||
this.child(
|
||||
Icon::new(IconName::Close)
|
||||
.color(Color::Error)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
.overflow_hidden()
|
||||
.child(tool_icon)
|
||||
.child(if has_location {
|
||||
h_flex()
|
||||
.id(("open-tool-call-location", entry_ix))
|
||||
.w_full()
|
||||
.map(|this| {
|
||||
if use_card_layout {
|
||||
this.text_color(cx.theme().colors().text)
|
||||
} else {
|
||||
this.text_color(cx.theme().colors().text_muted)
|
||||
}
|
||||
})
|
||||
.child(self.render_markdown(
|
||||
tool_call.label.clone(),
|
||||
MarkdownStyle {
|
||||
prevent_mouse_interaction: true,
|
||||
..default_markdown_style(false, true, window, cx)
|
||||
},
|
||||
))
|
||||
.tooltip(Tooltip::text("Jump to File"))
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.open_tool_call_location(entry_ix, 0, window, cx);
|
||||
}))
|
||||
.into_any_element()
|
||||
} else {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.child(self.render_markdown(
|
||||
tool_call.label.clone(),
|
||||
default_markdown_style(false, true, window, cx),
|
||||
))
|
||||
.into_any()
|
||||
})
|
||||
.when(!has_location, |this| this.child(gradient_overlay)),
|
||||
)
|
||||
}
|
||||
})
|
||||
.when(is_collapsible || failed_or_canceled, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.px_1()
|
||||
.gap_px()
|
||||
.when(is_collapsible, |this| {
|
||||
this.child(
|
||||
Disclosure::new(("expand", entry_ix), is_open)
|
||||
.opened_icon(IconName::ChevronUp)
|
||||
.closed_icon(IconName::ChevronDown)
|
||||
.visible_on_hover(&card_header_id)
|
||||
.on_click(cx.listener({
|
||||
let id = tool_call.id.clone();
|
||||
move |this: &mut Self, _, _, cx: &mut Context<Self>| {
|
||||
if is_open {
|
||||
this.expanded_tool_calls.remove(&id);
|
||||
} else {
|
||||
this.expanded_tool_calls.insert(id.clone());
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
})),
|
||||
)
|
||||
})
|
||||
.when(failed_or_canceled, |this| {
|
||||
this.child(
|
||||
Icon::new(IconName::Close)
|
||||
.color(Color::Error)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.children(tool_output_display)
|
||||
}
|
||||
|
||||
fn render_tool_call_label(
|
||||
&self,
|
||||
entry_ix: usize,
|
||||
tool_call: &ToolCall,
|
||||
is_edit: bool,
|
||||
use_card_layout: bool,
|
||||
window: &Window,
|
||||
cx: &Context<Self>,
|
||||
) -> Div {
|
||||
let has_location = tool_call.locations.len() == 1;
|
||||
|
||||
let tool_icon = if tool_call.kind == acp::ToolKind::Edit && has_location {
|
||||
FileIcons::get_icon(&tool_call.locations[0].path, cx)
|
||||
.map(Icon::from_path)
|
||||
.unwrap_or(Icon::new(IconName::ToolPencil))
|
||||
} else {
|
||||
Icon::new(match tool_call.kind {
|
||||
acp::ToolKind::Read => IconName::ToolSearch,
|
||||
acp::ToolKind::Edit => IconName::ToolPencil,
|
||||
acp::ToolKind::Delete => IconName::ToolDeleteFile,
|
||||
acp::ToolKind::Move => IconName::ArrowRightLeft,
|
||||
acp::ToolKind::Search => IconName::ToolSearch,
|
||||
acp::ToolKind::Execute => IconName::ToolTerminal,
|
||||
acp::ToolKind::Think => IconName::ToolThink,
|
||||
acp::ToolKind::Fetch => IconName::ToolWeb,
|
||||
acp::ToolKind::SwitchMode => IconName::ArrowRightLeft,
|
||||
acp::ToolKind::Other => IconName::ToolHammer,
|
||||
})
|
||||
}
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted);
|
||||
|
||||
let gradient_overlay = {
|
||||
div()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.right_0()
|
||||
.w_12()
|
||||
.h_full()
|
||||
.map(|this| {
|
||||
if use_card_layout {
|
||||
this.bg(linear_gradient(
|
||||
90.,
|
||||
linear_color_stop(self.tool_card_header_bg(cx), 1.),
|
||||
linear_color_stop(self.tool_card_header_bg(cx).opacity(0.2), 0.),
|
||||
))
|
||||
} else {
|
||||
this.bg(linear_gradient(
|
||||
90.,
|
||||
linear_color_stop(cx.theme().colors().panel_background, 1.),
|
||||
linear_color_stop(
|
||||
cx.theme().colors().panel_background.opacity(0.2),
|
||||
0.,
|
||||
),
|
||||
))
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.relative()
|
||||
.w_full()
|
||||
.h(window.line_height() - px(2.))
|
||||
.text_size(self.tool_name_font_size())
|
||||
.gap_1p5()
|
||||
.when(has_location || use_card_layout, |this| this.px_1())
|
||||
.when(has_location, |this| {
|
||||
this.cursor(CursorStyle::PointingHand)
|
||||
.rounded(rems_from_px(3.)) // Concentric border radius
|
||||
.hover(|s| s.bg(cx.theme().colors().element_hover.opacity(0.5)))
|
||||
})
|
||||
.overflow_hidden()
|
||||
.child(tool_icon)
|
||||
.child(if has_location {
|
||||
h_flex()
|
||||
.id(("open-tool-call-location", entry_ix))
|
||||
.w_full()
|
||||
.map(|this| {
|
||||
if use_card_layout {
|
||||
this.text_color(cx.theme().colors().text)
|
||||
} else {
|
||||
this.text_color(cx.theme().colors().text_muted)
|
||||
}
|
||||
})
|
||||
.child(self.render_markdown(
|
||||
tool_call.label.clone(),
|
||||
MarkdownStyle {
|
||||
prevent_mouse_interaction: true,
|
||||
..default_markdown_style(false, true, window, cx)
|
||||
},
|
||||
))
|
||||
.tooltip(Tooltip::text("Jump to File"))
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.open_tool_call_location(entry_ix, 0, window, cx);
|
||||
}))
|
||||
.into_any_element()
|
||||
} else {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.child(self.render_markdown(
|
||||
tool_call.label.clone(),
|
||||
default_markdown_style(false, true, window, cx),
|
||||
))
|
||||
.into_any()
|
||||
})
|
||||
.when(!is_edit, |this| this.child(gradient_overlay))
|
||||
}
|
||||
|
||||
fn render_tool_call_content(
|
||||
&self,
|
||||
entry_ix: usize,
|
||||
@@ -5736,6 +5669,23 @@ pub(crate) mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_spawn_external_agent_login_handles_spaces(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
// Verify paths with spaces aren't pre-quoted
|
||||
let path_with_spaces = "/Users/test/Library/Application Support/Zed/cli.js";
|
||||
let login_task = task::SpawnInTerminal {
|
||||
command: Some("node".to_string()),
|
||||
args: vec![path_with_spaces.to_string(), "/login".to_string()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Args should be passed as-is, not pre-quoted
|
||||
assert!(!login_task.args[0].starts_with('"'));
|
||||
assert!(!login_task.args[0].starts_with('\''));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_notification_for_tool_authorization(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -543,23 +543,35 @@ impl AgentConfiguration {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let mut context_server_ids = self
|
||||
let mut registry_descriptors = self
|
||||
.context_server_store
|
||||
.read(cx)
|
||||
.server_ids(cx)
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
.all_registry_descriptor_ids(cx);
|
||||
let server_count = registry_descriptors.len();
|
||||
|
||||
// Sort context servers: ones without mcp-server- prefix first, then prefixed ones
|
||||
context_server_ids.sort_by(|a, b| {
|
||||
const MCP_PREFIX: &str = "mcp-server-";
|
||||
match (a.0.strip_prefix(MCP_PREFIX), b.0.strip_prefix(MCP_PREFIX)) {
|
||||
// Sort context servers: non-mcp-server ones first, then mcp-server ones
|
||||
registry_descriptors.sort_by(|a, b| {
|
||||
let has_mcp_prefix_a = a.0.starts_with("mcp-server-");
|
||||
let has_mcp_prefix_b = b.0.starts_with("mcp-server-");
|
||||
|
||||
match (has_mcp_prefix_a, has_mcp_prefix_b) {
|
||||
// If one has mcp-server- prefix and other doesn't, non-mcp comes first
|
||||
(Some(_), None) => std::cmp::Ordering::Greater,
|
||||
(None, Some(_)) => std::cmp::Ordering::Less,
|
||||
(true, false) => std::cmp::Ordering::Greater,
|
||||
(false, true) => std::cmp::Ordering::Less,
|
||||
// If both have same prefix status, sort by appropriate key
|
||||
(Some(a), Some(b)) => a.cmp(b),
|
||||
(None, None) => a.0.cmp(&b.0),
|
||||
_ => {
|
||||
let get_sort_key = |server_id: &str| -> String {
|
||||
if let Some(suffix) = server_id.strip_prefix("mcp-server-") {
|
||||
suffix.to_string()
|
||||
} else {
|
||||
server_id.to_string()
|
||||
}
|
||||
};
|
||||
|
||||
let key_a = get_sort_key(&a.0);
|
||||
let key_b = get_sort_key(&b.0);
|
||||
key_a.cmp(&key_b)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -624,8 +636,8 @@ impl AgentConfiguration {
|
||||
)
|
||||
.child(add_server_popover),
|
||||
)
|
||||
.child(v_flex().w_full().gap_1().map(|mut parent| {
|
||||
if context_server_ids.is_empty() {
|
||||
.child(v_flex().w_full().gap_1().map(|parent| {
|
||||
if registry_descriptors.is_empty() {
|
||||
parent.child(
|
||||
h_flex()
|
||||
.p_4()
|
||||
@@ -641,18 +653,26 @@ impl AgentConfiguration {
|
||||
),
|
||||
)
|
||||
} else {
|
||||
for (index, context_server_id) in context_server_ids.into_iter().enumerate() {
|
||||
if index > 0 {
|
||||
parent = parent.child(
|
||||
Divider::horizontal()
|
||||
.color(DividerColor::BorderFaded)
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
parent =
|
||||
parent.child(self.render_context_server(context_server_id, window, cx));
|
||||
{
|
||||
parent.children(registry_descriptors.into_iter().enumerate().flat_map(
|
||||
|(index, context_server_id)| {
|
||||
let mut elements: Vec<AnyElement> = vec![
|
||||
self.render_context_server(context_server_id, window, cx)
|
||||
.into_any_element(),
|
||||
];
|
||||
|
||||
if index < server_count - 1 {
|
||||
elements.push(
|
||||
Divider::horizontal()
|
||||
.color(DividerColor::BorderFaded)
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
|
||||
elements
|
||||
},
|
||||
))
|
||||
}
|
||||
parent
|
||||
}
|
||||
}))
|
||||
}
|
||||
@@ -1086,13 +1106,7 @@ impl AgentConfiguration {
|
||||
IconName::AiClaude,
|
||||
"Claude Code",
|
||||
))
|
||||
.map(|mut parent| {
|
||||
for agent in user_defined_agents {
|
||||
parent = parent.child(Divider::horizontal().color(DividerColor::BorderFaded))
|
||||
.child(agent);
|
||||
}
|
||||
parent
|
||||
})
|
||||
.children(user_defined_agents),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::ops::Range;
|
||||
use std::ops::{Not, Range};
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
@@ -408,7 +408,6 @@ impl ActiveView {
|
||||
|
||||
pub struct AgentPanel {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
loading: bool,
|
||||
user_store: Entity<UserStore>,
|
||||
project: Entity<Project>,
|
||||
fs: Arc<dyn Fs>,
|
||||
@@ -514,7 +513,6 @@ impl AgentPanel {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
panel.as_mut(cx).loading = true;
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width.map(|w| w.round());
|
||||
@@ -529,7 +527,6 @@ impl AgentPanel {
|
||||
panel.new_agent_thread(AgentType::NativeAgent, window, cx);
|
||||
});
|
||||
}
|
||||
panel.as_mut(cx).loading = false;
|
||||
panel
|
||||
})?;
|
||||
|
||||
@@ -665,43 +662,6 @@ impl AgentPanel {
|
||||
)
|
||||
});
|
||||
|
||||
let mut old_disable_ai = false;
|
||||
cx.observe_global_in::<SettingsStore>(window, move |panel, window, cx| {
|
||||
let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
|
||||
if old_disable_ai != disable_ai {
|
||||
let agent_panel_id = cx.entity_id();
|
||||
let agent_panel_visible = panel
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let agent_dock_position = panel.position(window, cx);
|
||||
let agent_dock = workspace.dock_at_position(agent_dock_position);
|
||||
let agent_panel_focused = agent_dock
|
||||
.read(cx)
|
||||
.active_panel()
|
||||
.is_some_and(|panel| panel.panel_id() == agent_panel_id);
|
||||
|
||||
let active_panel_visible = agent_dock
|
||||
.read(cx)
|
||||
.visible_panel()
|
||||
.is_some_and(|panel| panel.panel_id() == agent_panel_id);
|
||||
|
||||
if agent_panel_focused {
|
||||
cx.dispatch_action(&ToggleFocus);
|
||||
}
|
||||
|
||||
active_panel_visible
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if agent_panel_visible {
|
||||
cx.emit(PanelEvent::Close);
|
||||
}
|
||||
|
||||
old_disable_ai = disable_ai;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
active_view,
|
||||
workspace,
|
||||
@@ -714,9 +674,11 @@ impl AgentPanel {
|
||||
prompt_store,
|
||||
configuration: None,
|
||||
configuration_subscription: None,
|
||||
|
||||
inline_assist_context_store,
|
||||
previous_view: None,
|
||||
history_store: history_store.clone(),
|
||||
|
||||
new_thread_menu_handle: PopoverMenuHandle::default(),
|
||||
agent_panel_menu_handle: PopoverMenuHandle::default(),
|
||||
assistant_navigation_menu_handle: PopoverMenuHandle::default(),
|
||||
@@ -729,7 +691,6 @@ impl AgentPanel {
|
||||
acp_history,
|
||||
acp_history_store,
|
||||
selected_agent: AgentType::default(),
|
||||
loading: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,6 +703,7 @@ impl AgentPanel {
|
||||
if workspace
|
||||
.panel::<Self>(cx)
|
||||
.is_some_and(|panel| panel.read(cx).enabled(cx))
|
||||
&& !DisableAiSettings::get_global(cx).disable_ai
|
||||
{
|
||||
workspace.toggle_panel_focus::<Self>(window, cx);
|
||||
}
|
||||
@@ -861,7 +823,6 @@ impl AgentPanel {
|
||||
agent: crate::ExternalAgent,
|
||||
}
|
||||
|
||||
let loading = self.loading;
|
||||
let history = self.acp_history_store.clone();
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
@@ -903,9 +864,7 @@ impl AgentPanel {
|
||||
}
|
||||
};
|
||||
|
||||
if !loading {
|
||||
telemetry::event!("Agent Thread Started", agent = ext_agent.name());
|
||||
}
|
||||
telemetry::event!("Agent Thread Started", agent = ext_agent.name());
|
||||
|
||||
let server = ext_agent.server(fs, history);
|
||||
|
||||
@@ -1108,7 +1067,7 @@ impl AgentPanel {
|
||||
let _ = settings
|
||||
.theme
|
||||
.agent_font_size
|
||||
.insert(theme::clamp_font_size(agent_font_size).into());
|
||||
.insert(Some(theme::clamp_font_size(agent_font_size).into()));
|
||||
});
|
||||
} else {
|
||||
theme::adjust_agent_font_size(cx, |size| size + delta);
|
||||
@@ -1540,7 +1499,7 @@ impl Panel for AgentPanel {
|
||||
}
|
||||
|
||||
fn enabled(&self, cx: &App) -> bool {
|
||||
AgentSettings::get_global(cx).enabled(cx)
|
||||
DisableAiSettings::get_global(cx).disable_ai.not() && AgentSettings::get_global(cx).enabled
|
||||
}
|
||||
|
||||
fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
|
||||
|
||||
@@ -251,7 +251,7 @@ pub(crate) fn search_files(
|
||||
fuzzy::match_path_sets(
|
||||
candidate_sets.as_slice(),
|
||||
query.as_str(),
|
||||
&None,
|
||||
None,
|
||||
false,
|
||||
100,
|
||||
&cancellation_flag,
|
||||
|
||||
@@ -144,7 +144,8 @@ impl InlineAssistant {
|
||||
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let enabled = AgentSettings::get_global(cx).enabled(cx);
|
||||
let enabled = !DisableAiSettings::get_global(cx).disable_ai
|
||||
&& AgentSettings::get_global(cx).enabled;
|
||||
terminal_panel.update(cx, |terminal_panel, cx| {
|
||||
terminal_panel.set_assistant_enabled(enabled, cx)
|
||||
});
|
||||
@@ -256,7 +257,8 @@ impl InlineAssistant {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
if !AgentSettings::get_global(cx).enabled(cx) {
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
if !settings.enabled || DisableAiSettings::get_global(cx).disable_ai {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1786,7 +1788,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
||||
_: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Vec<CodeAction>>> {
|
||||
if !AgentSettings::get_global(cx).enabled(cx) {
|
||||
if !AgentSettings::get_global(cx).enabled {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use ai_onboarding::{AgentPanelOnboardingCard, PlanDefinitions};
|
||||
use client::zed_urls;
|
||||
use cloud_llm_client::{Plan, PlanV2};
|
||||
use cloud_llm_client::{Plan, PlanV1};
|
||||
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
|
||||
use ui::{Divider, Tooltip, prelude::*};
|
||||
|
||||
@@ -112,7 +112,7 @@ impl Component for EndTrialUpsell {
|
||||
Some(
|
||||
v_flex()
|
||||
.child(EndTrialUpsell {
|
||||
plan: Plan::V2(PlanV2::ZedFree),
|
||||
plan: Plan::V1(PlanV1::ZedFree),
|
||||
dismiss_upsell: Arc::new(|_, _| {}),
|
||||
})
|
||||
.into_any_element(),
|
||||
|
||||
@@ -120,7 +120,7 @@ impl ZedAiOnboarding {
|
||||
.max_w_full()
|
||||
.gap_1()
|
||||
.child(Headline::new("Welcome to Zed AI"))
|
||||
.child(YoungAccountBanner::new(is_v2))
|
||||
.child(YoungAccountBanner)
|
||||
.child(
|
||||
v_flex()
|
||||
.mt_2()
|
||||
@@ -372,7 +372,7 @@ impl Component for ZedAiOnboarding {
|
||||
"Free Plan",
|
||||
onboarding(
|
||||
SignInStatus::SignedIn,
|
||||
Some(Plan::V2(PlanV2::ZedFree)),
|
||||
Some(Plan::V1(PlanV1::ZedFree)),
|
||||
false,
|
||||
),
|
||||
),
|
||||
@@ -380,7 +380,7 @@ impl Component for ZedAiOnboarding {
|
||||
"Pro Trial",
|
||||
onboarding(
|
||||
SignInStatus::SignedIn,
|
||||
Some(Plan::V2(PlanV2::ZedProTrial)),
|
||||
Some(Plan::V1(PlanV1::ZedProTrial)),
|
||||
false,
|
||||
),
|
||||
),
|
||||
@@ -388,7 +388,7 @@ impl Component for ZedAiOnboarding {
|
||||
"Pro Plan",
|
||||
onboarding(
|
||||
SignInStatus::SignedIn,
|
||||
Some(Plan::V2(PlanV2::ZedPro)),
|
||||
Some(Plan::V1(PlanV1::ZedPro)),
|
||||
false,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -175,7 +175,7 @@ impl RenderOnce for AiUpsellCard {
|
||||
.child(Label::new("Try Zed AI").size(LabelSize::Large))
|
||||
.map(|this| {
|
||||
if self.account_too_young {
|
||||
this.child(YoungAccountBanner::new(is_v2_plan)).child(
|
||||
this.child(YoungAccountBanner).child(
|
||||
v_flex()
|
||||
.mt_2()
|
||||
.gap_1()
|
||||
@@ -215,7 +215,7 @@ impl RenderOnce for AiUpsellCard {
|
||||
.child(
|
||||
footer_container
|
||||
.child(
|
||||
Button::new("start_trial", "Start Pro Trial")
|
||||
Button::new("start_trial", "Start 14-day Free Pro Trial")
|
||||
.full_width()
|
||||
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||
.when_some(self.tab_index, |this, tab_index| {
|
||||
@@ -230,7 +230,7 @@ impl RenderOnce for AiUpsellCard {
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Label::new("14 days, no credit card required")
|
||||
Label::new("No credit card required")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
@@ -327,7 +327,7 @@ impl Component for AiUpsellCard {
|
||||
sign_in_status: SignInStatus::SignedIn,
|
||||
sign_in: Arc::new(|_, _| {}),
|
||||
account_too_young: false,
|
||||
user_plan: Some(Plan::V2(PlanV2::ZedFree)),
|
||||
user_plan: Some(Plan::V1(PlanV1::ZedFree)),
|
||||
tab_index: Some(1),
|
||||
}
|
||||
.into_any_element(),
|
||||
@@ -338,7 +338,7 @@ impl Component for AiUpsellCard {
|
||||
sign_in_status: SignInStatus::SignedIn,
|
||||
sign_in: Arc::new(|_, _| {}),
|
||||
account_too_young: true,
|
||||
user_plan: Some(Plan::V2(PlanV2::ZedFree)),
|
||||
user_plan: Some(Plan::V1(PlanV1::ZedFree)),
|
||||
tab_index: Some(1),
|
||||
}
|
||||
.into_any_element(),
|
||||
@@ -349,7 +349,7 @@ impl Component for AiUpsellCard {
|
||||
sign_in_status: SignInStatus::SignedIn,
|
||||
sign_in: Arc::new(|_, _| {}),
|
||||
account_too_young: false,
|
||||
user_plan: Some(Plan::V2(PlanV2::ZedProTrial)),
|
||||
user_plan: Some(Plan::V1(PlanV1::ZedProTrial)),
|
||||
tab_index: Some(1),
|
||||
}
|
||||
.into_any_element(),
|
||||
@@ -360,7 +360,7 @@ impl Component for AiUpsellCard {
|
||||
sign_in_status: SignInStatus::SignedIn,
|
||||
sign_in: Arc::new(|_, _| {}),
|
||||
account_too_young: false,
|
||||
user_plan: Some(Plan::V2(PlanV2::ZedPro)),
|
||||
user_plan: Some(Plan::V1(PlanV1::ZedPro)),
|
||||
tab_index: Some(1),
|
||||
}
|
||||
.into_any_element(),
|
||||
|
||||
@@ -7,62 +7,33 @@ pub struct PlanDefinitions;
|
||||
impl PlanDefinitions {
|
||||
pub const AI_DESCRIPTION: &'static str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI.";
|
||||
|
||||
pub fn free_plan(&self, is_v2: bool) -> impl IntoElement {
|
||||
if is_v2 {
|
||||
List::new()
|
||||
.child(ListBulletItem::new("2,000 accepted edit predictions"))
|
||||
.child(ListBulletItem::new(
|
||||
"Unlimited prompts with your AI API keys",
|
||||
))
|
||||
.child(ListBulletItem::new(
|
||||
"Unlimited use of external agents like Claude Code",
|
||||
))
|
||||
} else {
|
||||
List::new()
|
||||
.child(ListBulletItem::new("50 prompts with Claude models"))
|
||||
.child(ListBulletItem::new("2,000 accepted edit predictions"))
|
||||
}
|
||||
pub fn free_plan(&self, _is_v2: bool) -> impl IntoElement {
|
||||
List::new()
|
||||
.child(ListBulletItem::new("50 prompts with Claude models"))
|
||||
.child(ListBulletItem::new("2,000 accepted edit predictions"))
|
||||
}
|
||||
|
||||
pub fn pro_trial(&self, is_v2: bool, period: bool) -> impl IntoElement {
|
||||
if is_v2 {
|
||||
List::new()
|
||||
.child(ListBulletItem::new("Unlimited edit predictions"))
|
||||
.child(ListBulletItem::new("$20 of tokens"))
|
||||
.when(period, |this| {
|
||||
this.child(ListBulletItem::new(
|
||||
"Try it out for 14 days, no credit card required",
|
||||
))
|
||||
})
|
||||
} else {
|
||||
List::new()
|
||||
.child(ListBulletItem::new("150 prompts with Claude models"))
|
||||
.child(ListBulletItem::new(
|
||||
"Unlimited edit predictions with Zeta, our open-source model",
|
||||
pub fn pro_trial(&self, _is_v2: bool, period: bool) -> impl IntoElement {
|
||||
List::new()
|
||||
.child(ListBulletItem::new("150 prompts with Claude models"))
|
||||
.child(ListBulletItem::new(
|
||||
"Unlimited edit predictions with Zeta, our open-source model",
|
||||
))
|
||||
.when(period, |this| {
|
||||
this.child(ListBulletItem::new(
|
||||
"Try it out for 14 days for free, no credit card required",
|
||||
))
|
||||
.when(period, |this| {
|
||||
this.child(ListBulletItem::new(
|
||||
"Try it out for 14 days, no credit card required",
|
||||
))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pro_plan(&self, is_v2: bool, price: bool) -> impl IntoElement {
|
||||
if is_v2 {
|
||||
List::new()
|
||||
.child(ListBulletItem::new("Unlimited edit predictions"))
|
||||
.child(ListBulletItem::new("$5 of tokens"))
|
||||
.child(ListBulletItem::new("Usage-based billing beyond $5"))
|
||||
} else {
|
||||
List::new()
|
||||
.child(ListBulletItem::new("500 prompts with Claude models"))
|
||||
.child(ListBulletItem::new(
|
||||
"Unlimited edit predictions with Zeta, our open-source model",
|
||||
))
|
||||
.when(price, |this| {
|
||||
this.child(ListBulletItem::new("$20 USD per month"))
|
||||
})
|
||||
}
|
||||
pub fn pro_plan(&self, _is_v2: bool, price: bool) -> impl IntoElement {
|
||||
List::new()
|
||||
.child(ListBulletItem::new("500 prompts with Claude models"))
|
||||
.child(ListBulletItem::new(
|
||||
"Unlimited edit predictions with Zeta, our open-source model",
|
||||
))
|
||||
.when(price, |this| {
|
||||
this.child(ListBulletItem::new("$20 USD per month"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,30 +2,17 @@ use gpui::{IntoElement, ParentElement};
|
||||
use ui::{Banner, prelude::*};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct YoungAccountBanner {
|
||||
is_v2: bool,
|
||||
}
|
||||
|
||||
impl YoungAccountBanner {
|
||||
pub fn new(is_v2: bool) -> Self {
|
||||
Self { is_v2 }
|
||||
}
|
||||
}
|
||||
pub struct YoungAccountBanner;
|
||||
|
||||
impl RenderOnce for YoungAccountBanner {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
const YOUNG_ACCOUNT_DISCLAIMER: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for free plan usage or Pro plan free trial. You can request an exception by reaching out to billing-support@zed.dev";
|
||||
const YOUNG_ACCOUNT_DISCLAIMER_V2: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for the Pro trial. You can request an exception by reaching out to billing-support@zed.dev";
|
||||
const YOUNG_ACCOUNT_DISCLAIMER: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for free plan usage or Pro plan free trial. To request an exception, reach out to billing-support@zed.dev.";
|
||||
|
||||
let label = div()
|
||||
.w_full()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.child(if self.is_v2 {
|
||||
YOUNG_ACCOUNT_DISCLAIMER_V2
|
||||
} else {
|
||||
YOUNG_ACCOUNT_DISCLAIMER
|
||||
});
|
||||
.child(YOUNG_ACCOUNT_DISCLAIMER);
|
||||
|
||||
div()
|
||||
.max_w_full()
|
||||
|
||||
@@ -16,12 +16,8 @@ anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
net.workspace = true
|
||||
proto.workspace = true
|
||||
parking_lot.workspace = true
|
||||
smol.workspace = true
|
||||
tempfile.workspace = true
|
||||
util.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
zeroize.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
mod encrypted_password;
|
||||
|
||||
pub use encrypted_password::{EncryptedPassword, ProcessExt};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::sync::OnceLock;
|
||||
use std::{ffi::OsStr, time::Duration};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
@@ -16,8 +10,6 @@ use gpui::{AsyncApp, BackgroundExecutor, Task};
|
||||
use smol::fs;
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::encrypted_password::decrypt;
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum AskPassResult {
|
||||
CancelledByUser,
|
||||
@@ -25,19 +17,16 @@ pub enum AskPassResult {
|
||||
}
|
||||
|
||||
pub struct AskPassDelegate {
|
||||
tx: mpsc::UnboundedSender<(String, oneshot::Sender<EncryptedPassword>)>,
|
||||
tx: mpsc::UnboundedSender<(String, oneshot::Sender<String>)>,
|
||||
_task: Task<()>,
|
||||
}
|
||||
|
||||
impl AskPassDelegate {
|
||||
pub fn new(
|
||||
cx: &mut AsyncApp,
|
||||
password_prompt: impl Fn(String, oneshot::Sender<EncryptedPassword>, &mut AsyncApp)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
password_prompt: impl Fn(String, oneshot::Sender<String>, &mut AsyncApp) + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
let (tx, mut rx) = mpsc::unbounded::<(String, oneshot::Sender<_>)>();
|
||||
let (tx, mut rx) = mpsc::unbounded::<(String, oneshot::Sender<String>)>();
|
||||
let task = cx.spawn(async move |cx: &mut AsyncApp| {
|
||||
while let Some((prompt, channel)) = rx.next().await {
|
||||
password_prompt(prompt, channel, cx);
|
||||
@@ -46,7 +35,7 @@ impl AskPassDelegate {
|
||||
Self { tx, _task: task }
|
||||
}
|
||||
|
||||
pub async fn ask_password(&mut self, prompt: String) -> Result<EncryptedPassword> {
|
||||
pub async fn ask_password(&mut self, prompt: String) -> Result<String> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.tx.send((prompt, tx)).await?;
|
||||
Ok(rx.await?)
|
||||
@@ -59,7 +48,7 @@ pub struct AskPassSession {
|
||||
#[cfg(target_os = "windows")]
|
||||
askpass_helper: String,
|
||||
#[cfg(target_os = "windows")]
|
||||
secret: std::sync::Arc<OnceLock<EncryptedPassword>>,
|
||||
secret: std::sync::Arc<parking_lot::Mutex<String>>,
|
||||
_askpass_task: Task<()>,
|
||||
askpass_opened_rx: Option<oneshot::Receiver<()>>,
|
||||
askpass_kill_master_rx: Option<oneshot::Receiver<()>>,
|
||||
@@ -79,7 +68,7 @@ impl AskPassSession {
|
||||
use util::fs::make_file_executable;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let secret = std::sync::Arc::new(OnceLock::new());
|
||||
let secret = std::sync::Arc::new(parking_lot::Mutex::new(String::new()));
|
||||
let temp_dir = tempfile::Builder::new().prefix("zed-askpass").tempdir()?;
|
||||
let askpass_socket = temp_dir.path().join("askpass.sock");
|
||||
let askpass_script_path = temp_dir.path().join(ASKPASS_SCRIPT_NAME);
|
||||
@@ -115,12 +104,10 @@ impl AskPassSession {
|
||||
.context("getting askpass password")
|
||||
.log_err()
|
||||
{
|
||||
stream.write_all(password.as_bytes()).await.log_err();
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
askpass_secret.get_or_init(|| password.clone());
|
||||
}
|
||||
if let Ok(decrypted) = decrypt(password) {
|
||||
stream.write_all(decrypted.as_bytes()).await.log_err();
|
||||
*askpass_secret.lock() = password;
|
||||
}
|
||||
} else {
|
||||
if let Some(kill_tx) = kill_tx.take() {
|
||||
@@ -201,8 +188,8 @@ impl AskPassSession {
|
||||
|
||||
/// This will return the password that was last set by the askpass script.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn get_password(&self) -> Option<EncryptedPassword> {
|
||||
self.secret.get().cloned()
|
||||
pub fn get_password(&self) -> String {
|
||||
self.secret.lock().clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
//! This module provides [EncryptedPassword] for storage of passwords in memory.
|
||||
//! On Windows that's implemented with CryptProtectMemory/CryptUnprotectMemory; on other platforms it just falls through
|
||||
//! to string for now.
|
||||
//!
|
||||
//! The "safety" of this module lies in exploiting visibility rules of Rust:
|
||||
//! 1. No outside module has access to the internal representation of [EncryptedPassword].
|
||||
//! 2. [EncryptedPassword] cannot be converted into a [String] or any other plaintext representation.
|
||||
//! All use cases that do need such functionality (of which we have two right now) are implemented within this module.
|
||||
//!
|
||||
//! Note that this is not bulletproof.
|
||||
//! 1. [ProcessExt] is implemented for [smol::process::Command], which is a builder for smol processes.
|
||||
//! Before the process itself is spawned the contents of [EncryptedPassword] are unencrypted in env var storage of said builder.
|
||||
//! 2. We're also sending plaintext passwords over RPC with [proto::AskPassResponse]. Go figure how great that is.
|
||||
//!
|
||||
//! Still, the goal of this module is to not have passwords laying around nilly-willy in memory.
|
||||
//! We do not claim that it is fool-proof.
|
||||
use anyhow::Result;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
type LengthWithoutPadding = u32;
|
||||
#[derive(Clone)]
|
||||
pub struct EncryptedPassword(Vec<u8>, LengthWithoutPadding);
|
||||
|
||||
pub trait ProcessExt {
|
||||
fn encrypted_env(&mut self, name: &str, value: EncryptedPassword) -> &mut Self;
|
||||
}
|
||||
|
||||
impl ProcessExt for smol::process::Command {
|
||||
fn encrypted_env(&mut self, name: &str, value: EncryptedPassword) -> &mut Self {
|
||||
if let Ok(password) = decrypt(value) {
|
||||
self.env(name, password);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<EncryptedPassword> for proto::AskPassResponse {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(pw: EncryptedPassword) -> Result<Self, Self::Error> {
|
||||
let pw = decrypt(pw)?;
|
||||
Ok(Self { response: pw })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EncryptedPassword {
|
||||
fn drop(&mut self) {
|
||||
self.0.zeroize();
|
||||
self.1.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for EncryptedPassword {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(password: &str) -> Result<EncryptedPassword> {
|
||||
let len: u32 = password.len().try_into()?;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use windows::Win32::Security::Cryptography::{
|
||||
CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS, CryptProtectMemory,
|
||||
};
|
||||
let mut value = password.bytes().collect::<Vec<_>>();
|
||||
let padded_length = len.next_multiple_of(CRYPTPROTECTMEMORY_BLOCK_SIZE);
|
||||
if padded_length != len {
|
||||
value.resize(padded_length as usize, 0);
|
||||
}
|
||||
if len != 0 {
|
||||
unsafe {
|
||||
CryptProtectMemory(
|
||||
value.as_mut_ptr() as _,
|
||||
len,
|
||||
CRYPTPROTECTMEMORY_SAME_PROCESS,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(Self(value, len))
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
Ok(Self(String::from(password).into(), len))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn decrypt(mut password: EncryptedPassword) -> Result<String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use anyhow::Context;
|
||||
use windows::Win32::Security::Cryptography::{
|
||||
CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS, CryptUnprotectMemory,
|
||||
};
|
||||
assert_eq!(
|
||||
password.0.len() % CRYPTPROTECTMEMORY_BLOCK_SIZE as usize,
|
||||
0,
|
||||
"Violated pre-condition (buffer size <{}> must be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE <{}>) for CryptUnprotectMemory.",
|
||||
password.0.len(),
|
||||
CRYPTPROTECTMEMORY_BLOCK_SIZE
|
||||
);
|
||||
if password.1 != 0 {
|
||||
unsafe {
|
||||
CryptUnprotectMemory(
|
||||
password.0.as_mut_ptr() as _,
|
||||
password.1,
|
||||
CRYPTPROTECTMEMORY_SAME_PROCESS,
|
||||
)
|
||||
.context("while decrypting a SSH password")?
|
||||
};
|
||||
|
||||
{
|
||||
// Remove padding
|
||||
_ = password.0.drain(password.1 as usize..);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(std::mem::take(&mut password.0))?)
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
Ok(String::from_utf8(std::mem::take(&mut password.0))?)
|
||||
}
|
||||
@@ -2445,7 +2445,7 @@ impl AssistantContext {
|
||||
.message_anchors
|
||||
.get(next_message_ix)
|
||||
.map_or(buffer.len(), |message| {
|
||||
buffer.clip_offset(message.start.to_previous_offset(buffer), Bias::Left)
|
||||
buffer.clip_offset(message.start.to_offset(buffer) - 1, Bias::Left)
|
||||
});
|
||||
Some(self.insert_message_at_offset(offset, role, status, cx))
|
||||
} else {
|
||||
|
||||
@@ -73,7 +73,7 @@ impl DiagnosticsSlashCommand {
|
||||
fuzzy::match_path_sets(
|
||||
candidate_sets.as_slice(),
|
||||
query.as_str(),
|
||||
&None,
|
||||
None,
|
||||
false,
|
||||
100,
|
||||
&cancellation_flag,
|
||||
|
||||
@@ -104,7 +104,7 @@ impl FileSlashCommand {
|
||||
fuzzy::match_path_sets(
|
||||
candidate_sets.as_slice(),
|
||||
query.as_str(),
|
||||
&None,
|
||||
None,
|
||||
false,
|
||||
100,
|
||||
&cancellation_flag,
|
||||
|
||||
@@ -856,14 +856,11 @@ mod tests {
|
||||
"**/.secretdir".to_string(),
|
||||
"**/.mymetadata".to_string(),
|
||||
]);
|
||||
settings.project.worktree.private_files = Some(
|
||||
vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
settings.project.worktree.private_files = Some(vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1163,8 +1160,7 @@ mod tests {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||
settings.project.worktree.private_files =
|
||||
Some(vec!["**/.env".to_string()].into());
|
||||
settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -513,14 +513,11 @@ mod tests {
|
||||
"**/.mymetadata".to_string(),
|
||||
"**/.hidden_subdir".to_string(),
|
||||
]);
|
||||
settings.project.worktree.private_files = Some(
|
||||
vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
settings.project.worktree.private_files = Some(vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -704,8 +701,7 @@ mod tests {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||
settings.project.worktree.private_files =
|
||||
Some(vec!["**/.env".to_string()].into());
|
||||
settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -684,14 +684,11 @@ mod test {
|
||||
"**/.secretdir".to_string(),
|
||||
"**/.mymetadata".to_string(),
|
||||
]);
|
||||
settings.project.worktree.private_files = Some(
|
||||
vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
settings.project.worktree.private_files = Some(vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -973,8 +970,7 @@ mod test {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||
settings.project.worktree.private_files =
|
||||
Some(vec!["**/.env".to_string()].into());
|
||||
settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use gpui::App;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use util::MergeFrom as _;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AudioSettings {
|
||||
@@ -22,7 +23,7 @@ pub struct AudioSettings {
|
||||
|
||||
/// Configuration of audio in Zed
|
||||
impl Settings for AudioSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let audio = &content.audio.as_ref().unwrap();
|
||||
AudioSettings {
|
||||
control_input_volume: audio.control_input_volume.unwrap(),
|
||||
@@ -31,6 +32,17 @@ impl Settings for AudioSettings {
|
||||
}
|
||||
}
|
||||
|
||||
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
|
||||
let Some(audio) = content.audio.as_ref() else {
|
||||
return;
|
||||
};
|
||||
self.control_input_volume
|
||||
.merge_from(&audio.control_input_volume);
|
||||
self.control_output_volume
|
||||
.merge_from(&audio.control_output_volume);
|
||||
self.rodio_audio.merge_from(&audio.rodio_audio);
|
||||
}
|
||||
|
||||
fn import_from_vscode(
|
||||
_vscode: &settings::VsCodeSettings,
|
||||
_current: &mut settings::SettingsContent,
|
||||
|
||||
@@ -9,7 +9,7 @@ use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use paths::remote_servers_dir;
|
||||
use release_channel::{AppCommitSha, ReleaseChannel};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use settings::{Settings, SettingsContent, SettingsStore};
|
||||
use smol::{fs, io::AsyncReadExt};
|
||||
use smol::{fs::File, process::Command};
|
||||
use std::{
|
||||
@@ -119,9 +119,21 @@ struct AutoUpdateSetting(bool);
|
||||
///
|
||||
/// Default: true
|
||||
impl Settings for AutoUpdateSetting {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
debug_assert_eq!(content.auto_update.unwrap(), true);
|
||||
Self(content.auto_update.unwrap())
|
||||
}
|
||||
|
||||
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
|
||||
if let Some(auto_update) = content.auto_update {
|
||||
self.0 = auto_update;
|
||||
}
|
||||
}
|
||||
|
||||
fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut SettingsContent) {
|
||||
// We could match on vscode's update.mode here, but
|
||||
// I think it's more important to have more people updating zed by default.
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
||||
@@ -111,13 +111,13 @@ impl sum_tree::Item for PendingHunk {
|
||||
}
|
||||
|
||||
impl sum_tree::Summary for DiffHunkSummary {
|
||||
type Context<'a> = &'a text::BufferSnapshot;
|
||||
type Context = text::BufferSnapshot;
|
||||
|
||||
fn zero(_cx: Self::Context<'_>) -> Self {
|
||||
fn zero(_cx: &Self::Context) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
|
||||
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
|
||||
self.buffer_range.start = self
|
||||
.buffer_range
|
||||
.start
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use gpui::App;
|
||||
use settings::Settings;
|
||||
use util::MergeFrom;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CallSettings {
|
||||
@@ -8,7 +9,7 @@ pub struct CallSettings {
|
||||
}
|
||||
|
||||
impl Settings for CallSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let call = content.calls.clone().unwrap();
|
||||
CallSettings {
|
||||
mute_on_join: call.mute_on_join.unwrap(),
|
||||
@@ -16,6 +17,13 @@ impl Settings for CallSettings {
|
||||
}
|
||||
}
|
||||
|
||||
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
|
||||
if let Some(call) = content.calls.clone() {
|
||||
self.mute_on_join.merge_from(&call.mute_on_join);
|
||||
self.share_on_join.merge_from(&call.share_on_join);
|
||||
}
|
||||
}
|
||||
|
||||
fn import_from_vscode(
|
||||
_vscode: &settings::VsCodeSettings,
|
||||
_current: &mut settings::SettingsContent,
|
||||
|
||||
@@ -20,8 +20,6 @@ use util::paths::PathWithPosition;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use std::io::IsTerminal;
|
||||
|
||||
const URL_PREFIX: [&'static str; 5] = ["zed://", "http://", "https://", "file://", "ssh://"];
|
||||
|
||||
struct Detect;
|
||||
|
||||
trait InstalledApp {
|
||||
@@ -312,7 +310,12 @@ fn main() -> Result<()> {
|
||||
let wsl = None;
|
||||
|
||||
for path in args.paths_with_position.iter() {
|
||||
if URL_PREFIX.iter().any(|&prefix| path.starts_with(prefix)) {
|
||||
if path.starts_with("zed://")
|
||||
|| path.starts_with("http://")
|
||||
|| path.starts_with("https://")
|
||||
|| path.starts_with("file://")
|
||||
|| path.starts_with("ssh://")
|
||||
{
|
||||
urls.push(path.to_string());
|
||||
} else if path == "-" && args.paths_with_position.len() == 1 {
|
||||
let file = NamedTempFile::new()?;
|
||||
|
||||
@@ -41,6 +41,7 @@ rand.workspace = true
|
||||
regex.workspace = true
|
||||
release_channel.workspace = true
|
||||
rpc = { workspace = true, features = ["gpui"] }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_urlencoded.workspace = true
|
||||
|
||||
@@ -29,8 +29,9 @@ use proxy::connect_proxy_stream;
|
||||
use rand::prelude::*;
|
||||
use release_channel::{AppVersion, ReleaseChannel};
|
||||
use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsContent};
|
||||
use settings::{Settings, SettingsContent, SettingsKey, SettingsUi};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
convert::TryFrom,
|
||||
@@ -49,7 +50,7 @@ use telemetry::Telemetry;
|
||||
use thiserror::Error;
|
||||
use tokio::net::TcpStream;
|
||||
use url::Url;
|
||||
use util::{ConnectionResult, ResultExt};
|
||||
use util::{ConnectionResult, MergeFrom, ResultExt};
|
||||
|
||||
pub use rpc::*;
|
||||
pub use telemetry_events::Event;
|
||||
@@ -101,7 +102,7 @@ pub struct ClientSettings {
|
||||
}
|
||||
|
||||
impl Settings for ClientSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
if let Some(server_url) = &*ZED_SERVER_URL {
|
||||
return Self {
|
||||
server_url: server_url.clone(),
|
||||
@@ -111,6 +112,23 @@ impl Settings for ClientSettings {
|
||||
server_url: content.server_url.clone().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) {
|
||||
if ZED_SERVER_URL.is_some() {
|
||||
return;
|
||||
}
|
||||
if let Some(server_url) = content.server_url.clone() {
|
||||
self.server_url = server_url;
|
||||
}
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
|
||||
#[settings_key(None)]
|
||||
pub struct ProxySettingsContent {
|
||||
proxy: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
@@ -133,12 +151,18 @@ impl ProxySettings {
|
||||
}
|
||||
|
||||
impl Settings for ProxySettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
Self {
|
||||
proxy: content.proxy.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) {
|
||||
if let Some(proxy) = content.proxy.clone() {
|
||||
self.proxy = Some(proxy)
|
||||
}
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
|
||||
vscode.string_setting("http.proxy", &mut current.proxy);
|
||||
}
|
||||
@@ -519,13 +543,21 @@ pub struct TelemetrySettings {
|
||||
}
|
||||
|
||||
impl settings::Settings for TelemetrySettings {
|
||||
fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self {
|
||||
fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self {
|
||||
Self {
|
||||
diagnostics: content.telemetry.as_ref().unwrap().diagnostics.unwrap(),
|
||||
metrics: content.telemetry.as_ref().unwrap().metrics.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn refine(&mut self, content: &SettingsContent, _cx: &mut App) {
|
||||
let Some(telemetry) = &content.telemetry else {
|
||||
return;
|
||||
};
|
||||
self.diagnostics.merge_from(&telemetry.diagnostics);
|
||||
self.metrics.merge_from(&telemetry.metrics);
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
|
||||
let mut telemetry = settings::TelemetrySettingsContent::default();
|
||||
vscode.enum_setting("telemetry.telemetryLevel", &mut telemetry.metrics, |s| {
|
||||
|
||||
@@ -9,7 +9,7 @@ use futures::AsyncReadExt as _;
|
||||
use gpui::{App, Task};
|
||||
use gpui_tokio::Tokio;
|
||||
use http_client::http::request;
|
||||
use http_client::{AsyncBody, HttpClientWithUrl, HttpRequestExt, Method, Request, StatusCode};
|
||||
use http_client::{AsyncBody, HttpClientWithUrl, Method, Request, StatusCode};
|
||||
use parking_lot::RwLock;
|
||||
use yawc::WebSocket;
|
||||
|
||||
@@ -119,16 +119,15 @@ impl CloudApiClient {
|
||||
&self,
|
||||
system_id: Option<String>,
|
||||
) -> Result<CreateLlmTokenResponse> {
|
||||
let request_builder = Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(
|
||||
self.http_client
|
||||
.build_zed_cloud_url("/client/llm_tokens", &[])?
|
||||
.as_ref(),
|
||||
)
|
||||
.when_some(system_id, |builder, system_id| {
|
||||
builder.header(ZED_SYSTEM_ID_HEADER_NAME, system_id)
|
||||
});
|
||||
let mut request_builder = Request::builder().method(Method::POST).uri(
|
||||
self.http_client
|
||||
.build_zed_cloud_url("/client/llm_tokens", &[])?
|
||||
.as_ref(),
|
||||
);
|
||||
|
||||
if let Some(system_id) = system_id {
|
||||
request_builder = request_builder.header(ZED_SYSTEM_ID_HEADER_NAME, system_id);
|
||||
}
|
||||
|
||||
let request = self.build_request(request_builder, AsyncBody::default())?;
|
||||
|
||||
|
||||
@@ -24,15 +24,11 @@ pub struct PredictEditsRequest {
|
||||
pub can_collect_data: bool,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub diagnostic_groups: Vec<DiagnosticGroup>,
|
||||
#[serde(skip_serializing_if = "is_default", default)]
|
||||
pub diagnostic_groups_truncated: bool,
|
||||
/// Info about the git repository state, only present when can_collect_data is true.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub git_info: Option<PredictEditsGitInfo>,
|
||||
// Only available to staff
|
||||
#[serde(default)]
|
||||
pub debug_info: bool,
|
||||
pub prompt_max_bytes: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -52,9 +48,6 @@ pub struct Signature {
|
||||
pub text_is_truncated: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub parent_index: Option<usize>,
|
||||
/// Range of `text` within the file, possibly truncated according to `text_is_truncated`. The
|
||||
/// file is implicitly the file that contains the descendant declaration or excerpt.
|
||||
pub range: Range<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -62,7 +55,7 @@ pub struct ReferencedDeclaration {
|
||||
pub path: PathBuf,
|
||||
pub text: String,
|
||||
pub text_is_truncated: bool,
|
||||
/// Range of `text` within file, possibly truncated according to `text_is_truncated`
|
||||
/// Range of `text` within file, potentially truncated according to `text_is_truncated`
|
||||
pub range: Range<usize>,
|
||||
/// Range within `text`
|
||||
pub signature_range: Range<usize>,
|
||||
@@ -96,8 +89,10 @@ pub struct ScoreComponents {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct DiagnosticGroup(pub Box<serde_json::value::RawValue>);
|
||||
pub struct DiagnosticGroup {
|
||||
pub language_server: String,
|
||||
pub diagnostic_group: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PredictEditsResponse {
|
||||
@@ -122,6 +117,56 @@ pub struct Edit {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
|
||||
*value == T::default()
|
||||
/*
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SerializedJson<T> {
|
||||
raw: Box<RawValue>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> SerializedJson<T>
|
||||
where
|
||||
T: Serialize + for<'de> Deserialize<'de>,
|
||||
{
|
||||
pub fn new(value: &T) -> Result<Self, serde_json::Error> {
|
||||
Ok(SerializedJson {
|
||||
raw: serde_json::value::to_raw_value(value)?,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deserialize(&self) -> Result<T, serde_json::Error> {
|
||||
serde_json::from_str(self.raw.get())
|
||||
}
|
||||
|
||||
pub fn as_raw(&self) -> &RawValue {
|
||||
&self.raw
|
||||
}
|
||||
|
||||
pub fn into_raw(self) -> Box<RawValue> {
|
||||
self.raw
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Serialize for SerializedJson<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.raw.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Deserialize<'de> for SerializedJson<T> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let raw = Box::<RawValue>::deserialize(deserializer)?;
|
||||
Ok(SerializedJson {
|
||||
raw,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,461 +0,0 @@
|
||||
//! Zeta2 prompt planning and generation code shared with cloud.
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use cloud_llm_client::predict_edits_v3::{self, Event, ReferencedDeclaration};
|
||||
use indoc::indoc;
|
||||
use ordered_float::OrderedFloat;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::fmt::Write;
|
||||
use std::{cmp::Reverse, collections::BinaryHeap, ops::Range, path::Path};
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
|
||||
pub const DEFAULT_MAX_PROMPT_BYTES: usize = 10 * 1024;
|
||||
|
||||
pub const CURSOR_MARKER: &str = "<|user_cursor_is_here|>";
|
||||
/// NOTE: Differs from zed version of constant - includes a newline
|
||||
pub const EDITABLE_REGION_START_MARKER_WITH_NEWLINE: &str = "<|editable_region_start|>\n";
|
||||
/// NOTE: Differs from zed version of constant - includes a newline
|
||||
pub const EDITABLE_REGION_END_MARKER_WITH_NEWLINE: &str = "<|editable_region_end|>\n";
|
||||
|
||||
// TODO: use constants for markers?
|
||||
pub const SYSTEM_PROMPT: &str = indoc! {"
|
||||
You are a code completion assistant and your task is to analyze user edits and then rewrite an excerpt that the user provides, suggesting the appropriate edits within the excerpt, taking into account the cursor location.
|
||||
|
||||
The excerpt to edit will be wrapped in markers <|editable_region_start|> and <|editable_region_end|>. The cursor position is marked with <|user_cursor_is_here|>. Please respond with edited code for that region.
|
||||
"};
|
||||
|
||||
pub struct PlannedPrompt<'a> {
|
||||
request: &'a predict_edits_v3::PredictEditsRequest,
|
||||
/// Snippets to include in the prompt. These may overlap - they are merged / deduplicated in
|
||||
/// `to_prompt_string`.
|
||||
snippets: Vec<PlannedSnippet<'a>>,
|
||||
budget_used: usize,
|
||||
}
|
||||
|
||||
pub struct PlanOptions {
|
||||
pub max_bytes: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PlannedSnippet<'a> {
|
||||
path: &'a Path,
|
||||
range: Range<usize>,
|
||||
text: &'a str,
|
||||
// TODO: Indicate this in the output
|
||||
#[allow(dead_code)]
|
||||
text_is_truncated: bool,
|
||||
}
|
||||
|
||||
#[derive(EnumIter, Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||
pub enum SnippetStyle {
|
||||
Signature,
|
||||
Declaration,
|
||||
}
|
||||
|
||||
impl<'a> PlannedPrompt<'a> {
|
||||
/// Greedy one-pass knapsack algorithm to populate the prompt plan. Does the following:
|
||||
///
|
||||
/// Initializes a priority queue by populating it with each snippet, finding the SnippetStyle
|
||||
/// that minimizes `score_density = score / snippet.range(style).len()`. When a "signature"
|
||||
/// snippet is popped, insert an entry for the "declaration" variant that reflects the cost of
|
||||
/// upgrade.
|
||||
///
|
||||
/// TODO: Implement an early halting condition. One option might be to have another priority
|
||||
/// queue where the score is the size, and update it accordingly. Another option might be to
|
||||
/// have some simpler heuristic like bailing after N failed insertions, or based on how much
|
||||
/// budget is left.
|
||||
///
|
||||
/// TODO: Has the current known sources of imprecision:
|
||||
///
|
||||
/// * Does not consider snippet overlap when ranking. For example, it might add a field to the
|
||||
/// plan even though the containing struct is already included.
|
||||
///
|
||||
/// * Does not consider cost of signatures when ranking snippets - this is tricky since
|
||||
/// signatures may be shared by multiple snippets.
|
||||
///
|
||||
/// * Does not include file paths / other text when considering max_bytes.
|
||||
pub fn populate(
|
||||
request: &'a predict_edits_v3::PredictEditsRequest,
|
||||
options: &PlanOptions,
|
||||
) -> Result<Self> {
|
||||
let mut this = PlannedPrompt {
|
||||
request,
|
||||
snippets: Vec::new(),
|
||||
budget_used: request.excerpt.len(),
|
||||
};
|
||||
let mut included_parents = FxHashSet::default();
|
||||
let additional_parents = this.additional_parent_signatures(
|
||||
&request.excerpt_path,
|
||||
request.excerpt_parent,
|
||||
&included_parents,
|
||||
)?;
|
||||
this.add_parents(&mut included_parents, additional_parents);
|
||||
|
||||
if this.budget_used > options.max_bytes {
|
||||
return Err(anyhow!(
|
||||
"Excerpt + signatures size of {} already exceeds budget of {}",
|
||||
this.budget_used,
|
||||
options.max_bytes
|
||||
));
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct QueueEntry {
|
||||
score_density: OrderedFloat<f32>,
|
||||
declaration_index: usize,
|
||||
style: SnippetStyle,
|
||||
}
|
||||
|
||||
// Initialize priority queue with the best score for each snippet.
|
||||
let mut queue: BinaryHeap<QueueEntry> = BinaryHeap::new();
|
||||
for (declaration_index, declaration) in request.referenced_declarations.iter().enumerate() {
|
||||
let (style, score_density) = SnippetStyle::iter()
|
||||
.map(|style| {
|
||||
(
|
||||
style,
|
||||
OrderedFloat(declaration_score_density(&declaration, style)),
|
||||
)
|
||||
})
|
||||
.max_by_key(|(_, score_density)| *score_density)
|
||||
.unwrap();
|
||||
queue.push(QueueEntry {
|
||||
score_density,
|
||||
declaration_index,
|
||||
style,
|
||||
});
|
||||
}
|
||||
|
||||
// Knapsack selection loop
|
||||
while let Some(queue_entry) = queue.pop() {
|
||||
let Some(declaration) = request
|
||||
.referenced_declarations
|
||||
.get(queue_entry.declaration_index)
|
||||
else {
|
||||
return Err(anyhow!(
|
||||
"Invalid declaration index {}",
|
||||
queue_entry.declaration_index
|
||||
));
|
||||
};
|
||||
|
||||
let mut additional_bytes = declaration_size(declaration, queue_entry.style);
|
||||
if this.budget_used + additional_bytes > options.max_bytes {
|
||||
continue;
|
||||
}
|
||||
|
||||
let additional_parents = this.additional_parent_signatures(
|
||||
&declaration.path,
|
||||
declaration.parent_index,
|
||||
&mut included_parents,
|
||||
)?;
|
||||
additional_bytes += additional_parents
|
||||
.iter()
|
||||
.map(|(_, snippet)| snippet.text.len())
|
||||
.sum::<usize>();
|
||||
if this.budget_used + additional_bytes > options.max_bytes {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.budget_used += additional_bytes;
|
||||
this.add_parents(&mut included_parents, additional_parents);
|
||||
let planned_snippet = match queue_entry.style {
|
||||
SnippetStyle::Signature => {
|
||||
let Some(text) = declaration.text.get(declaration.signature_range.clone())
|
||||
else {
|
||||
return Err(anyhow!(
|
||||
"Invalid declaration signature_range {:?} with text.len() = {}",
|
||||
declaration.signature_range,
|
||||
declaration.text.len()
|
||||
));
|
||||
};
|
||||
PlannedSnippet {
|
||||
path: &declaration.path,
|
||||
range: (declaration.signature_range.start + declaration.range.start)
|
||||
..(declaration.signature_range.end + declaration.range.start),
|
||||
text,
|
||||
text_is_truncated: declaration.text_is_truncated,
|
||||
}
|
||||
}
|
||||
SnippetStyle::Declaration => PlannedSnippet {
|
||||
path: &declaration.path,
|
||||
range: declaration.range.clone(),
|
||||
text: &declaration.text,
|
||||
text_is_truncated: declaration.text_is_truncated,
|
||||
},
|
||||
};
|
||||
this.snippets.push(planned_snippet);
|
||||
|
||||
// When a Signature is consumed, insert an entry for Definition style.
|
||||
if queue_entry.style == SnippetStyle::Signature {
|
||||
let signature_size = declaration_size(&declaration, SnippetStyle::Signature);
|
||||
let declaration_size = declaration_size(&declaration, SnippetStyle::Declaration);
|
||||
let signature_score = declaration_score(&declaration, SnippetStyle::Signature);
|
||||
let declaration_score = declaration_score(&declaration, SnippetStyle::Declaration);
|
||||
|
||||
let score_diff = declaration_score - signature_score;
|
||||
let size_diff = declaration_size.saturating_sub(signature_size);
|
||||
if score_diff > 0.0001 && size_diff > 0 {
|
||||
queue.push(QueueEntry {
|
||||
declaration_index: queue_entry.declaration_index,
|
||||
score_density: OrderedFloat(score_diff / (size_diff as f32)),
|
||||
style: SnippetStyle::Declaration,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::Ok(this)
|
||||
}
|
||||
|
||||
fn add_parents(
|
||||
&mut self,
|
||||
included_parents: &mut FxHashSet<usize>,
|
||||
snippets: Vec<(usize, PlannedSnippet<'a>)>,
|
||||
) {
|
||||
for (parent_index, snippet) in snippets {
|
||||
included_parents.insert(parent_index);
|
||||
self.budget_used += snippet.text.len();
|
||||
self.snippets.push(snippet);
|
||||
}
|
||||
}
|
||||
|
||||
fn additional_parent_signatures(
|
||||
&self,
|
||||
path: &'a Path,
|
||||
parent_index: Option<usize>,
|
||||
included_parents: &FxHashSet<usize>,
|
||||
) -> Result<Vec<(usize, PlannedSnippet<'a>)>> {
|
||||
let mut results = Vec::new();
|
||||
self.additional_parent_signatures_impl(path, parent_index, included_parents, &mut results)?;
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
fn additional_parent_signatures_impl(
|
||||
&self,
|
||||
path: &'a Path,
|
||||
parent_index: Option<usize>,
|
||||
included_parents: &FxHashSet<usize>,
|
||||
results: &mut Vec<(usize, PlannedSnippet<'a>)>,
|
||||
) -> Result<()> {
|
||||
let Some(parent_index) = parent_index else {
|
||||
return Ok(());
|
||||
};
|
||||
if included_parents.contains(&parent_index) {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(parent_signature) = self.request.signatures.get(parent_index) else {
|
||||
return Err(anyhow!("Invalid parent index {}", parent_index));
|
||||
};
|
||||
results.push((
|
||||
parent_index,
|
||||
PlannedSnippet {
|
||||
path,
|
||||
range: parent_signature.range.clone(),
|
||||
text: &parent_signature.text,
|
||||
text_is_truncated: parent_signature.text_is_truncated,
|
||||
},
|
||||
));
|
||||
self.additional_parent_signatures_impl(
|
||||
path,
|
||||
parent_signature.parent_index,
|
||||
included_parents,
|
||||
results,
|
||||
)
|
||||
}
|
||||
|
||||
/// Renders the planned context. Each file starts with "```FILE_PATH\n` and ends with triple
|
||||
/// backticks, with a newline after each file. Outputs a line with "..." between nonconsecutive
|
||||
/// chunks.
|
||||
pub fn to_prompt_string(&self) -> String {
|
||||
let mut file_to_snippets: FxHashMap<&'a std::path::Path, Vec<&PlannedSnippet<'a>>> =
|
||||
FxHashMap::default();
|
||||
for snippet in &self.snippets {
|
||||
file_to_snippets
|
||||
.entry(&snippet.path)
|
||||
.or_default()
|
||||
.push(snippet);
|
||||
}
|
||||
|
||||
// Reorder so that file with cursor comes last
|
||||
let mut file_snippets = Vec::new();
|
||||
let mut excerpt_file_snippets = Vec::new();
|
||||
for (file_path, snippets) in file_to_snippets {
|
||||
if file_path == &self.request.excerpt_path {
|
||||
excerpt_file_snippets = snippets;
|
||||
} else {
|
||||
file_snippets.push((file_path, snippets, false));
|
||||
}
|
||||
}
|
||||
let excerpt_snippet = PlannedSnippet {
|
||||
path: &self.request.excerpt_path,
|
||||
range: self.request.excerpt_range.clone(),
|
||||
text: &self.request.excerpt,
|
||||
text_is_truncated: false,
|
||||
};
|
||||
excerpt_file_snippets.push(&excerpt_snippet);
|
||||
file_snippets.push((&self.request.excerpt_path, excerpt_file_snippets, true));
|
||||
|
||||
let mut excerpt_file_insertions = vec![
|
||||
(
|
||||
self.request.excerpt_range.start,
|
||||
EDITABLE_REGION_START_MARKER_WITH_NEWLINE,
|
||||
),
|
||||
(
|
||||
self.request.excerpt_range.start + self.request.cursor_offset,
|
||||
CURSOR_MARKER,
|
||||
),
|
||||
(
|
||||
self.request
|
||||
.excerpt_range
|
||||
.end
|
||||
.saturating_sub(0)
|
||||
.max(self.request.excerpt_range.start),
|
||||
EDITABLE_REGION_END_MARKER_WITH_NEWLINE,
|
||||
),
|
||||
];
|
||||
|
||||
let mut output = String::new();
|
||||
output.push_str("## User Edits\n\n");
|
||||
Self::push_events(&mut output, &self.request.events);
|
||||
|
||||
output.push_str("\n## Code\n\n");
|
||||
Self::push_file_snippets(&mut output, &mut excerpt_file_insertions, file_snippets);
|
||||
output
|
||||
}
|
||||
|
||||
fn push_events(output: &mut String, events: &[predict_edits_v3::Event]) {
|
||||
for event in events {
|
||||
match event {
|
||||
Event::BufferChange {
|
||||
path,
|
||||
old_path,
|
||||
diff,
|
||||
predicted,
|
||||
} => {
|
||||
if let Some(old_path) = &old_path
|
||||
&& let Some(new_path) = &path
|
||||
{
|
||||
if old_path != new_path {
|
||||
writeln!(
|
||||
output,
|
||||
"User renamed {} to {}\n\n",
|
||||
old_path.display(),
|
||||
new_path.display()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let path = path
|
||||
.as_ref()
|
||||
.map_or_else(|| "untitled".to_string(), |path| path.display().to_string());
|
||||
|
||||
if *predicted {
|
||||
writeln!(
|
||||
output,
|
||||
"User accepted prediction {:?}:\n```diff\n{}\n```\n",
|
||||
path, diff
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(output, "User edited {:?}:\n```diff\n{}\n```\n", path, diff)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_file_snippets(
|
||||
output: &mut String,
|
||||
excerpt_file_insertions: &mut Vec<(usize, &'static str)>,
|
||||
file_snippets: Vec<(&Path, Vec<&PlannedSnippet>, bool)>,
|
||||
) {
|
||||
fn push_excerpt_file_range(
|
||||
range: Range<usize>,
|
||||
text: &str,
|
||||
excerpt_file_insertions: &mut Vec<(usize, &'static str)>,
|
||||
output: &mut String,
|
||||
) {
|
||||
let mut last_offset = range.start;
|
||||
let mut i = 0;
|
||||
while i < excerpt_file_insertions.len() {
|
||||
let (offset, insertion) = &excerpt_file_insertions[i];
|
||||
let found = *offset >= range.start && *offset <= range.end;
|
||||
if found {
|
||||
output.push_str(&text[last_offset - range.start..offset - range.start]);
|
||||
output.push_str(insertion);
|
||||
last_offset = *offset;
|
||||
excerpt_file_insertions.remove(i);
|
||||
continue;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
output.push_str(&text[last_offset - range.start..]);
|
||||
}
|
||||
|
||||
for (file_path, mut snippets, is_excerpt_file) in file_snippets {
|
||||
output.push_str(&format!("```{}\n", file_path.display()));
|
||||
|
||||
let mut last_included_range: Option<Range<usize>> = None;
|
||||
snippets.sort_by_key(|s| (s.range.start, Reverse(s.range.end)));
|
||||
for snippet in snippets {
|
||||
if let Some(last_range) = &last_included_range
|
||||
&& snippet.range.start < last_range.end
|
||||
{
|
||||
if snippet.range.end <= last_range.end {
|
||||
continue;
|
||||
}
|
||||
// TODO: Should probably also handle case where there is just one char (newline)
|
||||
// between snippets - assume it's a newline.
|
||||
let text = &snippet.text[last_range.end - snippet.range.start..];
|
||||
if is_excerpt_file {
|
||||
push_excerpt_file_range(
|
||||
last_range.end..snippet.range.end,
|
||||
text,
|
||||
excerpt_file_insertions,
|
||||
output,
|
||||
);
|
||||
} else {
|
||||
output.push_str(text);
|
||||
}
|
||||
last_included_range = Some(last_range.start..snippet.range.end);
|
||||
continue;
|
||||
}
|
||||
if last_included_range.is_some() {
|
||||
output.push_str("…\n");
|
||||
}
|
||||
if is_excerpt_file {
|
||||
push_excerpt_file_range(
|
||||
snippet.range.clone(),
|
||||
snippet.text,
|
||||
excerpt_file_insertions,
|
||||
output,
|
||||
);
|
||||
} else {
|
||||
output.push_str(snippet.text);
|
||||
}
|
||||
last_included_range = Some(snippet.range.clone());
|
||||
}
|
||||
|
||||
output.push_str("```\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn declaration_score_density(declaration: &ReferencedDeclaration, style: SnippetStyle) -> f32 {
|
||||
declaration_score(declaration, style) / declaration_size(declaration, style) as f32
|
||||
}
|
||||
|
||||
fn declaration_score(declaration: &ReferencedDeclaration, style: SnippetStyle) -> f32 {
|
||||
match style {
|
||||
SnippetStyle::Signature => declaration.signature_score,
|
||||
SnippetStyle::Declaration => declaration.declaration_score,
|
||||
}
|
||||
}
|
||||
|
||||
fn declaration_size(declaration: &ReferencedDeclaration, style: SnippetStyle) -> usize {
|
||||
match style {
|
||||
SnippetStyle::Signature => declaration.signature_range.len(),
|
||||
SnippetStyle::Declaration => declaration.text.len(),
|
||||
}
|
||||
}
|
||||
@@ -1129,3 +1129,8 @@ async fn max_order(parent_path: &str, tx: &TransactionHandle) -> Result<i32> {
|
||||
enum QueryIds {
|
||||
Id,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
enum QueryUserIds {
|
||||
UserId,
|
||||
}
|
||||
|
||||
@@ -1895,13 +1895,13 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
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);
|
||||
let task_edits_made = 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);
|
||||
let edits_made = task_edits_made.load(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()),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use gpui::Pixels;
|
||||
use settings::Settings;
|
||||
use ui::px;
|
||||
use util::MergeFrom as _;
|
||||
use workspace::dock::DockPosition;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -18,7 +19,7 @@ pub struct NotificationPanelSettings {
|
||||
}
|
||||
|
||||
impl Settings for CollaborationPanelSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
|
||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
|
||||
let panel = content.collaboration_panel.as_ref().unwrap();
|
||||
|
||||
Self {
|
||||
@@ -27,10 +28,25 @@ impl Settings for CollaborationPanelSettings {
|
||||
default_width: panel.default_width.map(px).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) {
|
||||
if let Some(panel) = content.collaboration_panel.as_ref() {
|
||||
self.button.merge_from(&panel.button);
|
||||
self.default_width
|
||||
.merge_from(&panel.default_width.map(Pixels::from));
|
||||
self.dock.merge_from(&panel.dock.map(Into::into));
|
||||
}
|
||||
}
|
||||
|
||||
fn import_from_vscode(
|
||||
_vscode: &settings::VsCodeSettings,
|
||||
_content: &mut settings::SettingsContent,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings for NotificationPanelSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
|
||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
|
||||
let panel = content.notification_panel.as_ref().unwrap();
|
||||
return Self {
|
||||
button: panel.button.unwrap(),
|
||||
@@ -38,4 +54,19 @@ impl Settings for NotificationPanelSettings {
|
||||
default_width: panel.default_width.map(px).unwrap(),
|
||||
};
|
||||
}
|
||||
|
||||
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) {
|
||||
let Some(panel) = content.notification_panel.as_ref() else {
|
||||
return;
|
||||
};
|
||||
self.button.merge_from(&panel.button);
|
||||
self.dock.merge_from(&panel.dock.map(Into::into));
|
||||
self.default_width.merge_from(&panel.default_width.map(px));
|
||||
}
|
||||
|
||||
fn import_from_vscode(
|
||||
_vscode: &settings::VsCodeSettings,
|
||||
_current: &mut settings::SettingsContent,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ pub type IndexMap<K, V> = indexmap::IndexMap<K, V>;
|
||||
#[cfg(not(feature = "test-support"))]
|
||||
pub type IndexSet<T> = indexmap::IndexSet<T>;
|
||||
|
||||
pub use indexmap::Equivalent;
|
||||
pub use rustc_hash::FxHasher;
|
||||
pub use rustc_hash::{FxHashMap, FxHashSet};
|
||||
pub use std::collections::*;
|
||||
|
||||
@@ -10,7 +10,6 @@ use db::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
pub(crate) struct SerializedCommandInvocation {
|
||||
pub(crate) command_name: String,
|
||||
@@ -40,7 +39,6 @@ impl Column for SerializedCommandUsage {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl Column for SerializedCommandInvocation {
|
||||
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
||||
let (command_name, next_index): (String, i32) = Column::column(statement, start_index)?;
|
||||
@@ -86,9 +84,8 @@ impl CommandPaletteDB {
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
query! {
|
||||
pub(crate) fn get_last_invoked(command: &str) -> Result<Option<SerializedCommandInvocation>> {
|
||||
pub fn get_last_invoked(command: &str) -> Result<Option<SerializedCommandInvocation>> {
|
||||
SELECT
|
||||
command_name,
|
||||
user_query,
|
||||
|
||||
@@ -438,3 +438,13 @@ struct RawRequest {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
params: Option<Box<serde_json::value::RawValue>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RawResponse {
|
||||
jsonrpc: &'static str,
|
||||
id: RequestId,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
error: Option<crate::client::Error>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
result: Option<Box<serde_json::value::RawValue>>,
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use fs::Fs;
|
||||
use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};
|
||||
use gpui::WeakEntity;
|
||||
use gpui::{App, AsyncApp, Global, prelude::*};
|
||||
use http_client::HttpRequestExt;
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use itertools::Itertools;
|
||||
use paths::home_dir;
|
||||
@@ -742,7 +741,7 @@ async fn stream_completion(
|
||||
|
||||
let request_initiator = if is_user_initiated { "user" } else { "agent" };
|
||||
|
||||
let request_builder = HttpRequest::builder()
|
||||
let mut request_builder = HttpRequest::builder()
|
||||
.method(Method::POST)
|
||||
.uri(completion_url.as_ref())
|
||||
.header(
|
||||
@@ -755,10 +754,12 @@ async fn stream_completion(
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Copilot-Integration-Id", "vscode-chat")
|
||||
.header("X-Initiator", request_initiator)
|
||||
.when(is_vision_request, |builder| {
|
||||
builder.header("Copilot-Vision-Request", is_vision_request.to_string())
|
||||
});
|
||||
.header("X-Initiator", request_initiator);
|
||||
|
||||
if is_vision_request {
|
||||
request_builder =
|
||||
request_builder.header("Copilot-Vision-Request", is_vision_request.to_string());
|
||||
}
|
||||
|
||||
let is_streaming = request.stream;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use dap_types::SteppingGranularity;
|
||||
use gpui::App;
|
||||
use settings::{Settings, SettingsContent};
|
||||
use util::MergeFrom;
|
||||
|
||||
pub struct DebuggerSettings {
|
||||
/// Determines the stepping granularity.
|
||||
@@ -34,7 +35,7 @@ pub struct DebuggerSettings {
|
||||
}
|
||||
|
||||
impl Settings for DebuggerSettings {
|
||||
fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self {
|
||||
fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self {
|
||||
let content = content.debugger.clone().unwrap();
|
||||
Self {
|
||||
stepping_granularity: dap_granularity_from_settings(
|
||||
@@ -48,6 +49,27 @@ impl Settings for DebuggerSettings {
|
||||
dock: content.dock.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn refine(&mut self, content: &SettingsContent, _cx: &mut App) {
|
||||
let Some(content) = &content.debugger else {
|
||||
return;
|
||||
};
|
||||
self.stepping_granularity.merge_from(
|
||||
&content
|
||||
.stepping_granularity
|
||||
.map(dap_granularity_from_settings),
|
||||
);
|
||||
self.save_breakpoints.merge_from(&content.save_breakpoints);
|
||||
self.button.merge_from(&content.button);
|
||||
self.timeout.merge_from(&content.timeout);
|
||||
self.log_dap_communications
|
||||
.merge_from(&content.log_dap_communications);
|
||||
self.format_dap_log_messages
|
||||
.merge_from(&content.format_dap_log_messages);
|
||||
self.dock.merge_from(&content.dock);
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {}
|
||||
}
|
||||
|
||||
fn dap_granularity_from_settings(
|
||||
|
||||
@@ -134,10 +134,6 @@ impl DebugPanel {
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
}
|
||||
|
||||
pub fn project(&self) -> &Entity<Project> {
|
||||
&self.project
|
||||
}
|
||||
|
||||
pub fn load(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
@@ -625,15 +621,6 @@ impl DebugPanel {
|
||||
})
|
||||
};
|
||||
|
||||
let edit_debug_json_button = || {
|
||||
IconButton::new("debug-edit-debug-json", IconName::Code)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(zed_actions::OpenProjectDebugTasks.boxed_clone(), cx);
|
||||
})
|
||||
.tooltip(Tooltip::text("Edit debug.json"))
|
||||
};
|
||||
|
||||
let documentation_button = || {
|
||||
IconButton::new("debug-open-documentation", IconName::CircleHelp)
|
||||
.icon_size(IconSize::Small)
|
||||
@@ -908,9 +895,8 @@ impl DebugPanel {
|
||||
)
|
||||
.when(is_side, |this| {
|
||||
this.child(new_session_button())
|
||||
.child(edit_debug_json_button())
|
||||
.child(documentation_button())
|
||||
.child(logs_button())
|
||||
.child(documentation_button())
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
@@ -961,9 +947,8 @@ impl DebugPanel {
|
||||
))
|
||||
.when(!is_side, |this| {
|
||||
this.child(new_session_button())
|
||||
.child(edit_debug_json_button())
|
||||
.child(documentation_button())
|
||||
.child(logs_button())
|
||||
.child(documentation_button())
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{Context as _, bail};
|
||||
use collections::{FxHashMap, HashMap, HashSet};
|
||||
use language::{LanguageName, LanguageRegistry};
|
||||
use language::LanguageRegistry;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
path::{Path, PathBuf},
|
||||
@@ -22,7 +22,7 @@ use itertools::Itertools as _;
|
||||
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
|
||||
use project::{DebugScenarioContext, Project, TaskContexts, TaskSourceKind, task_store::TaskStore};
|
||||
use settings::Settings;
|
||||
use task::{DebugScenario, RevealTarget, VariableName, ZedDebugConfig};
|
||||
use task::{DebugScenario, RevealTarget, ZedDebugConfig};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
|
||||
@@ -978,7 +978,6 @@ pub(super) struct DebugDelegate {
|
||||
task_store: Entity<TaskStore>,
|
||||
candidates: Vec<(
|
||||
Option<TaskSourceKind>,
|
||||
Option<LanguageName>,
|
||||
DebugScenario,
|
||||
Option<DebugScenarioContext>,
|
||||
)>,
|
||||
@@ -1006,89 +1005,28 @@ impl DebugDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_task_subtitle(
|
||||
&self,
|
||||
task_kind: &Option<TaskSourceKind>,
|
||||
context: &Option<DebugScenarioContext>,
|
||||
cx: &mut App,
|
||||
) -> Option<String> {
|
||||
match task_kind {
|
||||
Some(TaskSourceKind::Worktree {
|
||||
id: worktree_id,
|
||||
directory_in_worktree,
|
||||
..
|
||||
}) => self
|
||||
.debug_panel
|
||||
.update(cx, |debug_panel, cx| {
|
||||
let project = debug_panel.project().read(cx);
|
||||
let worktrees: Vec<_> = project.visible_worktrees(cx).collect();
|
||||
|
||||
let mut path = if worktrees.len() > 1
|
||||
&& let Some(worktree) = project.worktree_for_id(*worktree_id, cx)
|
||||
{
|
||||
let worktree_path = worktree.read(cx).abs_path();
|
||||
let full_path = worktree_path.join(directory_in_worktree);
|
||||
full_path
|
||||
} else {
|
||||
directory_in_worktree.clone()
|
||||
};
|
||||
|
||||
match path
|
||||
.components()
|
||||
.next_back()
|
||||
.and_then(|component| component.as_os_str().to_str())
|
||||
{
|
||||
Some(".zed") => {
|
||||
path.push("debug.json");
|
||||
}
|
||||
Some(".vscode") => {
|
||||
path.push("launch.json");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(path.display().to_string())
|
||||
})
|
||||
.unwrap_or_else(|_| Some(directory_in_worktree.display().to_string())),
|
||||
Some(TaskSourceKind::AbsPath { abs_path, .. }) => {
|
||||
Some(abs_path.to_string_lossy().into_owned())
|
||||
}
|
||||
Some(TaskSourceKind::Lsp { language_name, .. }) => {
|
||||
Some(format!("LSP: {language_name}"))
|
||||
}
|
||||
Some(TaskSourceKind::Language { .. }) => None,
|
||||
_ => context.clone().and_then(|ctx| {
|
||||
ctx.task_context
|
||||
.task_variables
|
||||
.get(&VariableName::RelativeFile)
|
||||
.map(|f| format!("in {f}"))
|
||||
.or_else(|| {
|
||||
ctx.task_context
|
||||
.task_variables
|
||||
.get(&VariableName::Dirname)
|
||||
.map(|d| format!("in {d}/"))
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_scenario_language(
|
||||
fn get_scenario_kind(
|
||||
languages: &Arc<LanguageRegistry>,
|
||||
dap_registry: &DapRegistry,
|
||||
scenario: DebugScenario,
|
||||
) -> (Option<LanguageName>, DebugScenario) {
|
||||
) -> (Option<TaskSourceKind>, DebugScenario) {
|
||||
let language_names = languages.language_names();
|
||||
let language_name = dap_registry.adapter_language(&scenario.adapter);
|
||||
let language = dap_registry
|
||||
.adapter_language(&scenario.adapter)
|
||||
.map(|language| TaskSourceKind::Language { name: language.0 });
|
||||
|
||||
let language_name = language_name.or_else(|| {
|
||||
let language = language.or_else(|| {
|
||||
scenario.label.split_whitespace().find_map(|word| {
|
||||
language_names
|
||||
.iter()
|
||||
.find(|name| name.as_ref().eq_ignore_ascii_case(word))
|
||||
.cloned()
|
||||
.map(|name| TaskSourceKind::Language {
|
||||
name: name.to_owned().into(),
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
(language_name, scenario)
|
||||
(language, scenario)
|
||||
}
|
||||
|
||||
pub fn tasks_loaded(
|
||||
@@ -1142,9 +1080,9 @@ impl DebugDelegate {
|
||||
this.delegate.candidates = recent
|
||||
.into_iter()
|
||||
.map(|(scenario, context)| {
|
||||
let (language_name, scenario) =
|
||||
Self::get_scenario_language(&languages, dap_registry, scenario);
|
||||
(None, language_name, scenario, Some(context))
|
||||
let (kind, scenario) =
|
||||
Self::get_scenario_kind(&languages, dap_registry, scenario);
|
||||
(kind, scenario, Some(context))
|
||||
})
|
||||
.chain(
|
||||
scenarios
|
||||
@@ -1159,9 +1097,9 @@ impl DebugDelegate {
|
||||
})
|
||||
.filter(|(_, scenario)| valid_adapters.contains(&scenario.adapter))
|
||||
.map(|(kind, scenario)| {
|
||||
let (language_name, scenario) =
|
||||
Self::get_scenario_language(&languages, dap_registry, scenario);
|
||||
(Some(kind), language_name, scenario, None)
|
||||
let (language, scenario) =
|
||||
Self::get_scenario_kind(&languages, dap_registry, scenario);
|
||||
(language.or(Some(kind)), scenario, None)
|
||||
}),
|
||||
)
|
||||
.collect();
|
||||
@@ -1207,7 +1145,7 @@ impl PickerDelegate for DebugDelegate {
|
||||
let candidates: Vec<_> = candidates
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, (_, _, candidate, _))| {
|
||||
.map(|(index, (_, candidate, _))| {
|
||||
StringMatchCandidate::new(index, candidate.label.as_ref())
|
||||
})
|
||||
.collect();
|
||||
@@ -1376,7 +1314,7 @@ impl PickerDelegate for DebugDelegate {
|
||||
.get(self.selected_index())
|
||||
.and_then(|match_candidate| self.candidates.get(match_candidate.candidate_id).cloned());
|
||||
|
||||
let Some((kind, _, debug_scenario, context)) = debug_scenario else {
|
||||
let Some((kind, debug_scenario, context)) = debug_scenario else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1509,47 +1447,40 @@ impl PickerDelegate for DebugDelegate {
|
||||
cx: &mut Context<picker::Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let hit = &self.matches.get(ix)?;
|
||||
let (task_kind, language_name, _scenario, context) = &self.candidates[hit.candidate_id];
|
||||
|
||||
let highlighted_location = HighlightedMatch {
|
||||
text: hit.string.clone(),
|
||||
highlight_positions: hit.positions.clone(),
|
||||
char_count: hit.string.chars().count(),
|
||||
color: Color::Default,
|
||||
};
|
||||
let task_kind = &self.candidates[hit.candidate_id].0;
|
||||
|
||||
let subtitle = self.get_task_subtitle(task_kind, context, cx);
|
||||
|
||||
let language_icon = language_name.as_ref().and_then(|lang| {
|
||||
file_icons::FileIcons::get(cx)
|
||||
.get_icon_for_type(&lang.0.to_lowercase(), cx)
|
||||
.map(Icon::from_path)
|
||||
});
|
||||
|
||||
let (icon, indicator) = match task_kind {
|
||||
Some(TaskSourceKind::UserInput) => (Some(Icon::new(IconName::Terminal)), None),
|
||||
Some(TaskSourceKind::AbsPath { .. }) => (Some(Icon::new(IconName::Settings)), None),
|
||||
Some(TaskSourceKind::Worktree { .. }) => (Some(Icon::new(IconName::FileTree)), None),
|
||||
Some(TaskSourceKind::Lsp { language_name, .. }) => (
|
||||
file_icons::FileIcons::get(cx)
|
||||
.get_icon_for_type(&language_name.to_lowercase(), cx)
|
||||
.map(Icon::from_path),
|
||||
Some(Indicator::icon(
|
||||
Icon::new(IconName::BoltFilled)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small),
|
||||
)),
|
||||
),
|
||||
Some(TaskSourceKind::Language { name }) => (
|
||||
file_icons::FileIcons::get(cx)
|
||||
.get_icon_for_type(&name.to_lowercase(), cx)
|
||||
.map(Icon::from_path),
|
||||
None,
|
||||
),
|
||||
None => (Some(Icon::new(IconName::HistoryRerun)), None),
|
||||
let icon = match task_kind {
|
||||
Some(TaskSourceKind::UserInput) => Some(Icon::new(IconName::Terminal)),
|
||||
Some(TaskSourceKind::AbsPath { .. }) => Some(Icon::new(IconName::Settings)),
|
||||
Some(TaskSourceKind::Worktree { .. }) => Some(Icon::new(IconName::FileTree)),
|
||||
Some(TaskSourceKind::Lsp {
|
||||
language_name: name,
|
||||
..
|
||||
})
|
||||
| Some(TaskSourceKind::Language { name }) => file_icons::FileIcons::get(cx)
|
||||
.get_icon_for_type(&name.to_lowercase(), cx)
|
||||
.map(Icon::from_path),
|
||||
None => Some(Icon::new(IconName::HistoryRerun)),
|
||||
}
|
||||
.map(|icon| icon.color(Color::Muted).size(IconSize::Small));
|
||||
let indicator = if matches!(task_kind, Some(TaskSourceKind::Lsp { .. })) {
|
||||
Some(Indicator::icon(
|
||||
Icon::new(IconName::BoltFilled)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let icon = language_icon.or(icon).map(|icon| {
|
||||
IconWithIndicator::new(icon.color(Color::Muted).size(IconSize::Small), indicator)
|
||||
let icon = icon.map(|icon| {
|
||||
IconWithIndicator::new(icon, indicator)
|
||||
.indicator_border_color(Some(cx.theme().colors().border_transparent))
|
||||
});
|
||||
|
||||
@@ -1559,18 +1490,7 @@ impl PickerDelegate for DebugDelegate {
|
||||
.start_slot::<IconWithIndicator>(icon)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.child(
|
||||
v_flex()
|
||||
.items_start()
|
||||
.child(highlighted_location.render(window, cx))
|
||||
.when_some(subtitle, |this, subtitle_text| {
|
||||
this.child(
|
||||
Label::new(subtitle_text)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
}),
|
||||
),
|
||||
.child(highlighted_location.render(window, cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1619,17 +1539,4 @@ impl NewProcessModal {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn debug_picker_candidate_subtitles(&self, cx: &mut App) -> Vec<String> {
|
||||
self.debug_picker.update(cx, |picker, cx| {
|
||||
picker
|
||||
.delegate
|
||||
.candidates
|
||||
.iter()
|
||||
.filter_map(|(task_kind, _, _, context)| {
|
||||
picker.delegate.get_task_subtitle(task_kind, context, cx)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1026,7 +1026,7 @@ impl RunningState {
|
||||
};
|
||||
|
||||
let builder = ShellBuilder::new(remote_shell.as_deref(), &task.resolved.shell);
|
||||
let command_label = builder.command_label(task.resolved.command.as_deref().unwrap_or(""));
|
||||
let command_label = builder.command_label(&task.resolved.command_label);
|
||||
let (command, args) =
|
||||
builder.build(task.resolved.command.clone(), &task.resolved.args);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use gpui::{
|
||||
Action as _, AppContext, Context, Corner, Entity, FocusHandle, Focusable, HighlightStyle, Hsla,
|
||||
Render, Subscription, Task, TextStyle, WeakEntity, actions,
|
||||
};
|
||||
use language::{Anchor, Buffer, CharScopeContext, CodeLabel, TextBufferSnapshot, ToOffset};
|
||||
use language::{Anchor, Buffer, CodeLabel, TextBufferSnapshot, ToOffset};
|
||||
use menu::{Confirm, SelectNext, SelectPrevious};
|
||||
use project::{
|
||||
Completion, CompletionDisplayOptions, CompletionResponse,
|
||||
@@ -575,9 +575,7 @@ impl CompletionProvider for ConsoleQueryBarCompletionProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
let classifier = snapshot
|
||||
.char_classifier_at(position)
|
||||
.scope_context(Some(CharScopeContext::Completion));
|
||||
let classifier = snapshot.char_classifier_at(position).for_completion(true);
|
||||
if trigger_in_words && classifier.is_word(char) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use text::Point;
|
||||
use util::path;
|
||||
|
||||
use crate::NewProcessMode;
|
||||
use crate::new_process_modal::NewProcessModal;
|
||||
use crate::tests::{init_test, init_test_workspace};
|
||||
|
||||
#[gpui::test]
|
||||
@@ -179,7 +178,13 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
|
||||
crate::new_process_modal::NewProcessModal::show(
|
||||
workspace,
|
||||
window,
|
||||
NewProcessMode::Debug,
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -187,7 +192,7 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut
|
||||
|
||||
let modal = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace.active_modal::<NewProcessModal>(cx)
|
||||
workspace.active_modal::<crate::new_process_modal::NewProcessModal>(cx)
|
||||
})
|
||||
.unwrap()
|
||||
.expect("Modal should be active");
|
||||
@@ -276,73 +281,6 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut
|
||||
pretty_assertions::assert_eq!(expected_content, debug_json_content);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_debug_modal_subtitles_with_multiple_worktrees(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(executor.clone());
|
||||
|
||||
fs.insert_tree(
|
||||
path!("/workspace1"),
|
||||
json!({
|
||||
".zed": {
|
||||
"debug.json": r#"[
|
||||
{
|
||||
"adapter": "fake-adapter",
|
||||
"label": "Debug App 1",
|
||||
"request": "launch",
|
||||
"program": "./app1",
|
||||
"cwd": "."
|
||||
},
|
||||
{
|
||||
"adapter": "fake-adapter",
|
||||
"label": "Debug Tests 1",
|
||||
"request": "launch",
|
||||
"program": "./test1",
|
||||
"cwd": "."
|
||||
}
|
||||
]"#
|
||||
},
|
||||
"main.rs": "fn main() {}"
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/workspace1").as_ref()], cx).await;
|
||||
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let modal = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace.active_modal::<NewProcessModal>(cx)
|
||||
})
|
||||
.unwrap()
|
||||
.expect("Modal should be active");
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let subtitles = modal.update_in(cx, |modal, _, cx| {
|
||||
modal.debug_picker_candidate_subtitles(cx)
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
subtitles.as_slice(),
|
||||
[path!(".zed/debug.json"), path!(".zed/debug.json")]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -14,6 +14,7 @@ path = "src/edit_prediction_context.rs"
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
arrayvec.workspace = true
|
||||
chrono.workspace = true
|
||||
cloud_llm_client.workspace = true
|
||||
collections.workspace = true
|
||||
futures.workspace = true
|
||||
|
||||
@@ -68,7 +68,7 @@ impl Declaration {
|
||||
|
||||
pub fn item_range(&self) -> Range<usize> {
|
||||
match self {
|
||||
Declaration::File { declaration, .. } => declaration.item_range.clone(),
|
||||
Declaration::File { declaration, .. } => declaration.item_range_in_file.clone(),
|
||||
Declaration::Buffer { declaration, .. } => declaration.item_range.clone(),
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ impl Declaration {
|
||||
pub fn signature_text(&self) -> (Cow<'_, str>, bool) {
|
||||
match self {
|
||||
Declaration::File { declaration, .. } => (
|
||||
declaration.text[self.signature_range_in_item_text()].into(),
|
||||
declaration.text[declaration.signature_range_in_text.clone()].into(),
|
||||
declaration.signature_is_truncated,
|
||||
),
|
||||
Declaration::Buffer {
|
||||
@@ -105,18 +105,14 @@ impl Declaration {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signature_range(&self) -> Range<usize> {
|
||||
match self {
|
||||
Declaration::File { declaration, .. } => declaration.signature_range.clone(),
|
||||
Declaration::Buffer { declaration, .. } => declaration.signature_range.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signature_range_in_item_text(&self) -> Range<usize> {
|
||||
let signature_range = self.signature_range();
|
||||
let item_range = self.item_range();
|
||||
signature_range.start.saturating_sub(item_range.start)
|
||||
..(signature_range.end.saturating_sub(item_range.start)).min(item_range.len())
|
||||
match self {
|
||||
Declaration::File { declaration, .. } => declaration.signature_range_in_text.clone(),
|
||||
Declaration::Buffer { declaration, .. } => {
|
||||
declaration.signature_range.start - declaration.item_range.start
|
||||
..declaration.signature_range.end - declaration.item_range.start
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,13 +141,13 @@ pub struct FileDeclaration {
|
||||
pub parent: Option<DeclarationId>,
|
||||
pub identifier: Identifier,
|
||||
/// offset range of the declaration in the file, expanded to line boundaries and truncated
|
||||
pub item_range: Range<usize>,
|
||||
/// text of `item_range`
|
||||
pub item_range_in_file: Range<usize>,
|
||||
/// text of `item_range_in_file`
|
||||
pub text: Arc<str>,
|
||||
/// whether `text` was truncated
|
||||
pub text_is_truncated: bool,
|
||||
/// offset range of the signature in the file, expanded to line boundaries and truncated
|
||||
pub signature_range: Range<usize>,
|
||||
/// offset range of the signature within `text`
|
||||
pub signature_range_in_text: Range<usize>,
|
||||
/// whether `signature` was truncated
|
||||
pub signature_is_truncated: bool,
|
||||
}
|
||||
@@ -164,33 +160,31 @@ impl FileDeclaration {
|
||||
rope,
|
||||
);
|
||||
|
||||
let (mut signature_range_in_file, mut signature_is_truncated) =
|
||||
expand_range_to_line_boundaries_and_truncate(
|
||||
&declaration.signature_range,
|
||||
ITEM_TEXT_TRUNCATION_LENGTH,
|
||||
rope,
|
||||
);
|
||||
|
||||
if signature_range_in_file.start < item_range_in_file.start {
|
||||
signature_range_in_file.start = item_range_in_file.start;
|
||||
signature_is_truncated = true;
|
||||
}
|
||||
if signature_range_in_file.end > item_range_in_file.end {
|
||||
signature_range_in_file.end = item_range_in_file.end;
|
||||
signature_is_truncated = true;
|
||||
// TODO: consider logging if unexpected
|
||||
let signature_start = declaration
|
||||
.signature_range
|
||||
.start
|
||||
.saturating_sub(item_range_in_file.start);
|
||||
let mut signature_end = declaration
|
||||
.signature_range
|
||||
.end
|
||||
.saturating_sub(item_range_in_file.start);
|
||||
let signature_is_truncated = signature_end > item_range_in_file.len();
|
||||
if signature_is_truncated {
|
||||
signature_end = item_range_in_file.len();
|
||||
}
|
||||
|
||||
FileDeclaration {
|
||||
parent: None,
|
||||
identifier: declaration.identifier,
|
||||
signature_range: signature_range_in_file,
|
||||
signature_range_in_text: signature_start..signature_end,
|
||||
signature_is_truncated,
|
||||
text: rope
|
||||
.chunks_in_range(item_range_in_file.clone())
|
||||
.collect::<String>()
|
||||
.into(),
|
||||
text_is_truncated,
|
||||
item_range: item_range_in_file,
|
||||
item_range_in_file,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use itertools::Itertools as _;
|
||||
use language::BufferSnapshot;
|
||||
use ordered_float::OrderedFloat;
|
||||
use serde::Serialize;
|
||||
use std::{cmp::Reverse, collections::HashMap, ops::Range};
|
||||
use std::{collections::HashMap, ops::Range};
|
||||
use strum::EnumIter;
|
||||
use text::{Point, ToPoint};
|
||||
|
||||
@@ -40,9 +40,10 @@ impl ScoredSnippet {
|
||||
}
|
||||
|
||||
pub fn size(&self, style: SnippetStyle) -> usize {
|
||||
// TODO: how to handle truncation?
|
||||
match &self.declaration {
|
||||
Declaration::File { declaration, .. } => match style {
|
||||
SnippetStyle::Signature => declaration.signature_range.len(),
|
||||
SnippetStyle::Signature => declaration.signature_range_in_text.len(),
|
||||
SnippetStyle::Declaration => declaration.text.len(),
|
||||
},
|
||||
Declaration::Buffer { declaration, .. } => match style {
|
||||
@@ -159,10 +160,11 @@ pub fn scored_snippets(
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
snippets.sort_unstable_by_key(|snippet| {
|
||||
let score_density = snippet
|
||||
.score_density(SnippetStyle::Declaration)
|
||||
.max(snippet.score_density(SnippetStyle::Signature));
|
||||
Reverse(OrderedFloat(score_density))
|
||||
OrderedFloat(
|
||||
snippet
|
||||
.score_density(SnippetStyle::Declaration)
|
||||
.max(snippet.score_density(SnippetStyle::Signature)),
|
||||
)
|
||||
});
|
||||
|
||||
snippets
|
||||
@@ -274,8 +276,6 @@ pub struct Scores {
|
||||
|
||||
impl Scores {
|
||||
fn score(components: &ScoreComponents) -> Scores {
|
||||
// TODO: handle truncation
|
||||
|
||||
// Score related to how likely this is the correct declaration, range 0 to 1
|
||||
let accuracy_score = if components.is_same_file {
|
||||
// TODO: use declaration_line_distance_rank
|
||||
|
||||
@@ -59,7 +59,7 @@ impl EditPredictionContext {
|
||||
)?;
|
||||
let excerpt_text = excerpt.text(buffer);
|
||||
let cursor_offset_in_file = cursor_point.to_offset(buffer);
|
||||
// TODO fix this to not need saturating_sub
|
||||
// todo! fix this to not need saturating_sub
|
||||
let cursor_offset_in_excerpt = cursor_offset_in_file.saturating_sub(excerpt.range.start);
|
||||
|
||||
let snippets = if let Some(index_state) = index_state {
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::{BufferDeclaration, declaration::DeclarationId, syntax_index::SyntaxI
|
||||
//
|
||||
// - Filter outer syntax layers that don't support edit prediction.
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EditPredictionExcerptOptions {
|
||||
/// Limit for the number of bytes in the window around the cursor.
|
||||
pub max_bytes: usize,
|
||||
|
||||
@@ -578,11 +578,11 @@ mod tests {
|
||||
|
||||
let decl = expect_file_decl("c.rs", &decls[0].1, &project, cx);
|
||||
assert_eq!(decl.identifier, main.clone());
|
||||
assert_eq!(decl.item_range, 32..280);
|
||||
assert_eq!(decl.item_range_in_file, 32..280);
|
||||
|
||||
let decl = expect_file_decl("a.rs", &decls[1].1, &project, cx);
|
||||
assert_eq!(decl.identifier, main);
|
||||
assert_eq!(decl.item_range, 0..98);
|
||||
assert_eq!(decl.item_range_in_file, 0..98);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
35
crates/edit_prediction_context/src/wip_requests.rs
Normal file
35
crates/edit_prediction_context/src/wip_requests.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
// To discuss: What to send to the new endpoint? Thinking it'd make sense to put `prompt.rs` from
|
||||
// `zeta_context.rs` in cloud.
|
||||
//
|
||||
// * Run excerpt selection at several different sizes, send the largest size with offsets within for
|
||||
// the smaller sizes.
|
||||
//
|
||||
// * Longer event history.
|
||||
//
|
||||
// * Many more snippets than could fit in model context - allows ranking experimentation.
|
||||
|
||||
pub struct Zeta2Request {
|
||||
pub event_history: Vec<Event>,
|
||||
pub excerpt: String,
|
||||
pub excerpt_subsets: Vec<Zeta2ExcerptSubset>,
|
||||
/// Within `excerpt`
|
||||
pub cursor_position: usize,
|
||||
pub signatures: Vec<String>,
|
||||
pub retrieved_declarations: Vec<ReferencedDeclaration>,
|
||||
}
|
||||
|
||||
pub struct Zeta2ExcerptSubset {
|
||||
/// Within `excerpt` text.
|
||||
pub excerpt_range: Range<usize>,
|
||||
/// Within `signatures`.
|
||||
pub parent_signatures: Vec<usize>,
|
||||
}
|
||||
|
||||
pub struct ReferencedDeclaration {
|
||||
pub text: Arc<str>,
|
||||
/// Range within `text`
|
||||
pub signature_range: Range<usize>,
|
||||
/// Indices within `signatures`.
|
||||
pub parent_signatures: Vec<usize>,
|
||||
// A bunch of score metrics
|
||||
}
|
||||
@@ -89,7 +89,6 @@ ui.workspace = true
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
vim_mode_setting.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
@@ -22,11 +22,11 @@ use std::{
|
||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||
},
|
||||
};
|
||||
use sum_tree::{Bias, ContextLessSummary, Dimensions, SumTree, TreeMap};
|
||||
use sum_tree::{Bias, Dimensions, SumTree, Summary, TreeMap};
|
||||
use text::{BufferId, Edit};
|
||||
use ui::ElementId;
|
||||
|
||||
const NEWLINES: &[u8] = &[b'\n'; u128::BITS as usize];
|
||||
const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
|
||||
const BULLETS: &str = "********************************************************************************************************************************";
|
||||
|
||||
/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
|
||||
@@ -433,7 +433,7 @@ struct TransformSummary {
|
||||
}
|
||||
|
||||
pub struct BlockChunks<'a> {
|
||||
transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions<BlockRow, WrapRow>>,
|
||||
transforms: sum_tree::Cursor<'a, Transform, Dimensions<BlockRow, WrapRow>>,
|
||||
input_chunks: wrap_map::WrapChunks<'a>,
|
||||
input_chunk: Chunk<'a>,
|
||||
output_row: u32,
|
||||
@@ -443,7 +443,7 @@ pub struct BlockChunks<'a> {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BlockRows<'a> {
|
||||
transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions<BlockRow, WrapRow>>,
|
||||
transforms: sum_tree::Cursor<'a, Transform, Dimensions<BlockRow, WrapRow>>,
|
||||
input_rows: wrap_map::WrapRows<'a>,
|
||||
output_row: BlockRow,
|
||||
started: bool,
|
||||
@@ -527,7 +527,7 @@ impl BlockMap {
|
||||
|
||||
let mut transforms = self.transforms.borrow_mut();
|
||||
let mut new_transforms = SumTree::default();
|
||||
let mut cursor = transforms.cursor::<WrapRow>(());
|
||||
let mut cursor = transforms.cursor::<WrapRow>(&());
|
||||
let mut last_block_ix = 0;
|
||||
let mut blocks_in_edit = Vec::new();
|
||||
let mut edits = edits.into_iter().peekable();
|
||||
@@ -541,20 +541,20 @@ impl BlockMap {
|
||||
// * Isomorphic transforms that end *at* the start of the edit
|
||||
// * Below blocks that end at the start of the edit
|
||||
// However, if we hit a replace block that ends at the start of the edit we want to reconstruct it.
|
||||
new_transforms.append(cursor.slice(&old_start, Bias::Left), ());
|
||||
new_transforms.append(cursor.slice(&old_start, Bias::Left), &());
|
||||
if let Some(transform) = cursor.item()
|
||||
&& transform.summary.input_rows > 0
|
||||
&& cursor.end() == old_start
|
||||
&& transform.block.as_ref().is_none_or(|b| !b.is_replacement())
|
||||
{
|
||||
// Preserve the transform (push and next)
|
||||
new_transforms.push(transform.clone(), ());
|
||||
new_transforms.push(transform.clone(), &());
|
||||
cursor.next();
|
||||
|
||||
// Preserve below blocks at end of edit
|
||||
while let Some(transform) = cursor.item() {
|
||||
if transform.block.as_ref().is_some_and(|b| b.place_below()) {
|
||||
new_transforms.push(transform.clone(), ());
|
||||
new_transforms.push(transform.clone(), &());
|
||||
cursor.next();
|
||||
} else {
|
||||
break;
|
||||
@@ -720,7 +720,7 @@ impl BlockMap {
|
||||
summary,
|
||||
block: Some(block),
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -731,7 +731,7 @@ impl BlockMap {
|
||||
push_isomorphic(&mut new_transforms, rows_after_last_block, wrap_snapshot);
|
||||
}
|
||||
|
||||
new_transforms.append(cursor.suffix(), ());
|
||||
new_transforms.append(cursor.suffix(), &());
|
||||
debug_assert_eq!(
|
||||
new_transforms.summary().input_rows,
|
||||
wrap_snapshot.max_point().row() + 1
|
||||
@@ -925,11 +925,11 @@ fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32, wrap_snapshot: &Wra
|
||||
tree.update_last(
|
||||
|last_transform| {
|
||||
if last_transform.block.is_none() {
|
||||
last_transform.summary.add_summary(&summary);
|
||||
last_transform.summary.add_summary(&summary, &());
|
||||
merged = true;
|
||||
}
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
if !merged {
|
||||
tree.push(
|
||||
@@ -937,7 +937,7 @@ fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32, wrap_snapshot: &Wra
|
||||
summary,
|
||||
block: None,
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -997,7 +997,7 @@ impl BlockMapReader<'_> {
|
||||
.unwrap_or(self.wrap_snapshot.max_point().row() + 1),
|
||||
);
|
||||
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(&());
|
||||
cursor.seek(&start_wrap_row, Bias::Left);
|
||||
while let Some(transform) = cursor.item() {
|
||||
if cursor.start().0 > end_wrap_row {
|
||||
@@ -1313,7 +1313,7 @@ impl BlockSnapshot {
|
||||
) -> BlockChunks<'a> {
|
||||
let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
|
||||
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
|
||||
cursor.seek(&BlockRow(rows.start), Bias::Right);
|
||||
let transform_output_start = cursor.start().0.0;
|
||||
let transform_input_start = cursor.start().1.0;
|
||||
@@ -1345,7 +1345,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
|
||||
pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
|
||||
cursor.seek(&start_row, Bias::Right);
|
||||
let Dimensions(output_start, input_start, _) = cursor.start();
|
||||
let overshoot = if cursor
|
||||
@@ -1366,7 +1366,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
|
||||
pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
|
||||
let mut cursor = self.transforms.cursor::<BlockRow>(());
|
||||
let mut cursor = self.transforms.cursor::<BlockRow>(&());
|
||||
cursor.seek(&BlockRow(rows.start), Bias::Left);
|
||||
while cursor.start().0 < rows.start && cursor.end().0 <= rows.start {
|
||||
cursor.next();
|
||||
@@ -1397,7 +1397,7 @@ impl BlockSnapshot {
|
||||
|
||||
pub fn sticky_header_excerpt(&self, position: f32) -> Option<StickyHeaderExcerpt<'_>> {
|
||||
let top_row = position as u32;
|
||||
let mut cursor = self.transforms.cursor::<BlockRow>(());
|
||||
let mut cursor = self.transforms.cursor::<BlockRow>(&());
|
||||
cursor.seek(&BlockRow(top_row), Bias::Right);
|
||||
|
||||
while let Some(transform) = cursor.item() {
|
||||
@@ -1436,7 +1436,7 @@ impl BlockSnapshot {
|
||||
};
|
||||
let wrap_row = WrapRow(wrap_point.row());
|
||||
|
||||
let mut cursor = self.transforms.cursor::<WrapRow>(());
|
||||
let mut cursor = self.transforms.cursor::<WrapRow>(&());
|
||||
cursor.seek(&wrap_row, Bias::Left);
|
||||
|
||||
while let Some(transform) = cursor.item() {
|
||||
@@ -1464,7 +1464,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
|
||||
pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
|
||||
cursor.seek(&range.start, Bias::Right);
|
||||
|
||||
let mut longest_row = range.start;
|
||||
@@ -1515,7 +1515,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
|
||||
pub(super) fn line_len(&self, row: BlockRow) -> u32 {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
|
||||
cursor.seek(&BlockRow(row.0), Bias::Right);
|
||||
if let Some(transform) = cursor.item() {
|
||||
let Dimensions(output_start, input_start, _) = cursor.start();
|
||||
@@ -1533,13 +1533,13 @@ impl BlockSnapshot {
|
||||
}
|
||||
|
||||
pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
|
||||
cursor.seek(&row, Bias::Right);
|
||||
cursor.item().is_some_and(|t| t.block.is_some())
|
||||
}
|
||||
|
||||
pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
|
||||
cursor.seek(&row, Bias::Right);
|
||||
let Some(transform) = cursor.item() else {
|
||||
return false;
|
||||
@@ -1551,7 +1551,7 @@ impl BlockSnapshot {
|
||||
let wrap_point = self
|
||||
.wrap_snapshot
|
||||
.make_wrap_point(Point::new(row.0, 0), Bias::Left);
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(&());
|
||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right);
|
||||
cursor.item().is_some_and(|transform| {
|
||||
transform
|
||||
@@ -1562,7 +1562,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
|
||||
pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
|
||||
cursor.seek(&BlockRow(point.row), Bias::Right);
|
||||
|
||||
let max_input_row = WrapRow(self.transforms.summary().input_rows);
|
||||
@@ -1621,7 +1621,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
|
||||
pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(&());
|
||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right);
|
||||
if let Some(transform) = cursor.item() {
|
||||
if transform.block.is_some() {
|
||||
@@ -1639,7 +1639,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
|
||||
pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
|
||||
cursor.seek(&BlockRow(block_point.row), Bias::Right);
|
||||
if let Some(transform) = cursor.item() {
|
||||
match transform.block.as_ref() {
|
||||
@@ -1726,13 +1726,12 @@ impl<'a> Iterator for BlockChunks<'a> {
|
||||
|
||||
let start_in_block = self.output_row - block_start;
|
||||
let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
|
||||
// todo: We need to split the chunk here?
|
||||
let line_count = cmp::min(end_in_block - start_in_block, u128::BITS);
|
||||
let line_count = end_in_block - start_in_block;
|
||||
self.output_row += line_count;
|
||||
|
||||
return Some(Chunk {
|
||||
text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
|
||||
chars: 1u128.unbounded_shl(line_count) - 1,
|
||||
chars: (1 << line_count) - 1,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
@@ -1747,7 +1746,6 @@ impl<'a> Iterator for BlockChunks<'a> {
|
||||
if self.transforms.item().is_some() {
|
||||
return Some(Chunk {
|
||||
text: "\n",
|
||||
chars: 1,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
@@ -1775,7 +1773,7 @@ impl<'a> Iterator for BlockChunks<'a> {
|
||||
let chars_count = prefix.chars().count();
|
||||
let bullet_len = chars_count;
|
||||
prefix = &BULLETS[..bullet_len];
|
||||
chars = 1u128.unbounded_shl(bullet_len as u32) - 1;
|
||||
chars = (1 << bullet_len) - 1;
|
||||
tabs = 0;
|
||||
}
|
||||
|
||||
@@ -1848,17 +1846,19 @@ impl Iterator for BlockRows<'_> {
|
||||
impl sum_tree::Item for Transform {
|
||||
type Summary = TransformSummary;
|
||||
|
||||
fn summary(&self, _cx: ()) -> Self::Summary {
|
||||
fn summary(&self, _cx: &()) -> Self::Summary {
|
||||
self.summary.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::ContextLessSummary for TransformSummary {
|
||||
fn zero() -> Self {
|
||||
impl sum_tree::Summary for TransformSummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &Self) {
|
||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||
if summary.longest_row_chars > self.longest_row_chars {
|
||||
self.longest_row = self.output_rows + summary.longest_row;
|
||||
self.longest_row_chars = summary.longest_row_chars;
|
||||
@@ -1869,21 +1869,21 @@ impl sum_tree::ContextLessSummary for TransformSummary {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += summary.input_rows;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += summary.output_rows;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,9 +365,9 @@ impl Default for ItemSummary {
|
||||
}
|
||||
|
||||
impl sum_tree::Summary for ItemSummary {
|
||||
type Context<'a> = &'a MultiBufferSnapshot;
|
||||
type Context = MultiBufferSnapshot;
|
||||
|
||||
fn zero(_cx: Self::Context<'_>) -> Self {
|
||||
fn zero(_cx: &Self::Context) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ impl FoldPoint {
|
||||
pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint {
|
||||
let mut cursor = snapshot
|
||||
.transforms
|
||||
.cursor::<Dimensions<FoldPoint, InlayPoint>>(());
|
||||
.cursor::<Dimensions<FoldPoint, InlayPoint>>(&());
|
||||
cursor.seek(&self, Bias::Right);
|
||||
let overshoot = self.0 - cursor.start().0.0;
|
||||
InlayPoint(cursor.start().1.0 + overshoot)
|
||||
@@ -109,7 +109,7 @@ impl FoldPoint {
|
||||
pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset {
|
||||
let mut cursor = snapshot
|
||||
.transforms
|
||||
.cursor::<Dimensions<FoldPoint, TransformSummary>>(());
|
||||
.cursor::<Dimensions<FoldPoint, TransformSummary>>(&());
|
||||
cursor.seek(&self, Bias::Right);
|
||||
let overshoot = self.0 - cursor.start().1.output.lines;
|
||||
let mut offset = cursor.start().1.output.len;
|
||||
@@ -126,11 +126,11 @@ impl FoldPoint {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += &summary.output.lines;
|
||||
}
|
||||
}
|
||||
@@ -338,9 +338,9 @@ impl FoldMap {
|
||||
},
|
||||
placeholder: None,
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
),
|
||||
inlay_snapshot: inlay_snapshot,
|
||||
inlay_snapshot: inlay_snapshot.clone(),
|
||||
version: 0,
|
||||
fold_metadata_by_id: TreeMap::default(),
|
||||
},
|
||||
@@ -382,7 +382,7 @@ impl FoldMap {
|
||||
if !transform.is_fold() && prev_transform_isomorphic {
|
||||
panic!(
|
||||
"found adjacent isomorphic transforms: {:?}",
|
||||
self.snapshot.transforms.items(())
|
||||
self.snapshot.transforms.items(&())
|
||||
);
|
||||
}
|
||||
prev_transform_isomorphic = !transform.is_fold();
|
||||
@@ -413,7 +413,7 @@ impl FoldMap {
|
||||
let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable();
|
||||
|
||||
let mut new_transforms = SumTree::<Transform>::default();
|
||||
let mut cursor = self.snapshot.transforms.cursor::<InlayOffset>(());
|
||||
let mut cursor = self.snapshot.transforms.cursor::<InlayOffset>(&());
|
||||
cursor.seek(&InlayOffset(0), Bias::Right);
|
||||
|
||||
while let Some(mut edit) = inlay_edits_iter.next() {
|
||||
@@ -423,14 +423,14 @@ impl FoldMap {
|
||||
new_transforms.update_last(
|
||||
|transform| {
|
||||
if !transform.is_fold() {
|
||||
transform.summary.add_summary(&item.summary, ());
|
||||
transform.summary.add_summary(&item.summary, &());
|
||||
cursor.next();
|
||||
}
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
}
|
||||
new_transforms.append(cursor.slice(&edit.old.start, Bias::Left), ());
|
||||
new_transforms.append(cursor.slice(&edit.old.start, Bias::Left), &());
|
||||
edit.new.start -= edit.old.start - *cursor.start();
|
||||
edit.old.start = *cursor.start();
|
||||
|
||||
@@ -544,7 +544,7 @@ impl FoldMap {
|
||||
},
|
||||
}),
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -557,7 +557,7 @@ impl FoldMap {
|
||||
}
|
||||
}
|
||||
|
||||
new_transforms.append(cursor.suffix(), ());
|
||||
new_transforms.append(cursor.suffix(), &());
|
||||
if new_transforms.is_empty() {
|
||||
let text_summary = inlay_snapshot.text_summary();
|
||||
push_isomorphic(&mut new_transforms, text_summary);
|
||||
@@ -570,9 +570,9 @@ impl FoldMap {
|
||||
let mut old_transforms = self
|
||||
.snapshot
|
||||
.transforms
|
||||
.cursor::<Dimensions<InlayOffset, FoldOffset>>(());
|
||||
.cursor::<Dimensions<InlayOffset, FoldOffset>>(&());
|
||||
let mut new_transforms =
|
||||
new_transforms.cursor::<Dimensions<InlayOffset, FoldOffset>>(());
|
||||
new_transforms.cursor::<Dimensions<InlayOffset, FoldOffset>>(&());
|
||||
|
||||
for mut edit in inlay_edits {
|
||||
old_transforms.seek(&edit.old.start, Bias::Left);
|
||||
@@ -657,7 +657,7 @@ impl FoldSnapshot {
|
||||
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<FoldPoint, InlayPoint>>(());
|
||||
.cursor::<Dimensions<FoldPoint, InlayPoint>>(&());
|
||||
cursor.seek(&range.start, Bias::Right);
|
||||
if let Some(transform) = cursor.item() {
|
||||
let start_in_transform = range.start.0 - cursor.start().0.0;
|
||||
@@ -708,7 +708,7 @@ impl FoldSnapshot {
|
||||
pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<InlayPoint, FoldPoint>>(());
|
||||
.cursor::<Dimensions<InlayPoint, FoldPoint>>(&());
|
||||
cursor.seek(&point, Bias::Right);
|
||||
if cursor.item().is_some_and(|t| t.is_fold()) {
|
||||
if bias == Bias::Left || point == cursor.start().0 {
|
||||
@@ -744,7 +744,7 @@ impl FoldSnapshot {
|
||||
let fold_point = FoldPoint::new(start_row, 0);
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<FoldPoint, InlayPoint>>(());
|
||||
.cursor::<Dimensions<FoldPoint, InlayPoint>>(&());
|
||||
cursor.seek(&fold_point, Bias::Left);
|
||||
|
||||
let overshoot = fold_point.0 - cursor.start().0.0;
|
||||
@@ -787,7 +787,7 @@ impl FoldSnapshot {
|
||||
{
|
||||
let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer);
|
||||
let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
|
||||
let mut cursor = self.transforms.cursor::<InlayOffset>(());
|
||||
let mut cursor = self.transforms.cursor::<InlayOffset>(&());
|
||||
cursor.seek(&inlay_offset, Bias::Right);
|
||||
cursor.item().is_some_and(|t| t.placeholder.is_some())
|
||||
}
|
||||
@@ -796,7 +796,7 @@ impl FoldSnapshot {
|
||||
let mut inlay_point = self
|
||||
.inlay_snapshot
|
||||
.to_inlay_point(Point::new(buffer_row.0, 0));
|
||||
let mut cursor = self.transforms.cursor::<InlayPoint>(());
|
||||
let mut cursor = self.transforms.cursor::<InlayPoint>(&());
|
||||
cursor.seek(&inlay_point, Bias::Right);
|
||||
loop {
|
||||
match cursor.item() {
|
||||
@@ -828,7 +828,7 @@ impl FoldSnapshot {
|
||||
) -> FoldChunks<'a> {
|
||||
let mut transform_cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<FoldOffset, InlayOffset>>(());
|
||||
.cursor::<Dimensions<FoldOffset, InlayOffset>>(&());
|
||||
transform_cursor.seek(&range.start, Bias::Right);
|
||||
|
||||
let inlay_start = {
|
||||
@@ -893,7 +893,7 @@ impl FoldSnapshot {
|
||||
pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint {
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<FoldPoint, InlayPoint>>(());
|
||||
.cursor::<Dimensions<FoldPoint, InlayPoint>>(&());
|
||||
cursor.seek(&point, Bias::Right);
|
||||
if let Some(transform) = cursor.item() {
|
||||
let transform_start = cursor.start().0.0;
|
||||
@@ -925,7 +925,7 @@ fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: TextSummary) {
|
||||
did_merge = true;
|
||||
}
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
if !did_merge {
|
||||
transforms.push(
|
||||
@@ -936,7 +936,7 @@ fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: TextSummary) {
|
||||
},
|
||||
placeholder: None,
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -946,7 +946,7 @@ fn intersecting_folds<'a>(
|
||||
folds: &'a SumTree<Fold>,
|
||||
range: Range<usize>,
|
||||
inclusive: bool,
|
||||
) -> FilterCursor<'a, 'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> {
|
||||
) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> {
|
||||
let buffer = &inlay_snapshot.buffer;
|
||||
let start = buffer.anchor_before(range.start.to_offset(buffer));
|
||||
let end = buffer.anchor_after(range.end.to_offset(buffer));
|
||||
@@ -1062,17 +1062,19 @@ struct TransformSummary {
|
||||
impl sum_tree::Item for Transform {
|
||||
type Summary = TransformSummary;
|
||||
|
||||
fn summary(&self, _cx: ()) -> Self::Summary {
|
||||
fn summary(&self, _cx: &()) -> Self::Summary {
|
||||
self.summary.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::ContextLessSummary for TransformSummary {
|
||||
fn zero() -> Self {
|
||||
impl sum_tree::Summary for TransformSummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self) {
|
||||
fn add_summary(&mut self, other: &Self, _: &()) {
|
||||
self.input += &other.input;
|
||||
self.output += &other.output;
|
||||
}
|
||||
@@ -1159,13 +1161,13 @@ impl Default for FoldSummary {
|
||||
}
|
||||
|
||||
impl sum_tree::Summary for FoldSummary {
|
||||
type Context<'a> = &'a MultiBufferSnapshot;
|
||||
type Context = MultiBufferSnapshot;
|
||||
|
||||
fn zero(_cx: &MultiBufferSnapshot) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
|
||||
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
|
||||
if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less {
|
||||
self.min_start = other.min_start;
|
||||
}
|
||||
@@ -1217,7 +1219,7 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FoldRows<'a> {
|
||||
cursor: Cursor<'a, 'static, Transform, Dimensions<FoldPoint, InlayPoint>>,
|
||||
cursor: Cursor<'a, Transform, Dimensions<FoldPoint, InlayPoint>>,
|
||||
input_rows: InlayBufferRows<'a>,
|
||||
fold_point: FoldPoint,
|
||||
}
|
||||
@@ -1338,7 +1340,7 @@ impl DerefMut for ChunkRendererContext<'_, '_> {
|
||||
}
|
||||
|
||||
pub struct FoldChunks<'a> {
|
||||
transform_cursor: Cursor<'a, 'static, Transform, Dimensions<FoldOffset, InlayOffset>>,
|
||||
transform_cursor: Cursor<'a, Transform, Dimensions<FoldOffset, InlayOffset>>,
|
||||
inlay_chunks: InlayChunks<'a>,
|
||||
inlay_chunk: Option<(InlayOffset, InlayChunk<'a>)>,
|
||||
inlay_offset: InlayOffset,
|
||||
@@ -1486,7 +1488,7 @@ impl FoldOffset {
|
||||
pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
|
||||
let mut cursor = snapshot
|
||||
.transforms
|
||||
.cursor::<Dimensions<FoldOffset, TransformSummary>>(());
|
||||
.cursor::<Dimensions<FoldOffset, TransformSummary>>(&());
|
||||
cursor.seek(&self, Bias::Right);
|
||||
let overshoot = if cursor.item().is_none_or(|t| t.is_fold()) {
|
||||
Point::new(0, (self.0 - cursor.start().0.0) as u32)
|
||||
@@ -1502,7 +1504,7 @@ impl FoldOffset {
|
||||
pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset {
|
||||
let mut cursor = snapshot
|
||||
.transforms
|
||||
.cursor::<Dimensions<FoldOffset, InlayOffset>>(());
|
||||
.cursor::<Dimensions<FoldOffset, InlayOffset>>(&());
|
||||
cursor.seek(&self, Bias::Right);
|
||||
let overshoot = self.0 - cursor.start().0.0;
|
||||
InlayOffset(cursor.start().1.0 + overshoot)
|
||||
@@ -1532,31 +1534,31 @@ impl Sub for FoldOffset {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += &summary.output.len;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += &summary.input.lines;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += &summary.input.len;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ impl Inlay {
|
||||
impl sum_tree::Item for Transform {
|
||||
type Summary = TransformSummary;
|
||||
|
||||
fn summary(&self, _: ()) -> Self::Summary {
|
||||
fn summary(&self, _: &()) -> Self::Summary {
|
||||
match self {
|
||||
Transform::Isomorphic(summary) => TransformSummary {
|
||||
input: *summary,
|
||||
@@ -128,12 +128,14 @@ struct TransformSummary {
|
||||
output: TextSummary,
|
||||
}
|
||||
|
||||
impl sum_tree::ContextLessSummary for TransformSummary {
|
||||
fn zero() -> Self {
|
||||
impl sum_tree::Summary for TransformSummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self) {
|
||||
fn add_summary(&mut self, other: &Self, _: &()) {
|
||||
self.input += &other.input;
|
||||
self.output += &other.output;
|
||||
}
|
||||
@@ -173,11 +175,11 @@ impl SubAssign for InlayOffset {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += &summary.output.len;
|
||||
}
|
||||
}
|
||||
@@ -202,45 +204,45 @@ impl Sub for InlayPoint {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += &summary.output.lines;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
*self += &summary.input.len;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
*self += &summary.input.lines;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InlayBufferRows<'a> {
|
||||
transforms: Cursor<'a, 'static, Transform, Dimensions<InlayPoint, Point>>,
|
||||
transforms: Cursor<'a, Transform, Dimensions<InlayPoint, Point>>,
|
||||
buffer_rows: MultiBufferRows<'a>,
|
||||
inlay_row: u32,
|
||||
max_buffer_row: MultiBufferRow,
|
||||
}
|
||||
|
||||
pub struct InlayChunks<'a> {
|
||||
transforms: Cursor<'a, 'static, Transform, Dimensions<InlayOffset, usize>>,
|
||||
transforms: Cursor<'a, Transform, Dimensions<InlayOffset, usize>>,
|
||||
buffer_chunks: CustomHighlightsChunks<'a>,
|
||||
buffer_chunk: Option<Chunk<'a>>,
|
||||
inlay_chunks: Option<text::ChunkWithBitmaps<'a>>,
|
||||
@@ -540,7 +542,7 @@ impl InlayMap {
|
||||
let version = 0;
|
||||
let snapshot = InlaySnapshot {
|
||||
buffer: buffer.clone(),
|
||||
transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), ()),
|
||||
transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()),
|
||||
version,
|
||||
};
|
||||
|
||||
@@ -587,10 +589,10 @@ impl InlayMap {
|
||||
let mut new_transforms = SumTree::default();
|
||||
let mut cursor = snapshot
|
||||
.transforms
|
||||
.cursor::<Dimensions<usize, InlayOffset>>(());
|
||||
.cursor::<Dimensions<usize, InlayOffset>>(&());
|
||||
let mut buffer_edits_iter = buffer_edits.iter().peekable();
|
||||
while let Some(buffer_edit) = buffer_edits_iter.next() {
|
||||
new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left), ());
|
||||
new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left), &());
|
||||
if let Some(Transform::Isomorphic(transform)) = cursor.item()
|
||||
&& cursor.end().0 == buffer_edit.old.start
|
||||
{
|
||||
@@ -640,7 +642,7 @@ impl InlayMap {
|
||||
buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
|
||||
);
|
||||
|
||||
new_transforms.push(Transform::Inlay(inlay.clone()), ());
|
||||
new_transforms.push(Transform::Inlay(inlay.clone()), &());
|
||||
}
|
||||
|
||||
// Apply the rest of the edit.
|
||||
@@ -672,9 +674,9 @@ impl InlayMap {
|
||||
}
|
||||
}
|
||||
|
||||
new_transforms.append(cursor.suffix(), ());
|
||||
new_transforms.append(cursor.suffix(), &());
|
||||
if new_transforms.is_empty() {
|
||||
new_transforms.push(Transform::Isomorphic(Default::default()), ());
|
||||
new_transforms.push(Transform::Isomorphic(Default::default()), &());
|
||||
}
|
||||
|
||||
drop(cursor);
|
||||
@@ -810,7 +812,7 @@ impl InlaySnapshot {
|
||||
pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<InlayOffset, InlayPoint, usize>>(());
|
||||
.cursor::<Dimensions<InlayOffset, InlayPoint, usize>>(&());
|
||||
cursor.seek(&offset, Bias::Right);
|
||||
let overshoot = offset.0 - cursor.start().0.0;
|
||||
match cursor.item() {
|
||||
@@ -840,7 +842,7 @@ impl InlaySnapshot {
|
||||
pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<InlayPoint, InlayOffset, Point>>(());
|
||||
.cursor::<Dimensions<InlayPoint, InlayOffset, Point>>(&());
|
||||
cursor.seek(&point, Bias::Right);
|
||||
let overshoot = point.0 - cursor.start().0.0;
|
||||
match cursor.item() {
|
||||
@@ -859,7 +861,7 @@ impl InlaySnapshot {
|
||||
}
|
||||
}
|
||||
pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<InlayPoint, Point>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<InlayPoint, Point>>(&());
|
||||
cursor.seek(&point, Bias::Right);
|
||||
match cursor.item() {
|
||||
Some(Transform::Isomorphic(_)) => {
|
||||
@@ -871,7 +873,9 @@ impl InlaySnapshot {
|
||||
}
|
||||
}
|
||||
pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<InlayOffset, usize>>(());
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<InlayOffset, usize>>(&());
|
||||
cursor.seek(&offset, Bias::Right);
|
||||
match cursor.item() {
|
||||
Some(Transform::Isomorphic(_)) => {
|
||||
@@ -884,7 +888,9 @@ impl InlaySnapshot {
|
||||
}
|
||||
|
||||
pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<usize, InlayOffset>>(());
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<usize, InlayOffset>>(&());
|
||||
cursor.seek(&offset, Bias::Left);
|
||||
loop {
|
||||
match cursor.item() {
|
||||
@@ -917,7 +923,7 @@ impl InlaySnapshot {
|
||||
}
|
||||
}
|
||||
pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<Point, InlayPoint>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<Point, InlayPoint>>(&());
|
||||
cursor.seek(&point, Bias::Left);
|
||||
loop {
|
||||
match cursor.item() {
|
||||
@@ -951,7 +957,7 @@ impl InlaySnapshot {
|
||||
}
|
||||
|
||||
pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<InlayPoint, Point>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<InlayPoint, Point>>(&());
|
||||
cursor.seek(&point, Bias::Left);
|
||||
loop {
|
||||
match cursor.item() {
|
||||
@@ -1048,7 +1054,9 @@ impl InlaySnapshot {
|
||||
pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
|
||||
let mut summary = TextSummary::default();
|
||||
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<InlayOffset, usize>>(());
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<InlayOffset, usize>>(&());
|
||||
cursor.seek(&range.start, Bias::Right);
|
||||
|
||||
let overshoot = range.start.0 - cursor.start().0.0;
|
||||
@@ -1096,7 +1104,7 @@ impl InlaySnapshot {
|
||||
}
|
||||
|
||||
pub fn row_infos(&self, row: u32) -> InlayBufferRows<'_> {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<InlayPoint, Point>>(());
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<InlayPoint, Point>>(&());
|
||||
let inlay_point = InlayPoint::new(row, 0);
|
||||
cursor.seek(&inlay_point, Bias::Left);
|
||||
|
||||
@@ -1138,7 +1146,9 @@ impl InlaySnapshot {
|
||||
language_aware: bool,
|
||||
highlights: Highlights<'a>,
|
||||
) -> InlayChunks<'a> {
|
||||
let mut cursor = self.transforms.cursor::<Dimensions<InlayOffset, usize>>(());
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<InlayOffset, usize>>(&());
|
||||
cursor.seek(&range.start, Bias::Right);
|
||||
|
||||
let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
|
||||
@@ -1202,11 +1212,11 @@ fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
|
||||
*transform += summary.take().unwrap();
|
||||
}
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
|
||||
if let Some(summary) = summary {
|
||||
sum_tree.push(Transform::Isomorphic(summary), ());
|
||||
sum_tree.push(Transform::Isomorphic(summary), &());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -551,7 +551,7 @@ impl TabChunks<'_> {
|
||||
self.chunk = Chunk {
|
||||
text: &SPACES[0..(to_next_stop as usize)],
|
||||
is_tab: true,
|
||||
chars: 1u128.unbounded_shl(to_next_stop) - 1,
|
||||
chars: (1u128 << to_next_stop) - 1,
|
||||
..Default::default()
|
||||
};
|
||||
self.inside_leading_tab = to_next_stop > 0;
|
||||
@@ -623,7 +623,7 @@ impl<'a> Iterator for TabChunks<'a> {
|
||||
return Some(Chunk {
|
||||
text: &SPACES[..len as usize],
|
||||
is_tab: true,
|
||||
chars: 1u128.unbounded_shl(len) - 1,
|
||||
chars: (1 << len) - 1,
|
||||
tabs: 0,
|
||||
..self.chunk.clone()
|
||||
});
|
||||
|
||||
@@ -55,7 +55,7 @@ pub struct WrapChunks<'a> {
|
||||
input_chunk: Chunk<'a>,
|
||||
output_position: WrapPoint,
|
||||
max_output_row: u32,
|
||||
transforms: Cursor<'a, 'static, Transform, Dimensions<WrapPoint, TabPoint>>,
|
||||
transforms: Cursor<'a, Transform, Dimensions<WrapPoint, TabPoint>>,
|
||||
snapshot: &'a WrapSnapshot,
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ pub struct WrapRows<'a> {
|
||||
output_row: u32,
|
||||
soft_wrapped: bool,
|
||||
max_output_row: u32,
|
||||
transforms: Cursor<'a, 'static, Transform, Dimensions<WrapPoint, TabPoint>>,
|
||||
transforms: Cursor<'a, Transform, Dimensions<WrapPoint, TabPoint>>,
|
||||
}
|
||||
|
||||
impl WrapRows<'_> {
|
||||
@@ -221,7 +221,7 @@ impl WrapMap {
|
||||
if !summary.lines.is_zero() {
|
||||
self.snapshot
|
||||
.transforms
|
||||
.push(Transform::isomorphic(summary), ());
|
||||
.push(Transform::isomorphic(summary), &());
|
||||
}
|
||||
let new_rows = self.snapshot.transforms.summary().output.lines.row + 1;
|
||||
self.snapshot.interpolated = false;
|
||||
@@ -318,7 +318,7 @@ impl WrapSnapshot {
|
||||
let mut transforms = SumTree::default();
|
||||
let extent = tab_snapshot.text_summary();
|
||||
if !extent.lines.is_zero() {
|
||||
transforms.push(Transform::isomorphic(extent), ());
|
||||
transforms.push(Transform::isomorphic(extent), &());
|
||||
}
|
||||
Self {
|
||||
transforms,
|
||||
@@ -336,7 +336,7 @@ impl WrapSnapshot {
|
||||
if tab_edits.is_empty() {
|
||||
new_transforms = self.transforms.clone();
|
||||
} else {
|
||||
let mut old_cursor = self.transforms.cursor::<TabPoint>(());
|
||||
let mut old_cursor = self.transforms.cursor::<TabPoint>(&());
|
||||
|
||||
let mut tab_edits_iter = tab_edits.iter().peekable();
|
||||
new_transforms =
|
||||
@@ -368,7 +368,7 @@ impl WrapSnapshot {
|
||||
|
||||
old_cursor.next();
|
||||
new_transforms
|
||||
.append(old_cursor.slice(&next_edit.old.start, Bias::Right), ());
|
||||
.append(old_cursor.slice(&next_edit.old.start, Bias::Right), &());
|
||||
}
|
||||
} else {
|
||||
if old_cursor.end() > edit.old.end {
|
||||
@@ -378,7 +378,7 @@ impl WrapSnapshot {
|
||||
new_transforms.push_or_extend(Transform::isomorphic(summary));
|
||||
}
|
||||
old_cursor.next();
|
||||
new_transforms.append(old_cursor.suffix(), ());
|
||||
new_transforms.append(old_cursor.suffix(), &());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,7 +434,7 @@ impl WrapSnapshot {
|
||||
new_transforms = self.transforms.clone();
|
||||
} else {
|
||||
let mut row_edits = row_edits.into_iter().peekable();
|
||||
let mut old_cursor = self.transforms.cursor::<TabPoint>(());
|
||||
let mut old_cursor = self.transforms.cursor::<TabPoint>(&());
|
||||
|
||||
new_transforms = old_cursor.slice(
|
||||
&TabPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
|
||||
@@ -511,7 +511,7 @@ impl WrapSnapshot {
|
||||
if let Some(transform) = edit_transforms.next() {
|
||||
new_transforms.push_or_extend(transform);
|
||||
}
|
||||
new_transforms.extend(edit_transforms, ());
|
||||
new_transforms.extend(edit_transforms, &());
|
||||
|
||||
old_cursor.seek_forward(&TabPoint::new(edit.old_rows.end, 0), Bias::Right);
|
||||
if let Some(next_edit) = row_edits.peek() {
|
||||
@@ -526,7 +526,7 @@ impl WrapSnapshot {
|
||||
new_transforms.append(
|
||||
old_cursor
|
||||
.slice(&TabPoint::new(next_edit.old_rows.start, 0), Bias::Right),
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -537,7 +537,7 @@ impl WrapSnapshot {
|
||||
new_transforms.push_or_extend(Transform::isomorphic(summary));
|
||||
}
|
||||
old_cursor.next();
|
||||
new_transforms.append(old_cursor.suffix(), ());
|
||||
new_transforms.append(old_cursor.suffix(), &());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,8 +556,8 @@ impl WrapSnapshot {
|
||||
|
||||
fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &WrapSnapshot) -> Patch<u32> {
|
||||
let mut wrap_edits = Vec::with_capacity(tab_edits.len());
|
||||
let mut old_cursor = self.transforms.cursor::<TransformSummary>(());
|
||||
let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>(());
|
||||
let mut old_cursor = self.transforms.cursor::<TransformSummary>(&());
|
||||
let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>(&());
|
||||
for mut tab_edit in tab_edits.iter().cloned() {
|
||||
tab_edit.old.start.0.column = 0;
|
||||
tab_edit.old.end.0 += Point::new(1, 0);
|
||||
@@ -600,7 +600,7 @@ impl WrapSnapshot {
|
||||
let output_end = WrapPoint::new(rows.end, 0);
|
||||
let mut transforms = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(());
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(&());
|
||||
transforms.seek(&output_start, Bias::Right);
|
||||
let mut input_start = TabPoint(transforms.start().1.0);
|
||||
if transforms.item().is_some_and(|t| t.is_isomorphic()) {
|
||||
@@ -630,7 +630,7 @@ impl WrapSnapshot {
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(());
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(&());
|
||||
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left);
|
||||
if cursor
|
||||
.item()
|
||||
@@ -657,7 +657,7 @@ impl WrapSnapshot {
|
||||
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(());
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(&());
|
||||
cursor.seek(&start, Bias::Right);
|
||||
if let Some(transform) = cursor.item() {
|
||||
let start_in_transform = start.0 - cursor.start().0.0;
|
||||
@@ -711,7 +711,7 @@ impl WrapSnapshot {
|
||||
}
|
||||
|
||||
pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
|
||||
let mut cursor = self.transforms.cursor::<WrapPoint>(());
|
||||
let mut cursor = self.transforms.cursor::<WrapPoint>(&());
|
||||
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right);
|
||||
cursor.item().and_then(|transform| {
|
||||
if transform.is_isomorphic() {
|
||||
@@ -729,7 +729,7 @@ impl WrapSnapshot {
|
||||
pub fn row_infos(&self, start_row: u32) -> WrapRows<'_> {
|
||||
let mut transforms = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(());
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(&());
|
||||
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left);
|
||||
let mut input_row = transforms.start().1.row();
|
||||
if transforms.item().is_some_and(|t| t.is_isomorphic()) {
|
||||
@@ -751,7 +751,7 @@ impl WrapSnapshot {
|
||||
pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint {
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(());
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(&());
|
||||
cursor.seek(&point, Bias::Right);
|
||||
let mut tab_point = cursor.start().1.0;
|
||||
if cursor.item().is_some_and(|t| t.is_isomorphic()) {
|
||||
@@ -771,14 +771,14 @@ impl WrapSnapshot {
|
||||
pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint {
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<TabPoint, WrapPoint>>(());
|
||||
.cursor::<Dimensions<TabPoint, WrapPoint>>(&());
|
||||
cursor.seek(&point, Bias::Right);
|
||||
WrapPoint(cursor.start().1.0 + (point.0 - cursor.start().0.0))
|
||||
}
|
||||
|
||||
pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint {
|
||||
if bias == Bias::Left {
|
||||
let mut cursor = self.transforms.cursor::<WrapPoint>(());
|
||||
let mut cursor = self.transforms.cursor::<WrapPoint>(&());
|
||||
cursor.seek(&point, Bias::Right);
|
||||
if cursor.item().is_some_and(|t| !t.is_isomorphic()) {
|
||||
point = *cursor.start();
|
||||
@@ -798,7 +798,7 @@ impl WrapSnapshot {
|
||||
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(());
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(&());
|
||||
cursor.seek(&point, Bias::Right);
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev();
|
||||
@@ -820,7 +820,7 @@ impl WrapSnapshot {
|
||||
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(());
|
||||
.cursor::<Dimensions<WrapPoint, TabPoint>>(&());
|
||||
cursor.seek(&point, Bias::Right);
|
||||
while let Some(transform) = cursor.item() {
|
||||
if transform.is_isomorphic() && cursor.start().1.column() == 0 {
|
||||
@@ -857,7 +857,7 @@ impl WrapSnapshot {
|
||||
);
|
||||
|
||||
{
|
||||
let mut transforms = self.transforms.cursor::<()>(()).peekable();
|
||||
let mut transforms = self.transforms.cursor::<()>(&()).peekable();
|
||||
while let Some(transform) = transforms.next() {
|
||||
if let Some(next_transform) = transforms.peek() {
|
||||
assert!(transform.is_isomorphic() != next_transform.is_isomorphic());
|
||||
@@ -1075,7 +1075,7 @@ impl Transform {
|
||||
impl sum_tree::Item for Transform {
|
||||
type Summary = TransformSummary;
|
||||
|
||||
fn summary(&self, _cx: ()) -> Self::Summary {
|
||||
fn summary(&self, _cx: &()) -> Self::Summary {
|
||||
self.summary.clone()
|
||||
}
|
||||
}
|
||||
@@ -1106,11 +1106,11 @@ impl SumTreeExt for SumTree<Transform> {
|
||||
last_transform.summary.output += &transform.summary.output;
|
||||
}
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
|
||||
if let Some(transform) = transform {
|
||||
self.push(transform, ());
|
||||
self.push(transform, &());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1137,39 +1137,41 @@ impl WrapPoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::ContextLessSummary for TransformSummary {
|
||||
fn zero() -> Self {
|
||||
impl sum_tree::Summary for TransformSummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self) {
|
||||
fn add_summary(&mut self, other: &Self, _: &()) {
|
||||
self.input += &other.input;
|
||||
self.output += &other.output;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += summary.input.lines;
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::SeekTarget<'_, TransformSummary, TransformSummary> for TabPoint {
|
||||
fn cmp(&self, cursor_location: &TransformSummary, _: ()) -> std::cmp::Ordering {
|
||||
fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering {
|
||||
Ord::cmp(&self.0, &cursor_location.input.lines)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += summary.output.lines;
|
||||
}
|
||||
}
|
||||
@@ -1383,7 +1385,7 @@ mod tests {
|
||||
let mut summary = TextSummary::default();
|
||||
for (ix, item) in wrapped_snapshot
|
||||
.transforms
|
||||
.items(())
|
||||
.items(&())
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
|
||||
@@ -121,10 +121,10 @@ use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
|
||||
use itertools::{Either, Itertools};
|
||||
use language::{
|
||||
AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
|
||||
BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
|
||||
DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
|
||||
IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
|
||||
TextObject, TransactionId, TreeSitterOptions, WordsQuery,
|
||||
BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
|
||||
DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
|
||||
Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
|
||||
TransactionId, TreeSitterOptions, WordsQuery,
|
||||
language_settings::{
|
||||
self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
|
||||
all_language_settings, language_settings,
|
||||
@@ -177,7 +177,7 @@ use std::{
|
||||
borrow::Cow,
|
||||
cell::{OnceCell, RefCell},
|
||||
cmp::{self, Ordering, Reverse},
|
||||
iter::{self, Peekable},
|
||||
iter::Peekable,
|
||||
mem,
|
||||
num::NonZeroU32,
|
||||
ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
|
||||
@@ -2518,10 +2518,6 @@ impl Editor {
|
||||
key_context
|
||||
}
|
||||
|
||||
pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
|
||||
self.last_bounds.as_ref()
|
||||
}
|
||||
|
||||
fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
|
||||
if self.mouse_cursor_hidden {
|
||||
self.mouse_cursor_hidden = false;
|
||||
@@ -3127,8 +3123,7 @@ impl Editor {
|
||||
let position_matches = start_offset == completion_position.to_offset(buffer);
|
||||
let continue_showing = if position_matches {
|
||||
if self.snippet_stack.is_empty() {
|
||||
buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
|
||||
== Some(CharKind::Word)
|
||||
buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
|
||||
} else {
|
||||
// Snippet choices can be shown even when the cursor is in whitespace.
|
||||
// Dismissing the menu with actions like backspace is handled by
|
||||
@@ -3556,7 +3551,7 @@ impl Editor {
|
||||
let position = display_map
|
||||
.clip_point(position, Bias::Left)
|
||||
.to_offset(&display_map, Bias::Left);
|
||||
let (range, _) = buffer.surrounding_word(position, None);
|
||||
let (range, _) = buffer.surrounding_word(position, false);
|
||||
start = buffer.anchor_before(range.start);
|
||||
end = buffer.anchor_before(range.end);
|
||||
mode = SelectMode::Word(start..end);
|
||||
@@ -3716,10 +3711,10 @@ impl Editor {
|
||||
.to_offset(&display_map, Bias::Left);
|
||||
let original_range = original_range.to_offset(buffer);
|
||||
|
||||
let head_offset = if buffer.is_inside_word(offset, None)
|
||||
let head_offset = if buffer.is_inside_word(offset, false)
|
||||
|| original_range.contains(&offset)
|
||||
{
|
||||
let (word_range, _) = buffer.surrounding_word(offset, None);
|
||||
let (word_range, _) = buffer.surrounding_word(offset, false);
|
||||
if word_range.start < original_range.start {
|
||||
word_range.start
|
||||
} else {
|
||||
@@ -4249,7 +4244,7 @@ impl Editor {
|
||||
let is_word_char = text.chars().next().is_none_or(|char| {
|
||||
let classifier = snapshot
|
||||
.char_classifier_at(start_anchor.to_offset(&snapshot))
|
||||
.scope_context(Some(CharScopeContext::LinkedEdit));
|
||||
.ignore_punctuation(true);
|
||||
classifier.is_word(char)
|
||||
});
|
||||
|
||||
@@ -5106,8 +5101,7 @@ impl Editor {
|
||||
|
||||
fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
|
||||
let offset = position.to_offset(buffer);
|
||||
let (word_range, kind) =
|
||||
buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
|
||||
let (word_range, kind) = buffer.surrounding_word(offset, true);
|
||||
if offset > word_range.start && kind == Some(CharKind::Word) {
|
||||
Some(
|
||||
buffer
|
||||
@@ -5577,7 +5571,7 @@ impl Editor {
|
||||
} = buffer_position;
|
||||
|
||||
let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
|
||||
buffer_snapshot.surrounding_word(buffer_position, None)
|
||||
buffer_snapshot.surrounding_word(buffer_position, false)
|
||||
{
|
||||
let word_to_exclude = buffer_snapshot
|
||||
.text_for_range(word_range.clone())
|
||||
@@ -6793,8 +6787,8 @@ impl Editor {
|
||||
}
|
||||
|
||||
let snapshot = cursor_buffer.read(cx).snapshot();
|
||||
let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
|
||||
let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
|
||||
let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
|
||||
let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
|
||||
if start_word_range != end_word_range {
|
||||
self.document_highlights_task.take();
|
||||
self.clear_background_highlights::<DocumentHighlightRead>(cx);
|
||||
@@ -11446,7 +11440,7 @@ impl Editor {
|
||||
let selection_is_empty = selection.is_empty();
|
||||
|
||||
let (start, end) = if selection_is_empty {
|
||||
let (word_range, _) = buffer.surrounding_word(selection.start, None);
|
||||
let (word_range, _) = buffer.surrounding_word(selection.start, false);
|
||||
(word_range.start, word_range.end)
|
||||
} else {
|
||||
(
|
||||
@@ -12456,14 +12450,13 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
let clipboard_text = Cow::Borrowed(text.as_str());
|
||||
let clipboard_text = Cow::Borrowed(text);
|
||||
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
let had_active_edit_prediction = this.has_active_edit_prediction();
|
||||
let old_selections = this.selections.all::<usize>(cx);
|
||||
let cursor_offset = this.selections.last::<usize>(cx).head();
|
||||
|
||||
if let Some(mut clipboard_selections) = clipboard_selections {
|
||||
let old_selections = this.selections.all::<usize>(cx);
|
||||
let all_selections_were_entire_line =
|
||||
clipboard_selections.iter().all(|s| s.is_entire_line);
|
||||
let first_selection_indent_column =
|
||||
@@ -12471,6 +12464,7 @@ impl Editor {
|
||||
if clipboard_selections.len() != old_selections.len() {
|
||||
clipboard_selections.drain(..);
|
||||
}
|
||||
let cursor_offset = this.selections.last::<usize>(cx).head();
|
||||
let mut auto_indent_on_paste = true;
|
||||
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
@@ -12493,36 +12487,22 @@ impl Editor {
|
||||
start_offset = end_offset + 1;
|
||||
original_indent_column = Some(clipboard_selection.first_line_indent);
|
||||
} else {
|
||||
to_insert = &*clipboard_text;
|
||||
to_insert = clipboard_text.as_str();
|
||||
entire_line = all_selections_were_entire_line;
|
||||
original_indent_column = first_selection_indent_column
|
||||
}
|
||||
|
||||
let (range, to_insert) =
|
||||
if selection.is_empty() && handle_entire_lines && entire_line {
|
||||
// If the corresponding selection was empty when this slice of the
|
||||
// clipboard text was written, then the entire line containing the
|
||||
// selection was copied. If this selection is also currently empty,
|
||||
// then paste the line before the current line of the buffer.
|
||||
let column = selection.start.to_point(&snapshot).column as usize;
|
||||
let line_start = selection.start - column;
|
||||
(line_start..line_start, Cow::Borrowed(to_insert))
|
||||
} else {
|
||||
let language = snapshot.language_at(selection.head());
|
||||
let range = selection.range();
|
||||
if let Some(language) = language
|
||||
&& language.name() == "Markdown".into()
|
||||
{
|
||||
edit_for_markdown_paste(
|
||||
&snapshot,
|
||||
range,
|
||||
to_insert,
|
||||
url::Url::parse(to_insert).ok(),
|
||||
)
|
||||
} else {
|
||||
(range, Cow::Borrowed(to_insert))
|
||||
}
|
||||
};
|
||||
// If the corresponding selection was empty when this slice of the
|
||||
// clipboard text was written, then the entire line containing the
|
||||
// selection was copied. If this selection is also currently empty,
|
||||
// then paste the line before the current line of the buffer.
|
||||
let range = if selection.is_empty() && handle_entire_lines && entire_line {
|
||||
let column = selection.start.to_point(&snapshot).column as usize;
|
||||
let line_start = selection.start - column;
|
||||
line_start..line_start
|
||||
} else {
|
||||
selection.range()
|
||||
};
|
||||
|
||||
edits.push((range, to_insert));
|
||||
original_indent_columns.push(original_indent_column);
|
||||
@@ -12545,53 +12525,7 @@ impl Editor {
|
||||
let selections = this.selections.all::<usize>(cx);
|
||||
this.change_selections(Default::default(), window, cx, |s| s.select(selections));
|
||||
} else {
|
||||
let url = url::Url::parse(&clipboard_text).ok();
|
||||
|
||||
let auto_indent_mode = if !clipboard_text.is_empty() {
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns: Vec::new(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let selection_anchors = this.buffer.update(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
|
||||
let anchors = old_selections
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let anchor = snapshot.anchor_after(s.head());
|
||||
s.map(|_| anchor)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut edits = Vec::new();
|
||||
|
||||
for selection in old_selections.iter() {
|
||||
let language = snapshot.language_at(selection.head());
|
||||
let range = selection.range();
|
||||
|
||||
let (edit_range, edit_text) = if let Some(language) = language
|
||||
&& language.name() == "Markdown".into()
|
||||
{
|
||||
edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
|
||||
} else {
|
||||
(range, clipboard_text.clone())
|
||||
};
|
||||
|
||||
edits.push((edit_range, edit_text));
|
||||
}
|
||||
|
||||
drop(snapshot);
|
||||
buffer.edit(edits, auto_indent_mode, cx);
|
||||
|
||||
anchors
|
||||
});
|
||||
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
s.select_anchors(selection_anchors);
|
||||
});
|
||||
this.insert(&clipboard_text, window, cx);
|
||||
}
|
||||
|
||||
let trigger_in_words =
|
||||
@@ -14272,8 +14206,8 @@ impl Editor {
|
||||
start_offset + query_match.start()..start_offset + query_match.end();
|
||||
|
||||
if !select_next_state.wordwise
|
||||
|| (!buffer.is_inside_word(offset_range.start, None)
|
||||
&& !buffer.is_inside_word(offset_range.end, None))
|
||||
|| (!buffer.is_inside_word(offset_range.start, false)
|
||||
&& !buffer.is_inside_word(offset_range.end, false))
|
||||
{
|
||||
// TODO: This is n^2, because we might check all the selections
|
||||
if !selections
|
||||
@@ -14337,7 +14271,7 @@ impl Editor {
|
||||
|
||||
if only_carets {
|
||||
for selection in &mut selections {
|
||||
let (word_range, _) = buffer.surrounding_word(selection.start, None);
|
||||
let (word_range, _) = buffer.surrounding_word(selection.start, false);
|
||||
selection.start = word_range.start;
|
||||
selection.end = word_range.end;
|
||||
selection.goal = SelectionGoal::None;
|
||||
@@ -14422,8 +14356,8 @@ impl Editor {
|
||||
};
|
||||
|
||||
if !select_next_state.wordwise
|
||||
|| (!buffer.is_inside_word(offset_range.start, None)
|
||||
&& !buffer.is_inside_word(offset_range.end, None))
|
||||
|| (!buffer.is_inside_word(offset_range.start, false)
|
||||
&& !buffer.is_inside_word(offset_range.end, false))
|
||||
{
|
||||
new_selections.push(offset_range.start..offset_range.end);
|
||||
}
|
||||
@@ -14497,8 +14431,8 @@ impl Editor {
|
||||
end_offset - query_match.end()..end_offset - query_match.start();
|
||||
|
||||
if !select_prev_state.wordwise
|
||||
|| (!buffer.is_inside_word(offset_range.start, None)
|
||||
&& !buffer.is_inside_word(offset_range.end, None))
|
||||
|| (!buffer.is_inside_word(offset_range.start, false)
|
||||
&& !buffer.is_inside_word(offset_range.end, false))
|
||||
{
|
||||
next_selected_range = Some(offset_range);
|
||||
break;
|
||||
@@ -14556,7 +14490,7 @@ impl Editor {
|
||||
|
||||
if only_carets {
|
||||
for selection in &mut selections {
|
||||
let (word_range, _) = buffer.surrounding_word(selection.start, None);
|
||||
let (word_range, _) = buffer.surrounding_word(selection.start, false);
|
||||
selection.start = word_range.start;
|
||||
selection.end = word_range.end;
|
||||
selection.goal = SelectionGoal::None;
|
||||
@@ -15034,10 +14968,11 @@ impl Editor {
|
||||
if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
|
||||
// manually select word at selection
|
||||
if ["string_content", "inline"].contains(&node.kind()) {
|
||||
let (word_range, _) = buffer.surrounding_word(old_range.start, None);
|
||||
let (word_range, _) = buffer.surrounding_word(old_range.start, false);
|
||||
// ignore if word is already selected
|
||||
if !word_range.is_empty() && old_range != word_range {
|
||||
let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
|
||||
let (last_word_range, _) =
|
||||
buffer.surrounding_word(old_range.end, false);
|
||||
// only select word if start and end point belongs to same word
|
||||
if word_range == last_word_range {
|
||||
selected_larger_node = true;
|
||||
@@ -16405,30 +16340,15 @@ impl Editor {
|
||||
|
||||
let workspace = self.workspace();
|
||||
|
||||
cx.spawn_in(window, async move |editor, cx| {
|
||||
let locations: Vec<Location> = future::join_all(definitions)
|
||||
cx.spawn_in(window, async move |editor, acx| {
|
||||
let mut locations: Vec<Location> = future::join_all(definitions)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|location| location.transpose())
|
||||
.collect::<Result<_>>()
|
||||
.context("location tasks")?;
|
||||
let mut locations = cx.update(|_, cx| {
|
||||
locations
|
||||
.into_iter()
|
||||
.map(|location| {
|
||||
let buffer = location.buffer.read(cx);
|
||||
(location.buffer, location.range.to_point(buffer))
|
||||
})
|
||||
.into_group_map()
|
||||
})?;
|
||||
let mut num_locations = 0;
|
||||
for ranges in locations.values_mut() {
|
||||
ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
|
||||
ranges.dedup();
|
||||
num_locations += ranges.len();
|
||||
}
|
||||
|
||||
if num_locations > 1 {
|
||||
if locations.len() > 1 {
|
||||
let Some(workspace) = workspace else {
|
||||
return Ok(Navigated::No);
|
||||
};
|
||||
@@ -16440,14 +16360,14 @@ impl Editor {
|
||||
Some(GotoDefinitionKind::Type) => "Types",
|
||||
};
|
||||
let title = editor
|
||||
.update_in(cx, |_, _, cx| {
|
||||
.update_in(acx, |_, _, cx| {
|
||||
let target = locations
|
||||
.iter()
|
||||
.flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
|
||||
.map(|(buffer, location)| {
|
||||
buffer
|
||||
.map(|location| {
|
||||
location
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_for_range(location.clone())
|
||||
.text_for_range(location.range.clone())
|
||||
.collect::<String>()
|
||||
})
|
||||
.filter(|text| !text.contains('\n'))
|
||||
@@ -16463,7 +16383,7 @@ impl Editor {
|
||||
.context("buffer title")?;
|
||||
|
||||
let opened = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
.update_in(acx, |workspace, window, cx| {
|
||||
Self::open_locations_in_multibuffer(
|
||||
workspace,
|
||||
locations,
|
||||
@@ -16477,11 +16397,11 @@ impl Editor {
|
||||
.is_ok();
|
||||
|
||||
anyhow::Ok(Navigated::from_bool(opened))
|
||||
} else if num_locations == 0 {
|
||||
} else if locations.is_empty() {
|
||||
// If there is one url or file, open it directly
|
||||
match first_url_or_file {
|
||||
Some(Either::Left(url)) => {
|
||||
cx.update(|_, cx| cx.open_url(&url))?;
|
||||
acx.update(|_, cx| cx.open_url(&url))?;
|
||||
Ok(Navigated::Yes)
|
||||
}
|
||||
Some(Either::Right(path)) => {
|
||||
@@ -16490,7 +16410,7 @@ impl Editor {
|
||||
};
|
||||
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
.update_in(acx, |workspace, window, cx| {
|
||||
workspace.open_resolved_path(path, window, cx)
|
||||
})?
|
||||
.await?;
|
||||
@@ -16503,16 +16423,14 @@ impl Editor {
|
||||
return Ok(Navigated::No);
|
||||
};
|
||||
|
||||
let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
|
||||
let target_range = target_ranges.first().unwrap().clone();
|
||||
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
let range = target_range.to_point(target_buffer.read(cx));
|
||||
let target = locations.pop().unwrap();
|
||||
editor.update_in(acx, |editor, window, cx| {
|
||||
let range = target.range.to_point(target.buffer.read(cx));
|
||||
let range = editor.range_for_match(&range);
|
||||
let range = collapse_multiline_range(range);
|
||||
|
||||
if !split
|
||||
&& Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
|
||||
&& Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
|
||||
{
|
||||
editor.go_to_singleton_buffer_range(range, window, cx);
|
||||
} else {
|
||||
@@ -16528,7 +16446,7 @@ impl Editor {
|
||||
|
||||
workspace.open_project_item(
|
||||
pane,
|
||||
target_buffer.clone(),
|
||||
target.buffer.clone(),
|
||||
true,
|
||||
true,
|
||||
window,
|
||||
@@ -16638,31 +16556,18 @@ impl Editor {
|
||||
let Some(locations) = references.await? else {
|
||||
return anyhow::Ok(Navigated::No);
|
||||
};
|
||||
let mut locations = cx.update(|_, cx| {
|
||||
locations
|
||||
.into_iter()
|
||||
.map(|location| {
|
||||
let buffer = location.buffer.read(cx);
|
||||
(location.buffer, location.range.to_point(buffer))
|
||||
})
|
||||
.into_group_map()
|
||||
})?;
|
||||
if locations.is_empty() {
|
||||
return anyhow::Ok(Navigated::No);
|
||||
}
|
||||
for ranges in locations.values_mut() {
|
||||
ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
|
||||
ranges.dedup();
|
||||
}
|
||||
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
let target = locations
|
||||
.iter()
|
||||
.flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
|
||||
.map(|(buffer, location)| {
|
||||
buffer
|
||||
.map(|location| {
|
||||
location
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_for_range(location.clone())
|
||||
.text_for_range(location.range.clone())
|
||||
.collect::<String>()
|
||||
})
|
||||
.filter(|text| !text.contains('\n'))
|
||||
@@ -16691,7 +16596,7 @@ impl Editor {
|
||||
/// Opens a multibuffer with the given project locations in it
|
||||
pub fn open_locations_in_multibuffer(
|
||||
workspace: &mut Workspace,
|
||||
locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
|
||||
mut locations: Vec<Location>,
|
||||
title: String,
|
||||
split: bool,
|
||||
multibuffer_selection_mode: MultibufferSelectionMode,
|
||||
@@ -16703,8 +16608,11 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
|
||||
|
||||
let mut locations = locations.into_iter().peekable();
|
||||
let mut ranges: Vec<Range<Anchor>> = Vec::new();
|
||||
let capability = workspace.project().read(cx).capability();
|
||||
let mut ranges = <Vec<Range<Anchor>>>::new();
|
||||
|
||||
// a key to find existing multibuffer editors with the same set of locations
|
||||
// to prevent us from opening more and more multibuffer tabs for searches and the like
|
||||
@@ -16712,12 +16620,26 @@ impl Editor {
|
||||
let excerpt_buffer = cx.new(|cx| {
|
||||
let key = &mut key.1;
|
||||
let mut multibuffer = MultiBuffer::new(capability);
|
||||
for (buffer, mut ranges_for_buffer) in locations {
|
||||
while let Some(location) = locations.next() {
|
||||
let buffer = location.buffer.read(cx);
|
||||
let mut ranges_for_buffer = Vec::new();
|
||||
let range = location.range.to_point(buffer);
|
||||
ranges_for_buffer.push(range.clone());
|
||||
|
||||
while let Some(next_location) =
|
||||
locations.next_if(|next_location| next_location.buffer == location.buffer)
|
||||
{
|
||||
ranges_for_buffer.push(next_location.range.to_point(buffer));
|
||||
}
|
||||
|
||||
ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
|
||||
key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
|
||||
key.push((
|
||||
location.buffer.read(cx).remote_id(),
|
||||
ranges_for_buffer.clone(),
|
||||
));
|
||||
let (new_ranges, _) = multibuffer.set_excerpts_for_path(
|
||||
PathKey::for_buffer(&buffer, cx),
|
||||
buffer.clone(),
|
||||
PathKey::for_buffer(&location.buffer, cx),
|
||||
location.buffer.clone(),
|
||||
ranges_for_buffer,
|
||||
multibuffer_context_lines(cx),
|
||||
cx,
|
||||
@@ -19325,8 +19247,6 @@ impl Editor {
|
||||
&& let Some(path) = path.to_str()
|
||||
{
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19340,8 +19260,6 @@ impl Editor {
|
||||
&& let Some(path) = path.to_str()
|
||||
{
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19799,14 +19717,11 @@ impl Editor {
|
||||
.selections
|
||||
.all_anchors(cx)
|
||||
.iter()
|
||||
.map(|selection| {
|
||||
(
|
||||
buffer.clone(),
|
||||
(selection.start.text_anchor..selection.end.text_anchor)
|
||||
.to_point(buffer.read(cx)),
|
||||
)
|
||||
.map(|selection| Location {
|
||||
buffer: buffer.clone(),
|
||||
range: selection.start.text_anchor..selection.end.text_anchor,
|
||||
})
|
||||
.into_group_map();
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
@@ -21763,30 +21678,12 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn edit_for_markdown_paste<'a>(
|
||||
buffer: &MultiBufferSnapshot,
|
||||
range: Range<usize>,
|
||||
to_insert: &'a str,
|
||||
url: Option<url::Url>,
|
||||
) -> (Range<usize>, Cow<'a, str>) {
|
||||
if url.is_none() {
|
||||
return (range, Cow::Borrowed(to_insert));
|
||||
};
|
||||
|
||||
let old_text = buffer.text_for_range(range.clone()).collect::<String>();
|
||||
|
||||
let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
|
||||
Cow::Borrowed(to_insert)
|
||||
} else {
|
||||
Cow::Owned(format!("[{old_text}]({to_insert})"))
|
||||
};
|
||||
(range, new_text)
|
||||
}
|
||||
|
||||
// todo(settings_refactor) this should not be!
|
||||
fn vim_enabled(cx: &App) -> bool {
|
||||
vim_mode_setting::VimModeSetting::try_get(cx)
|
||||
.map(|vim_mode| vim_mode.0)
|
||||
.unwrap_or(false)
|
||||
cx.global::<SettingsStore>()
|
||||
.raw_user_settings()
|
||||
.and_then(|settings| settings.content.vim_mode)
|
||||
== Some(true)
|
||||
}
|
||||
|
||||
fn process_completion_for_edit(
|
||||
@@ -22650,8 +22547,7 @@ fn snippet_completions(
|
||||
let mut is_incomplete = false;
|
||||
let mut completions: Vec<Completion> = Vec::new();
|
||||
for (scope, snippets) in scopes.into_iter() {
|
||||
let classifier =
|
||||
CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
|
||||
let classifier = CharClassifier::new(Some(scope)).for_completion(true);
|
||||
let mut last_word = chars
|
||||
.chars()
|
||||
.take_while(|c| classifier.is_word(*c))
|
||||
@@ -22872,9 +22768,7 @@ impl CompletionProvider for Entity<Project> {
|
||||
if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
|
||||
return false;
|
||||
}
|
||||
let classifier = snapshot
|
||||
.char_classifier_at(position)
|
||||
.scope_context(Some(CharScopeContext::Completion));
|
||||
let classifier = snapshot.char_classifier_at(position).for_completion(true);
|
||||
if trigger_in_words && classifier.is_word(char) {
|
||||
return true;
|
||||
}
|
||||
@@ -22987,7 +22881,7 @@ impl SemanticsProvider for Entity<Project> {
|
||||
// Fallback on using TreeSitter info to determine identifier range
|
||||
buffer.read_with(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
let (range, kind) = snapshot.surrounding_word(position, None);
|
||||
let (range, kind) = snapshot.surrounding_word(position, false);
|
||||
if kind != Some(CharKind::Word) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ pub use settings::{
|
||||
};
|
||||
use settings::{Settings, SettingsContent};
|
||||
use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar};
|
||||
use util::MergeFrom;
|
||||
|
||||
/// Imports from the VSCode settings at
|
||||
/// https://code.visualstudio.com/docs/reference/default-settings
|
||||
@@ -189,7 +190,7 @@ impl ScrollbarVisibility for EditorSettings {
|
||||
}
|
||||
|
||||
impl Settings for EditorSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let editor = content.editor.clone();
|
||||
let scrollbar = editor.scrollbar.unwrap();
|
||||
let minimap = editor.minimap.unwrap();
|
||||
@@ -237,7 +238,7 @@ impl Settings for EditorSettings {
|
||||
display_in: minimap.display_in.unwrap(),
|
||||
thumb: minimap.thumb.unwrap(),
|
||||
thumb_border: minimap.thumb_border.unwrap(),
|
||||
current_line_highlight: minimap.current_line_highlight,
|
||||
current_line_highlight: minimap.current_line_highlight.flatten(),
|
||||
max_width_columns: minimap.max_width_columns.unwrap(),
|
||||
},
|
||||
gutter: Gutter {
|
||||
@@ -289,6 +290,162 @@ impl Settings for EditorSettings {
|
||||
}
|
||||
}
|
||||
|
||||
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
|
||||
let editor = &content.editor;
|
||||
self.cursor_blink.merge_from(&editor.cursor_blink);
|
||||
if let Some(cursor_shape) = editor.cursor_shape {
|
||||
self.cursor_shape = Some(cursor_shape.into())
|
||||
}
|
||||
self.current_line_highlight
|
||||
.merge_from(&editor.current_line_highlight);
|
||||
self.selection_highlight
|
||||
.merge_from(&editor.selection_highlight);
|
||||
self.rounded_selection.merge_from(&editor.rounded_selection);
|
||||
self.lsp_highlight_debounce
|
||||
.merge_from(&editor.lsp_highlight_debounce);
|
||||
self.hover_popover_enabled
|
||||
.merge_from(&editor.hover_popover_enabled);
|
||||
self.hover_popover_delay
|
||||
.merge_from(&editor.hover_popover_delay);
|
||||
self.scroll_beyond_last_line
|
||||
.merge_from(&editor.scroll_beyond_last_line);
|
||||
self.vertical_scroll_margin
|
||||
.merge_from(&editor.vertical_scroll_margin);
|
||||
self.autoscroll_on_clicks
|
||||
.merge_from(&editor.autoscroll_on_clicks);
|
||||
self.horizontal_scroll_margin
|
||||
.merge_from(&editor.horizontal_scroll_margin);
|
||||
self.scroll_sensitivity
|
||||
.merge_from(&editor.scroll_sensitivity);
|
||||
self.fast_scroll_sensitivity
|
||||
.merge_from(&editor.fast_scroll_sensitivity);
|
||||
self.relative_line_numbers
|
||||
.merge_from(&editor.relative_line_numbers);
|
||||
self.seed_search_query_from_cursor
|
||||
.merge_from(&editor.seed_search_query_from_cursor);
|
||||
self.use_smartcase_search
|
||||
.merge_from(&editor.use_smartcase_search);
|
||||
self.multi_cursor_modifier
|
||||
.merge_from(&editor.multi_cursor_modifier);
|
||||
self.redact_private_values
|
||||
.merge_from(&editor.redact_private_values);
|
||||
self.expand_excerpt_lines
|
||||
.merge_from(&editor.expand_excerpt_lines);
|
||||
self.excerpt_context_lines
|
||||
.merge_from(&editor.excerpt_context_lines);
|
||||
self.middle_click_paste
|
||||
.merge_from(&editor.middle_click_paste);
|
||||
self.double_click_in_multibuffer
|
||||
.merge_from(&editor.double_click_in_multibuffer);
|
||||
self.search_wrap.merge_from(&editor.search_wrap);
|
||||
self.auto_signature_help
|
||||
.merge_from(&editor.auto_signature_help);
|
||||
self.show_signature_help_after_edits
|
||||
.merge_from(&editor.show_signature_help_after_edits);
|
||||
self.go_to_definition_fallback
|
||||
.merge_from(&editor.go_to_definition_fallback);
|
||||
if let Some(hide_mouse) = editor.hide_mouse {
|
||||
self.hide_mouse = Some(hide_mouse)
|
||||
}
|
||||
self.snippet_sort_order
|
||||
.merge_from(&editor.snippet_sort_order);
|
||||
if let Some(diagnostics_max_severity) = editor.diagnostics_max_severity {
|
||||
self.diagnostics_max_severity = Some(diagnostics_max_severity.into());
|
||||
}
|
||||
self.inline_code_actions
|
||||
.merge_from(&editor.inline_code_actions);
|
||||
self.lsp_document_colors
|
||||
.merge_from(&editor.lsp_document_colors);
|
||||
self.minimum_contrast_for_highlights
|
||||
.merge_from(&editor.minimum_contrast_for_highlights);
|
||||
|
||||
if let Some(status_bar) = &editor.status_bar {
|
||||
self.status_bar
|
||||
.active_language_button
|
||||
.merge_from(&status_bar.active_language_button);
|
||||
self.status_bar
|
||||
.cursor_position_button
|
||||
.merge_from(&status_bar.cursor_position_button);
|
||||
}
|
||||
if let Some(toolbar) = &editor.toolbar {
|
||||
self.toolbar.breadcrumbs.merge_from(&toolbar.breadcrumbs);
|
||||
self.toolbar
|
||||
.quick_actions
|
||||
.merge_from(&toolbar.quick_actions);
|
||||
self.toolbar
|
||||
.selections_menu
|
||||
.merge_from(&toolbar.selections_menu);
|
||||
self.toolbar.agent_review.merge_from(&toolbar.agent_review);
|
||||
self.toolbar.code_actions.merge_from(&toolbar.code_actions);
|
||||
}
|
||||
if let Some(scrollbar) = &editor.scrollbar {
|
||||
self.scrollbar
|
||||
.show
|
||||
.merge_from(&scrollbar.show.map(Into::into));
|
||||
self.scrollbar.git_diff.merge_from(&scrollbar.git_diff);
|
||||
self.scrollbar
|
||||
.selected_text
|
||||
.merge_from(&scrollbar.selected_text);
|
||||
self.scrollbar
|
||||
.selected_symbol
|
||||
.merge_from(&scrollbar.selected_symbol);
|
||||
self.scrollbar
|
||||
.search_results
|
||||
.merge_from(&scrollbar.search_results);
|
||||
self.scrollbar
|
||||
.diagnostics
|
||||
.merge_from(&scrollbar.diagnostics);
|
||||
self.scrollbar.cursors.merge_from(&scrollbar.cursors);
|
||||
if let Some(axes) = &scrollbar.axes {
|
||||
self.scrollbar.axes.horizontal.merge_from(&axes.horizontal);
|
||||
self.scrollbar.axes.vertical.merge_from(&axes.vertical);
|
||||
}
|
||||
}
|
||||
if let Some(minimap) = &editor.minimap {
|
||||
self.minimap.show.merge_from(&minimap.show);
|
||||
self.minimap.display_in.merge_from(&minimap.display_in);
|
||||
self.minimap.thumb.merge_from(&minimap.thumb);
|
||||
self.minimap.thumb_border.merge_from(&minimap.thumb_border);
|
||||
self.minimap
|
||||
.current_line_highlight
|
||||
.merge_from(&minimap.current_line_highlight);
|
||||
self.minimap
|
||||
.max_width_columns
|
||||
.merge_from(&minimap.max_width_columns);
|
||||
}
|
||||
if let Some(gutter) = &editor.gutter {
|
||||
self.gutter
|
||||
.min_line_number_digits
|
||||
.merge_from(&gutter.min_line_number_digits);
|
||||
self.gutter.line_numbers.merge_from(&gutter.line_numbers);
|
||||
self.gutter.runnables.merge_from(&gutter.runnables);
|
||||
self.gutter.breakpoints.merge_from(&gutter.breakpoints);
|
||||
self.gutter.folds.merge_from(&gutter.folds);
|
||||
}
|
||||
if let Some(search) = &editor.search {
|
||||
self.search.button.merge_from(&search.button);
|
||||
self.search.whole_word.merge_from(&search.whole_word);
|
||||
self.search
|
||||
.case_sensitive
|
||||
.merge_from(&search.case_sensitive);
|
||||
self.search
|
||||
.include_ignored
|
||||
.merge_from(&search.include_ignored);
|
||||
self.search.regex.merge_from(&search.regex);
|
||||
}
|
||||
if let Some(enabled) = editor.jupyter.as_ref().and_then(|jupyter| jupyter.enabled) {
|
||||
self.jupyter.enabled = enabled;
|
||||
}
|
||||
if let Some(drag_and_drop_selection) = &editor.drag_and_drop_selection {
|
||||
self.drag_and_drop_selection
|
||||
.enabled
|
||||
.merge_from(&drag_and_drop_selection.enabled);
|
||||
self.drag_and_drop_selection
|
||||
.delay
|
||||
.merge_from(&drag_and_drop_selection.delay);
|
||||
}
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &VsCodeSettings, current: &mut SettingsContent) {
|
||||
vscode.enum_setting(
|
||||
"editor.cursorBlinking",
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
|
||||
use collections::HashMap;
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
|
||||
@@ -23774,28 +23773,6 @@ async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
fn set_linked_edit_ranges(
|
||||
opening: (Point, Point),
|
||||
closing: (Point, Point),
|
||||
editor: &mut Editor,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
let Some((buffer, _)) = editor
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_anchor_for_position(editor.selections.newest_anchor().start, cx)
|
||||
else {
|
||||
panic!("Failed to get buffer for selection position");
|
||||
};
|
||||
let buffer = buffer.read(cx);
|
||||
let buffer_id = buffer.remote_id();
|
||||
let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
|
||||
let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
|
||||
let mut linked_ranges = HashMap::default();
|
||||
linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
|
||||
editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -23874,12 +23851,22 @@ async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
|
||||
});
|
||||
set_linked_edit_ranges(
|
||||
(Point::new(0, 1), Point::new(0, 3)),
|
||||
(Point::new(0, 6), Point::new(0, 8)),
|
||||
editor,
|
||||
cx,
|
||||
);
|
||||
let Some((buffer, _)) = editor
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_anchor_for_position(editor.selections.newest_anchor().start, cx)
|
||||
else {
|
||||
panic!("Failed to get buffer for selection position");
|
||||
};
|
||||
let buffer = buffer.read(cx);
|
||||
let buffer_id = buffer.remote_id();
|
||||
let opening_range =
|
||||
buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
|
||||
let closing_range =
|
||||
buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
|
||||
let mut linked_ranges = HashMap::default();
|
||||
linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
|
||||
editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
|
||||
});
|
||||
let mut completion_handle =
|
||||
fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
|
||||
@@ -23923,77 +23910,6 @@ async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "TSX".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["tsx".to_string()],
|
||||
..LanguageMatcher::default()
|
||||
},
|
||||
brackets: BracketPairConfig {
|
||||
pairs: vec![BracketPair {
|
||||
start: "<".into(),
|
||||
end: ">".into(),
|
||||
close: true,
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
linked_edit_characters: HashSet::from_iter(['.']),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
|
||||
));
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
|
||||
// Test typing > does not extend linked pair
|
||||
cx.set_state("<divˇ<div></div>");
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
set_linked_edit_ranges(
|
||||
(Point::new(0, 1), Point::new(0, 4)),
|
||||
(Point::new(0, 11), Point::new(0, 14)),
|
||||
editor,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.handle_input(">", window, cx);
|
||||
});
|
||||
cx.assert_editor_state("<div>ˇ<div></div>");
|
||||
|
||||
// Test typing . do extend linked pair
|
||||
cx.set_state("<Animatedˇ></Animated>");
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
set_linked_edit_ranges(
|
||||
(Point::new(0, 1), Point::new(0, 9)),
|
||||
(Point::new(0, 12), Point::new(0, 20)),
|
||||
editor,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.handle_input(".", window, cx);
|
||||
});
|
||||
cx.assert_editor_state("<Animated.ˇ></Animated.>");
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
set_linked_edit_ranges(
|
||||
(Point::new(0, 1), Point::new(0, 10)),
|
||||
(Point::new(0, 13), Point::new(0, 21)),
|
||||
editor,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.handle_input("V", window, cx);
|
||||
});
|
||||
cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -25701,7 +25617,7 @@ async fn test_document_colors(cx: &mut TestAppContext) {
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.navigate_backward(&workspace::GoBack, window, cx);
|
||||
pane.navigate_backward(&Default::default(), window, cx);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
@@ -25729,50 +25645,6 @@ async fn test_document_colors(cx: &mut TestAppContext) {
|
||||
"Should have an initial inlay"
|
||||
);
|
||||
});
|
||||
|
||||
drop(color_request_handle);
|
||||
let closure_requests_made = Arc::clone(&requests_made);
|
||||
let mut empty_color_request_handle = fake_language_server
|
||||
.set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
|
||||
let requests_made = Arc::clone(&closure_requests_made);
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
|
||||
);
|
||||
requests_made.fetch_add(1, atomic::Ordering::Release);
|
||||
Ok(Vec::new())
|
||||
}
|
||||
});
|
||||
let save = editor.update_in(cx, |editor, window, cx| {
|
||||
editor.move_to_end(&MoveToEnd, window, cx);
|
||||
editor.handle_input("dirty_again", window, cx);
|
||||
editor.save(
|
||||
SaveOptions {
|
||||
format: false,
|
||||
autosave: true,
|
||||
},
|
||||
project.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
save.await.unwrap();
|
||||
|
||||
empty_color_request_handle.next().await.unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
4,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"Should query for colors once per save only, as formatting was not requested"
|
||||
);
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
Vec::<Rgba>::new(),
|
||||
extract_color_inlays(editor, cx),
|
||||
"Should clear all colors when the server returns an empty response"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -26018,217 +25890,6 @@ let result = variable * 2;",
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let url = "https://zed.dev";
|
||||
|
||||
let markdown_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Markdown".into(),
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
|
||||
cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
|
||||
editor.paste(&Paste, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(&format!(
|
||||
"Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
|
||||
));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let url = "https://zed.dev";
|
||||
|
||||
let markdown_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Markdown".into(),
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
|
||||
cx.set_state(&format!(
|
||||
"Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
|
||||
));
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.copy(&Copy, window, cx);
|
||||
});
|
||||
|
||||
cx.set_state(&format!(
|
||||
"Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
|
||||
));
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.paste(&Paste, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(&format!(
|
||||
"Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
|
||||
));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let url = "https://zed.dev";
|
||||
|
||||
let markdown_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Markdown".into(),
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
|
||||
cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
|
||||
editor.paste(&Paste, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let text = "Awesome";
|
||||
|
||||
let markdown_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Markdown".into(),
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
|
||||
cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
|
||||
editor.paste(&Paste, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let url = "https://zed.dev";
|
||||
|
||||
let markdown_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
|
||||
cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
|
||||
editor.paste(&Paste, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(&format!(
|
||||
"// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
|
||||
));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let url = "https://zed.dev";
|
||||
|
||||
let markdown_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Markdown".into(),
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
|
||||
let (editor, cx) = cx.add_window_view(|window, cx| {
|
||||
let multi_buffer = MultiBuffer::build_multi(
|
||||
[
|
||||
("this will embed -> link", vec![Point::row_range(0..1)]),
|
||||
("this will replace -> link", vec![Point::row_range(0..1)]),
|
||||
],
|
||||
cx,
|
||||
);
|
||||
let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges(vec![
|
||||
Point::new(0, 19)..Point::new(0, 23),
|
||||
Point::new(1, 21)..Point::new(1, 25),
|
||||
])
|
||||
});
|
||||
let first_buffer_id = multi_buffer
|
||||
.read(cx)
|
||||
.excerpt_buffer_ids()
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
|
||||
first_buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_language(Some(markdown_language.clone()), cx);
|
||||
});
|
||||
|
||||
editor
|
||||
});
|
||||
let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
|
||||
editor.paste(&Paste, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(&format!(
|
||||
"this will embed -> [link]({url})ˇ\nthis will replace -> {url}ˇ"
|
||||
));
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
|
||||
editor
|
||||
|
||||
@@ -3838,11 +3838,7 @@ impl EditorElement {
|
||||
Tooltip::with_meta_in(
|
||||
"Toggle Excerpt Fold",
|
||||
Some(&ToggleFold),
|
||||
if cfg!(target_os = "macos") {
|
||||
"Option+click to toggle all"
|
||||
} else {
|
||||
"Alt+click to toggle all"
|
||||
},
|
||||
"Alt+click to toggle all",
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
@@ -7616,7 +7612,7 @@ impl LineWithInvisibles {
|
||||
let text_runs: &[TextRun] = if segments.is_empty() {
|
||||
&styles
|
||||
} else {
|
||||
&Self::split_runs_by_bg_segments(&styles, segments, min_contrast, len)
|
||||
&Self::split_runs_by_bg_segments(&styles, segments, min_contrast)
|
||||
};
|
||||
let shaped_line = window.text_system().shape_line(
|
||||
line.clone().into(),
|
||||
@@ -7703,7 +7699,7 @@ impl LineWithInvisibles {
|
||||
let text_runs = if segments.is_empty() {
|
||||
&styles
|
||||
} else {
|
||||
&Self::split_runs_by_bg_segments(&styles, segments, min_contrast, len)
|
||||
&Self::split_runs_by_bg_segments(&styles, segments, min_contrast)
|
||||
};
|
||||
let shaped_line = window.text_system().shape_line(
|
||||
line.clone().into(),
|
||||
@@ -7802,10 +7798,9 @@ impl LineWithInvisibles {
|
||||
text_runs: &[TextRun],
|
||||
bg_segments: &[(Range<DisplayPoint>, Hsla)],
|
||||
min_contrast: f32,
|
||||
start_col_offset: usize,
|
||||
) -> Vec<TextRun> {
|
||||
let mut output_runs: Vec<TextRun> = Vec::with_capacity(text_runs.len());
|
||||
let mut line_col = start_col_offset;
|
||||
let mut line_col = 0usize;
|
||||
let mut segment_ix = 0usize;
|
||||
|
||||
for text_run in text_runs.iter() {
|
||||
@@ -11255,143 +11250,102 @@ mod tests {
|
||||
fn test_split_runs_by_bg_segments(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let dx = |start: u32, end: u32| {
|
||||
DisplayPoint::new(DisplayRow(0), start)..DisplayPoint::new(DisplayRow(0), end)
|
||||
};
|
||||
|
||||
let text_color = Hsla {
|
||||
h: 210.0,
|
||||
s: 0.1,
|
||||
l: 0.4,
|
||||
a: 1.0,
|
||||
};
|
||||
let bg_1 = Hsla {
|
||||
let bg1 = Hsla {
|
||||
h: 30.0,
|
||||
s: 0.6,
|
||||
l: 0.8,
|
||||
a: 1.0,
|
||||
};
|
||||
let bg_2 = Hsla {
|
||||
let bg2 = Hsla {
|
||||
h: 200.0,
|
||||
s: 0.6,
|
||||
l: 0.2,
|
||||
a: 1.0,
|
||||
};
|
||||
let min_contrast = 45.0;
|
||||
let adjusted_bg1 = ensure_minimum_contrast(text_color, bg_1, min_contrast);
|
||||
let adjusted_bg2 = ensure_minimum_contrast(text_color, bg_2, min_contrast);
|
||||
|
||||
// Case A: single run; disjoint segments inside the run
|
||||
{
|
||||
let runs = vec![generate_test_run(20, text_color)];
|
||||
let segs = vec![(dx(5, 10), bg_1), (dx(12, 16), bg_2)];
|
||||
let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 0);
|
||||
// Expected slices: [0,5) [5,10) [10,12) [12,16) [16,20)
|
||||
assert_eq!(
|
||||
out.iter().map(|r| r.len).collect::<Vec<_>>(),
|
||||
vec![5, 5, 2, 4, 4]
|
||||
);
|
||||
assert_eq!(out[0].color, text_color);
|
||||
assert_eq!(out[1].color, adjusted_bg1);
|
||||
assert_eq!(out[2].color, text_color);
|
||||
assert_eq!(out[3].color, adjusted_bg2);
|
||||
assert_eq!(out[4].color, text_color);
|
||||
}
|
||||
let runs = vec![generate_test_run(20, text_color)];
|
||||
let segs = vec![
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10),
|
||||
bg1,
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 16),
|
||||
bg2,
|
||||
),
|
||||
];
|
||||
let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast);
|
||||
// Expected slices: [0,5) [5,10) [10,12) [12,16) [16,20)
|
||||
assert_eq!(
|
||||
out.iter().map(|r| r.len).collect::<Vec<_>>(),
|
||||
vec![5, 5, 2, 4, 4]
|
||||
);
|
||||
assert_eq!(out[0].color, text_color);
|
||||
assert_eq!(
|
||||
out[1].color,
|
||||
ensure_minimum_contrast(text_color, bg1, min_contrast)
|
||||
);
|
||||
assert_eq!(out[2].color, text_color);
|
||||
assert_eq!(
|
||||
out[3].color,
|
||||
ensure_minimum_contrast(text_color, bg2, min_contrast)
|
||||
);
|
||||
assert_eq!(out[4].color, text_color);
|
||||
|
||||
// Case B: multiple runs; segment extends to end of line (u32::MAX)
|
||||
{
|
||||
let runs = vec![
|
||||
generate_test_run(8, text_color),
|
||||
generate_test_run(7, text_color),
|
||||
];
|
||||
let segs = vec![(dx(6, u32::MAX), bg_1)];
|
||||
let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 0);
|
||||
// Expected slices across runs: [0,6) [6,8) | [0,7)
|
||||
assert_eq!(out.iter().map(|r| r.len).collect::<Vec<_>>(), vec![6, 2, 7]);
|
||||
assert_eq!(out[0].color, text_color);
|
||||
assert_eq!(out[1].color, adjusted_bg1);
|
||||
assert_eq!(out[2].color, adjusted_bg1);
|
||||
}
|
||||
let runs = vec![
|
||||
generate_test_run(8, text_color),
|
||||
generate_test_run(7, text_color),
|
||||
];
|
||||
let segs = vec![(
|
||||
DisplayPoint::new(DisplayRow(0), 6)..DisplayPoint::new(DisplayRow(0), u32::MAX),
|
||||
bg1,
|
||||
)];
|
||||
let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast);
|
||||
// Expected slices across runs: [0,6) [6,8) | [0,7)
|
||||
assert_eq!(out.iter().map(|r| r.len).collect::<Vec<_>>(), vec![6, 2, 7]);
|
||||
let adjusted = ensure_minimum_contrast(text_color, bg1, min_contrast);
|
||||
assert_eq!(out[0].color, text_color);
|
||||
assert_eq!(out[1].color, adjusted);
|
||||
assert_eq!(out[2].color, adjusted);
|
||||
|
||||
// Case C: multi-byte characters
|
||||
{
|
||||
// for text: "Hello 🌍 世界!"
|
||||
let runs = vec![
|
||||
generate_test_run(5, text_color), // "Hello"
|
||||
generate_test_run(6, text_color), // " 🌍 "
|
||||
generate_test_run(6, text_color), // "世界"
|
||||
generate_test_run(1, text_color), // "!"
|
||||
];
|
||||
// selecting "🌍 世"
|
||||
let segs = vec![(dx(6, 14), bg_1)];
|
||||
let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 0);
|
||||
// "Hello" | " " | "🌍 " | "世" | "界" | "!"
|
||||
assert_eq!(
|
||||
out.iter().map(|r| r.len).collect::<Vec<_>>(),
|
||||
vec![5, 1, 5, 3, 3, 1]
|
||||
);
|
||||
assert_eq!(out[0].color, text_color); // "Hello"
|
||||
assert_eq!(out[2].color, adjusted_bg1); // "🌍 "
|
||||
assert_eq!(out[3].color, adjusted_bg1); // "世"
|
||||
assert_eq!(out[4].color, text_color); // "界"
|
||||
assert_eq!(out[5].color, text_color); // "!"
|
||||
}
|
||||
|
||||
// Case D: split multiple consecutive text runs with segments
|
||||
{
|
||||
let segs = vec![
|
||||
(dx(2, 4), bg_1), // selecting "cd"
|
||||
(dx(4, 8), bg_2), // selecting "efgh"
|
||||
(dx(9, 11), bg_1), // selecting "jk"
|
||||
(dx(12, 16), bg_2), // selecting "mnop"
|
||||
(dx(18, 19), bg_1), // selecting "s"
|
||||
];
|
||||
|
||||
// for text: "abcdef"
|
||||
let runs = vec![
|
||||
generate_test_run(2, text_color), // ab
|
||||
generate_test_run(4, text_color), // cdef
|
||||
];
|
||||
let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 0);
|
||||
// new splits "ab", "cd", "ef"
|
||||
assert_eq!(out.iter().map(|r| r.len).collect::<Vec<_>>(), vec![2, 2, 2]);
|
||||
assert_eq!(out[0].color, text_color);
|
||||
assert_eq!(out[1].color, adjusted_bg1);
|
||||
assert_eq!(out[2].color, adjusted_bg2);
|
||||
|
||||
// for text: "ghijklmn"
|
||||
let runs = vec![
|
||||
generate_test_run(3, text_color), // ghi
|
||||
generate_test_run(2, text_color), // jk
|
||||
generate_test_run(3, text_color), // lmn
|
||||
];
|
||||
let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 6); // 2 + 4 from first run
|
||||
// new splits "gh", "i", "jk", "l", "mn"
|
||||
assert_eq!(
|
||||
out.iter().map(|r| r.len).collect::<Vec<_>>(),
|
||||
vec![2, 1, 2, 1, 2]
|
||||
);
|
||||
assert_eq!(out[0].color, adjusted_bg2);
|
||||
assert_eq!(out[1].color, text_color);
|
||||
assert_eq!(out[2].color, adjusted_bg1);
|
||||
assert_eq!(out[3].color, text_color);
|
||||
assert_eq!(out[4].color, adjusted_bg2);
|
||||
|
||||
// for text: "opqrs"
|
||||
let runs = vec![
|
||||
generate_test_run(1, text_color), // o
|
||||
generate_test_run(4, text_color), // pqrs
|
||||
];
|
||||
let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast, 14); // 6 + 3 + 2 + 3 from first two runs
|
||||
// new splits "o", "p", "qr", "s"
|
||||
assert_eq!(
|
||||
out.iter().map(|r| r.len).collect::<Vec<_>>(),
|
||||
vec![1, 1, 2, 1]
|
||||
);
|
||||
assert_eq!(out[0].color, adjusted_bg2);
|
||||
assert_eq!(out[1].color, adjusted_bg2);
|
||||
assert_eq!(out[2].color, text_color);
|
||||
assert_eq!(out[3].color, adjusted_bg1);
|
||||
}
|
||||
// for text: "Hello 🌍 世界!"
|
||||
let runs = vec![
|
||||
generate_test_run(5, text_color), // "Hello"
|
||||
generate_test_run(6, text_color), // " 🌍 "
|
||||
generate_test_run(6, text_color), // "世界"
|
||||
generate_test_run(1, text_color), // "!"
|
||||
];
|
||||
// selecting "🌍 世"
|
||||
let segs = vec![(
|
||||
DisplayPoint::new(DisplayRow(0), 6)..DisplayPoint::new(DisplayRow(0), 14),
|
||||
bg1,
|
||||
)];
|
||||
let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast);
|
||||
// "Hello" | " " | "🌍 " | "世" | "界" | "!"
|
||||
assert_eq!(
|
||||
out.iter().map(|r| r.len).collect::<Vec<_>>(),
|
||||
vec![5, 1, 5, 3, 3, 1]
|
||||
);
|
||||
assert_eq!(out[0].color, text_color); // "Hello"
|
||||
assert_eq!(
|
||||
out[2].color,
|
||||
ensure_minimum_contrast(text_color, bg1, min_contrast)
|
||||
); // "🌍 "
|
||||
assert_eq!(
|
||||
out[3].color,
|
||||
ensure_minimum_contrast(text_color, bg1, min_contrast)
|
||||
); // "世"
|
||||
assert_eq!(out[4].color, text_color); // "界"
|
||||
assert_eq!(out[5].color, text_color); // "!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,27 +38,29 @@ pub struct GitBlameEntrySummary {
|
||||
impl sum_tree::Item for GitBlameEntry {
|
||||
type Summary = GitBlameEntrySummary;
|
||||
|
||||
fn summary(&self, _cx: ()) -> Self::Summary {
|
||||
fn summary(&self, _cx: &()) -> Self::Summary {
|
||||
GitBlameEntrySummary { rows: self.rows }
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::ContextLessSummary for GitBlameEntrySummary {
|
||||
fn zero() -> Self {
|
||||
impl sum_tree::Summary for GitBlameEntrySummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &Self) {
|
||||
fn add_summary(&mut self, summary: &Self, _cx: &()) {
|
||||
self.rows += summary.rows;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, GitBlameEntrySummary> for u32 {
|
||||
fn zero(_cx: ()) -> Self {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a GitBlameEntrySummary, _cx: ()) {
|
||||
fn add_summary(&mut self, summary: &'a GitBlameEntrySummary, _cx: &()) {
|
||||
*self += summary.rows;
|
||||
}
|
||||
}
|
||||
@@ -296,7 +298,7 @@ impl GitBlame {
|
||||
self.sync(cx, buffer_id);
|
||||
|
||||
let buffer_row = info.buffer_row?;
|
||||
let mut cursor = self.buffers.get(&buffer_id)?.entries.cursor::<u32>(());
|
||||
let mut cursor = self.buffers.get(&buffer_id)?.entries.cursor::<u32>(&());
|
||||
cursor.seek_forward(&buffer_row, Bias::Right);
|
||||
Some((buffer_id, cursor.item()?.blame.clone()?))
|
||||
})
|
||||
@@ -404,7 +406,7 @@ impl GitBlame {
|
||||
.peekable();
|
||||
|
||||
let mut new_entries = SumTree::default();
|
||||
let mut cursor = blame_buffer.entries.cursor::<u32>(());
|
||||
let mut cursor = blame_buffer.entries.cursor::<u32>(&());
|
||||
|
||||
while let Some(mut edit) = row_edits.next() {
|
||||
while let Some(next_edit) = row_edits.peek() {
|
||||
@@ -417,7 +419,7 @@ impl GitBlame {
|
||||
}
|
||||
}
|
||||
|
||||
new_entries.append(cursor.slice(&edit.old.start, Bias::Right), ());
|
||||
new_entries.append(cursor.slice(&edit.old.start, Bias::Right), &());
|
||||
|
||||
if edit.new.start > new_entries.summary().rows {
|
||||
new_entries.push(
|
||||
@@ -425,7 +427,7 @@ impl GitBlame {
|
||||
rows: edit.new.start - new_entries.summary().rows,
|
||||
blame: cursor.item().and_then(|entry| entry.blame.clone()),
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -436,7 +438,7 @@ impl GitBlame {
|
||||
rows: edit.new.len() as u32,
|
||||
blame: None,
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -452,14 +454,14 @@ impl GitBlame {
|
||||
rows: cursor.end() - edit.old.end,
|
||||
blame: entry.blame.clone(),
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
}
|
||||
|
||||
cursor.next();
|
||||
}
|
||||
}
|
||||
new_entries.append(cursor.suffix(), ());
|
||||
new_entries.append(cursor.suffix(), &());
|
||||
drop(cursor);
|
||||
|
||||
blame_buffer.buffer_snapshot = new_snapshot;
|
||||
@@ -630,7 +632,7 @@ fn build_blame_entry_sum_tree(entries: Vec<BlameEntry>, max_row: u32) -> SumTree
|
||||
current_row = entry.range.end;
|
||||
entries
|
||||
}),
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
|
||||
if max_row >= current_row {
|
||||
@@ -639,7 +641,7 @@ fn build_blame_entry_sum_tree(entries: Vec<BlameEntry>, max_row: u32) -> SumTree
|
||||
rows: (max_row + 1) - current_row,
|
||||
blame: None,
|
||||
},
|
||||
(),
|
||||
&(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,7 @@ pub fn refresh_matching_bracket_highlights(
|
||||
if (editor.cursor_shape == CursorShape::Block || editor.cursor_shape == CursorShape::Hollow)
|
||||
&& head < snapshot.buffer_snapshot.len()
|
||||
{
|
||||
if let Some(tail_ch) = snapshot.buffer_snapshot.chars_at(tail).next() {
|
||||
tail += tail_ch.len_utf8();
|
||||
}
|
||||
tail += 1;
|
||||
}
|
||||
|
||||
if let Some((opening_range, closing_range)) = snapshot
|
||||
|
||||
@@ -627,7 +627,7 @@ pub fn show_link_definition(
|
||||
TriggerPoint::Text(trigger_anchor) => {
|
||||
// If no symbol range returned from language server, use the surrounding word.
|
||||
let (offset_range, _) =
|
||||
snapshot.surrounding_word(*trigger_anchor, None);
|
||||
snapshot.surrounding_word(*trigger_anchor, false);
|
||||
RangeInEditor::Text(
|
||||
snapshot.anchor_before(offset_range.start)
|
||||
..snapshot.anchor_after(offset_range.end),
|
||||
@@ -898,7 +898,6 @@ fn surrounding_filename(
|
||||
} else {
|
||||
// Otherwise, we skip the quote
|
||||
inside_quotes = true;
|
||||
token_end += ch.len_utf8();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1546,10 +1545,6 @@ mod tests {
|
||||
("'fˇile.txt'", Some("file.txt")),
|
||||
("ˇ'file.txt'", Some("file.txt")),
|
||||
("ˇ'fi\\ le.txt'", Some("fi le.txt")),
|
||||
// Quoted multibyte characters
|
||||
(" ˇ\"常\"", Some("常")),
|
||||
(" \"ˇ常\"", Some("常")),
|
||||
("ˇ\"常\"", Some("常")),
|
||||
];
|
||||
|
||||
for (input, expected) in test_cases {
|
||||
|
||||
@@ -17,8 +17,8 @@ use gpui::{
|
||||
ParentElement, Pixels, SharedString, Styled, Task, WeakEntity, Window, point,
|
||||
};
|
||||
use language::{
|
||||
Bias, Buffer, BufferRow, CharKind, CharScopeContext, DiskState, LocalFile, Point,
|
||||
SelectionGoal, proto::serialize_anchor as serialize_text_anchor,
|
||||
Bias, Buffer, BufferRow, CharKind, DiskState, LocalFile, Point, SelectionGoal,
|
||||
proto::serialize_anchor as serialize_text_anchor,
|
||||
};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use project::{
|
||||
@@ -44,9 +44,7 @@ use workspace::{
|
||||
CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
|
||||
invalid_buffer_view::InvalidBufferView,
|
||||
item::{FollowableItem, Item, ItemEvent, ProjectItem, SaveOptions},
|
||||
searchable::{
|
||||
Direction, FilteredSearchRange, SearchEvent, SearchableItem, SearchableItemHandle,
|
||||
},
|
||||
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
||||
};
|
||||
use workspace::{
|
||||
OpenOptions,
|
||||
@@ -1512,7 +1510,7 @@ impl SearchableItem for Editor {
|
||||
|
||||
fn toggle_filtered_search_ranges(
|
||||
&mut self,
|
||||
enabled: Option<FilteredSearchRange>,
|
||||
enabled: bool,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -1522,16 +1520,15 @@ impl SearchableItem for Editor {
|
||||
.map(|(_, ranges)| ranges)
|
||||
}
|
||||
|
||||
if let Some(range) = enabled {
|
||||
let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
|
||||
if !enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
if ranges.iter().any(|s| s.start != s.end) {
|
||||
self.set_search_within_ranges(&ranges, cx);
|
||||
} else if let Some(previous_search_ranges) = self.previous_search_ranges.take()
|
||||
&& range != FilteredSearchRange::Selection
|
||||
{
|
||||
self.set_search_within_ranges(&previous_search_ranges, cx);
|
||||
}
|
||||
let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
|
||||
if ranges.iter().any(|s| s.start != s.end) {
|
||||
self.set_search_within_ranges(&ranges, cx);
|
||||
} else if let Some(previous_search_ranges) = self.previous_search_ranges.take() {
|
||||
self.set_search_within_ranges(&previous_search_ranges, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1576,8 +1573,7 @@ impl SearchableItem for Editor {
|
||||
}
|
||||
SeedQuerySetting::Selection => String::new(),
|
||||
SeedQuerySetting::Always => {
|
||||
let (range, kind) =
|
||||
snapshot.surrounding_word(selection.start, Some(CharScopeContext::Completion));
|
||||
let (range, kind) = snapshot.surrounding_word(selection.start, true);
|
||||
if kind == Some(CharKind::Word) {
|
||||
let text: String = snapshot.text_for_range(range).collect();
|
||||
if !text.trim().is_empty() {
|
||||
|
||||
@@ -228,70 +228,60 @@ impl Editor {
|
||||
};
|
||||
match colors {
|
||||
Ok(colors) => {
|
||||
if colors.colors.is_empty() {
|
||||
let new_entry =
|
||||
new_editor_colors.entry(buffer_id).or_insert_with(|| {
|
||||
(Vec::<(Range<Anchor>, DocumentColor)>::new(), None)
|
||||
});
|
||||
new_entry.0.clear();
|
||||
new_entry.1 = colors.cache_version;
|
||||
} else {
|
||||
for color in colors.colors {
|
||||
let color_start = point_from_lsp(color.lsp_range.start);
|
||||
let color_end = point_from_lsp(color.lsp_range.end);
|
||||
for color in colors.colors {
|
||||
let color_start = point_from_lsp(color.lsp_range.start);
|
||||
let color_end = point_from_lsp(color.lsp_range.end);
|
||||
|
||||
for (excerpt_id, buffer_snapshot, excerpt_range) in excerpts {
|
||||
if !excerpt_range.contains(&color_start.0)
|
||||
|| !excerpt_range.contains(&color_end.0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(color_start_anchor) = multi_buffer_snapshot
|
||||
.anchor_in_excerpt(
|
||||
*excerpt_id,
|
||||
buffer_snapshot.anchor_before(
|
||||
buffer_snapshot
|
||||
.clip_point_utf16(color_start, Bias::Left),
|
||||
),
|
||||
)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(color_end_anchor) = multi_buffer_snapshot
|
||||
.anchor_in_excerpt(
|
||||
*excerpt_id,
|
||||
buffer_snapshot.anchor_after(
|
||||
buffer_snapshot
|
||||
.clip_point_utf16(color_end, Bias::Right),
|
||||
),
|
||||
)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let new_entry =
|
||||
new_editor_colors.entry(buffer_id).or_insert_with(|| {
|
||||
(Vec::<(Range<Anchor>, DocumentColor)>::new(), None)
|
||||
});
|
||||
new_entry.1 = colors.cache_version;
|
||||
let new_buffer_colors = &mut new_entry.0;
|
||||
|
||||
let (Ok(i) | Err(i)) =
|
||||
new_buffer_colors.binary_search_by(|(probe, _)| {
|
||||
probe
|
||||
.start
|
||||
.cmp(&color_start_anchor, &multi_buffer_snapshot)
|
||||
.then_with(|| {
|
||||
probe.end.cmp(
|
||||
&color_end_anchor,
|
||||
&multi_buffer_snapshot,
|
||||
)
|
||||
})
|
||||
});
|
||||
new_buffer_colors
|
||||
.insert(i, (color_start_anchor..color_end_anchor, color));
|
||||
break;
|
||||
for (excerpt_id, buffer_snapshot, excerpt_range) in excerpts {
|
||||
if !excerpt_range.contains(&color_start.0)
|
||||
|| !excerpt_range.contains(&color_end.0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(color_start_anchor) = multi_buffer_snapshot
|
||||
.anchor_in_excerpt(
|
||||
*excerpt_id,
|
||||
buffer_snapshot.anchor_before(
|
||||
buffer_snapshot
|
||||
.clip_point_utf16(color_start, Bias::Left),
|
||||
),
|
||||
)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(color_end_anchor) = multi_buffer_snapshot
|
||||
.anchor_in_excerpt(
|
||||
*excerpt_id,
|
||||
buffer_snapshot.anchor_after(
|
||||
buffer_snapshot
|
||||
.clip_point_utf16(color_end, Bias::Right),
|
||||
),
|
||||
)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let new_entry =
|
||||
new_editor_colors.entry(buffer_id).or_insert_with(|| {
|
||||
(Vec::<(Range<Anchor>, DocumentColor)>::new(), None)
|
||||
});
|
||||
new_entry.1 = colors.cache_version;
|
||||
let new_buffer_colors = &mut new_entry.0;
|
||||
|
||||
let (Ok(i) | Err(i)) =
|
||||
new_buffer_colors.binary_search_by(|(probe, _)| {
|
||||
probe
|
||||
.start
|
||||
.cmp(&color_start_anchor, &multi_buffer_snapshot)
|
||||
.then_with(|| {
|
||||
probe
|
||||
.end
|
||||
.cmp(&color_end_anchor, &multi_buffer_snapshot)
|
||||
})
|
||||
});
|
||||
new_buffer_colors
|
||||
.insert(i, (color_start_anchor..color_end_anchor, color));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,10 +33,26 @@ impl ExtensionSettings {
|
||||
}
|
||||
|
||||
impl Settings for ExtensionSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
Self {
|
||||
auto_install_extensions: content.extension.auto_install_extensions.clone(),
|
||||
auto_update_extensions: content.extension.auto_update_extensions.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
|
||||
self.auto_install_extensions
|
||||
.extend(content.extension.auto_install_extensions.clone());
|
||||
self.auto_update_extensions
|
||||
.extend(content.extension.auto_update_extensions.clone());
|
||||
}
|
||||
|
||||
fn import_from_vscode(
|
||||
_vscode: &settings::VsCodeSettings,
|
||||
_current: &mut settings::SettingsContent,
|
||||
) {
|
||||
// settingsSync.ignoredExtensions controls autoupdate for vscode extensions, but we
|
||||
// don't have a mapping to zed-extensions. there's also extensions.autoCheckUpdates
|
||||
// and extensions.autoUpdate which are global switches, we don't support those yet
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ wasmtime::component::bindgen!({
|
||||
});
|
||||
|
||||
mod settings {
|
||||
#![allow(dead_code)]
|
||||
include!(concat!(env!("OUT_DIR"), "/since_v0.0.6/settings.rs"));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ wasmtime::component::bindgen!({
|
||||
pub use self::zed::extension::*;
|
||||
|
||||
mod settings {
|
||||
#![allow(dead_code)]
|
||||
include!(concat!(env!("OUT_DIR"), "/since_v0.2.0/settings.rs"));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ wasmtime::component::bindgen!({
|
||||
});
|
||||
|
||||
mod settings {
|
||||
#![allow(dead_code)]
|
||||
include!(concat!(env!("OUT_DIR"), "/since_v0.3.0/settings.rs"));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ wasmtime::component::bindgen!({
|
||||
});
|
||||
|
||||
mod settings {
|
||||
#![allow(dead_code)]
|
||||
include!(concat!(env!("OUT_DIR"), "/since_v0.4.0/settings.rs"));
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ wasmtime::component::bindgen!({
|
||||
});
|
||||
|
||||
mod settings {
|
||||
#![allow(dead_code)]
|
||||
include!(concat!(env!("OUT_DIR"), "/since_v0.5.0/settings.rs"));
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ use util::{archive::extract_zip, fs::make_file_executable, maybe};
|
||||
use wasmtime::component::{Linker, Resource};
|
||||
|
||||
pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0);
|
||||
pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 7, 0);
|
||||
pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0);
|
||||
|
||||
wasmtime::component::bindgen!({
|
||||
async: true,
|
||||
@@ -52,7 +52,6 @@ wasmtime::component::bindgen!({
|
||||
pub use self::zed::extension::*;
|
||||
|
||||
mod settings {
|
||||
#![allow(dead_code)]
|
||||
include!(concat!(env!("OUT_DIR"), "/since_v0.6.0/settings.rs"));
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user