diff --git a/Cargo.lock b/Cargo.lock
index 772fb2492b..86ab65b23a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -211,6 +211,7 @@ dependencies = [
"chrono",
"client",
"collections",
+ "command_palette_hooks",
"component",
"context_server",
"db",
@@ -232,6 +233,7 @@ dependencies = [
"jsonschema",
"language",
"language_model",
+ "language_models",
"languages",
"log",
"lsp",
@@ -270,6 +272,7 @@ dependencies = [
"time_format",
"tree-sitter-md",
"ui",
+ "ui_input",
"unindent",
"urlencoding",
"util",
@@ -1870,9 +1873,7 @@ version = "0.1.0"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
- "futures 0.3.31",
"http_client",
- "tokio",
"workspace-hack",
]
@@ -6359,6 +6360,7 @@ dependencies = [
"buffer_diff",
"call",
"chrono",
+ "client",
"collections",
"command_palette_hooks",
"component",
@@ -7400,9 +7402,9 @@ dependencies = [
[[package]]
name = "grid"
-version = "0.14.0"
+version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be136d9dacc2a13cc70bb6c8f902b414fb2641f8db1314637c6b7933411a8f82"
+checksum = "71b01d27060ad58be4663b9e4ac9e2d4806918e8876af8912afbddd1a91d5eaa"
[[package]]
name = "group"
@@ -7854,6 +7856,7 @@ dependencies = [
"derive_more 0.99.19",
"futures 0.3.31",
"http 1.3.1",
+ "http-body 1.0.1",
"log",
"serde",
"serde_json",
@@ -9098,11 +9101,11 @@ dependencies = [
"client",
"collections",
"component",
+ "convert_case 0.8.0",
"copilot",
"credentials_provider",
"deepseek",
"editor",
- "fs",
"futures 0.3.31",
"google_ai",
"gpui",
@@ -15956,13 +15959,12 @@ dependencies = [
[[package]]
name = "taffy"
-version = "0.5.1"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8b61630cba2afd2c851821add2e1bb1b7851a2436e839ab73b56558b009035e"
+checksum = "7aaef0ac998e6527d6d0d5582f7e43953bb17221ac75bb8eb2fcc2db3396db1c"
dependencies = [
"arrayvec",
"grid",
- "num-traits",
"serde",
"slotmap",
]
diff --git a/Cargo.toml b/Cargo.toml
index ea8690f2b3..ec793a7429 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -482,6 +482,7 @@ heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
hex = "0.4.3"
html5ever = "0.27.0"
http = "1.1"
+http-body = "1.0"
hyper = "0.14"
ignore = "0.4.22"
image = "0.25.1"
diff --git a/assets/icons/ai_open_ai_compat.svg b/assets/icons/ai_open_ai_compat.svg
new file mode 100644
index 0000000000..f6557caac3
--- /dev/null
+++ b/assets/icons/ai_open_ai_compat.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/keymaps/initial.json b/assets/keymaps/initial.json
index 0cfd28f0e5..ff6069a816 100644
--- a/assets/keymaps/initial.json
+++ b/assets/keymaps/initial.json
@@ -15,7 +15,7 @@
{
"context": "Editor && vim_mode == insert && !menu",
"bindings": {
- // "j k": "vim::SwitchToNormalMode"
+ // "j k": "vim::NormalBefore"
}
}
]
diff --git a/assets/keymaps/macos/textmate.json b/assets/keymaps/macos/textmate.json
index dccb675f6c..0bd8873b17 100644
--- a/assets/keymaps/macos/textmate.json
+++ b/assets/keymaps/macos/textmate.json
@@ -6,7 +6,7 @@
}
},
{
- "context": "Editor",
+ "context": "Editor && mode == full",
"bindings": {
"cmd-l": "go_to_line::Toggle",
"ctrl-shift-d": "editor::DuplicateLineDown",
@@ -15,7 +15,12 @@
"cmd-enter": "editor::NewlineBelow",
"cmd-alt-enter": "editor::NewlineAbove",
"cmd-shift-l": "editor::SelectLine",
- "cmd-shift-t": "outline::Toggle",
+ "cmd-shift-t": "outline::Toggle"
+ }
+ },
+ {
+ "context": "Editor",
+ "bindings": {
"alt-backspace": "editor::DeleteToPreviousWordStart",
"alt-shift-backspace": "editor::DeleteToNextWordEnd",
"alt-delete": "editor::DeleteToNextWordEnd",
@@ -39,10 +44,6 @@
"ctrl-_": "editor::ConvertToSnakeCase"
}
},
- {
- "context": "Editor && mode == full",
- "bindings": {}
- },
{
"context": "BufferSearchBar",
"bindings": {
diff --git a/assets/settings/default.json b/assets/settings/default.json
index 358871650b..dab1684aef 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -1076,6 +1076,10 @@
// Send anonymized usage data like what languages you're using Zed with.
"metrics": true
},
+ // Whether to disable all AI features in Zed.
+ //
+ // Default: false
+ "disable_ai": false,
// Automatically update Zed. This setting may be ignored on Linux if
// installed through a package manager.
"auto_update": true,
@@ -1712,6 +1716,7 @@
"openai": {
"api_url": "https://api.openai.com/v1"
},
+ "openai_compatible": {},
"open_router": {
"api_url": "https://openrouter.ai/api/v1"
},
diff --git a/assets/settings/initial_debug_tasks.json b/assets/settings/initial_debug_tasks.json
index 78fc1fc5f0..af4512bd51 100644
--- a/assets/settings/initial_debug_tasks.json
+++ b/assets/settings/initial_debug_tasks.json
@@ -15,13 +15,15 @@
"adapter": "JavaScript",
"program": "$ZED_FILE",
"request": "launch",
- "cwd": "$ZED_WORKTREE_ROOT"
+ "cwd": "$ZED_WORKTREE_ROOT",
+ "type": "pwa-node"
},
{
"label": "JavaScript debug terminal",
"adapter": "JavaScript",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT",
- "console": "integratedTerminal"
+ "console": "integratedTerminal",
+ "type": "pwa-node"
}
]
diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs
index e50763535a..1b3b022ab2 100644
--- a/crates/agent/src/thread.rs
+++ b/crates/agent/src/thread.rs
@@ -47,7 +47,7 @@ use std::{
time::{Duration, Instant},
};
use thiserror::Error;
-use util::{ResultExt as _, debug_panic, post_inc};
+use util::{ResultExt as _, post_inc};
use uuid::Uuid;
use zed_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
@@ -1582,20 +1582,18 @@ impl Thread {
model: Arc,
cx: &mut App,
) -> Option {
- let action_log = self.action_log.read(cx);
+ // Represent notification as a simulated `project_notifications` tool call
+ let tool_name = Arc::from("project_notifications");
+ let tool = self.tools.read(cx).tool(&tool_name, cx)?;
- if !action_log.has_unnotified_user_edits() {
+ if !self.profile.is_tool_enabled(tool.source(), tool.name(), cx) {
return None;
}
- // Represent notification as a simulated `project_notifications` tool call
- let tool_name = Arc::from("project_notifications");
- let Some(tool) = self.tools.read(cx).tool(&tool_name, cx) else {
- debug_panic!("`project_notifications` tool not found");
- return None;
- };
-
- if !self.profile.is_tool_enabled(tool.source(), tool.name(), cx) {
+ if self
+ .action_log
+ .update(cx, |log, cx| log.unnotified_user_edits(cx).is_none())
+ {
return None;
}
@@ -5492,7 +5490,7 @@ fn main() {{
let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
let context_store = cx.new(|_cx| ContextStore::new(project.downgrade(), None));
- let provider = Arc::new(FakeLanguageModelProvider);
+ let provider = Arc::new(FakeLanguageModelProvider::default());
let model = provider.test_model();
let model: Arc = Arc::new(model);
diff --git a/crates/agent_servers/src/claude/tools.rs b/crates/agent_servers/src/claude/tools.rs
index 9c82139a07..75a26ee230 100644
--- a/crates/agent_servers/src/claude/tools.rs
+++ b/crates/agent_servers/src/claude/tools.rs
@@ -434,10 +434,6 @@ pub struct EditToolParams {
pub new_text: String,
}
-#[derive(Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct EditToolResponse;
-
#[derive(Deserialize, JsonSchema, Debug)]
pub struct ReadToolParams {
/// The absolute path to the file to read.
@@ -450,12 +446,6 @@ pub struct ReadToolParams {
pub limit: Option,
}
-#[derive(Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct ReadToolResponse {
- pub content: String,
-}
-
#[derive(Deserialize, JsonSchema, Debug)]
pub struct WriteToolParams {
/// Absolute path for new file
diff --git a/crates/agent_servers/src/mcp_server.rs b/crates/agent_servers/src/mcp_server.rs
index 3e8d804afa..3cc3d8828c 100644
--- a/crates/agent_servers/src/mcp_server.rs
+++ b/crates/agent_servers/src/mcp_server.rs
@@ -14,11 +14,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use util::debug_panic;
-// todo! use shared tool inference?
-use crate::{
- claude::tools::{ClaudeTool, EditToolParams, ReadToolParams},
- tools::{EditToolResponse, ReadToolResponse},
-};
+use crate::claude::tools::{ClaudeTool, EditToolParams, ReadToolParams};
pub struct ZedMcpServer {
server: context_server::listener::McpServer,
@@ -196,11 +192,9 @@ impl ZedMcpServer {
let input =
serde_json::from_value(request.arguments.context("Arguments required")?)?;
- let result = Self::handle_read_tool_call(input, delegate, cx).await?;
+ let content = Self::handle_read_tool_call(input, delegate, cx).await?;
Ok(CallToolResponse {
- content: vec![ToolResponseContent::Text {
- text: serde_json::to_string(&result)?,
- }],
+ content,
is_error: None,
meta: None,
})
@@ -208,11 +202,9 @@ impl ZedMcpServer {
let input =
serde_json::from_value(request.arguments.context("Arguments required")?)?;
- let result = Self::handle_edit_tool_call(input, delegate, cx).await?;
+ Self::handle_edit_tool_call(input, delegate, cx).await?;
Ok(CallToolResponse {
- content: vec![ToolResponseContent::Text {
- text: serde_json::to_string(&result)?,
- }],
+ content: vec![],
is_error: None,
meta: None,
})
@@ -226,7 +218,7 @@ impl ZedMcpServer {
params: ReadToolParams,
delegate: AcpClientDelegate,
cx: &AsyncApp,
- ) -> Task> {
+ ) -> Task>> {
cx.foreground_executor().spawn(async move {
let response = delegate
.read_text_file(ReadTextFileParams {
@@ -236,9 +228,9 @@ impl ZedMcpServer {
})
.await?;
- Ok(ReadToolResponse {
- content: response.content,
- })
+ Ok(vec![ToolResponseContent::Text {
+ text: response.content,
+ }])
})
}
@@ -246,7 +238,7 @@ impl ZedMcpServer {
params: EditToolParams,
delegate: AcpClientDelegate,
cx: &AsyncApp,
- ) -> Task> {
+ ) -> Task> {
cx.foreground_executor().spawn(async move {
let response = delegate
.read_text_file_reusing_snapshot(ReadTextFileParams {
@@ -268,7 +260,7 @@ impl ZedMcpServer {
})
.await?;
- Ok(EditToolResponse)
+ Ok(())
})
}
diff --git a/crates/agent_ui/Cargo.toml b/crates/agent_ui/Cargo.toml
index e55ae86fb7..7d3b84e42e 100644
--- a/crates/agent_ui/Cargo.toml
+++ b/crates/agent_ui/Cargo.toml
@@ -32,6 +32,7 @@ buffer_diff.workspace = true
chrono.workspace = true
client.workspace = true
collections.workspace = true
+command_palette_hooks.workspace = true
component.workspace = true
context_server.workspace = true
db.workspace = true
@@ -53,6 +54,7 @@ itertools.workspace = true
jsonschema.workspace = true
language.workspace = true
language_model.workspace = true
+language_models.workspace = true
log.workspace = true
lsp.workspace = true
markdown.workspace = true
@@ -87,6 +89,7 @@ theme.workspace = true
time.workspace = true
time_format.workspace = true
ui.workspace = true
+ui_input.workspace = true
urlencoding.workspace = true
util.workspace = true
uuid.workspace = true
diff --git a/crates/agent_ui/src/active_thread.rs b/crates/agent_ui/src/active_thread.rs
index bfed81f5b7..e27c318221 100644
--- a/crates/agent_ui/src/active_thread.rs
+++ b/crates/agent_ui/src/active_thread.rs
@@ -3895,7 +3895,7 @@ mod tests {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.set_default_model(
Some(ConfiguredModel {
- provider: Arc::new(FakeLanguageModelProvider),
+ provider: Arc::new(FakeLanguageModelProvider::default()),
model,
}),
cx,
@@ -3979,7 +3979,7 @@ mod tests {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.set_default_model(
Some(ConfiguredModel {
- provider: Arc::new(FakeLanguageModelProvider),
+ provider: Arc::new(FakeLanguageModelProvider::default()),
model: model.clone(),
}),
cx,
diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs
index 0697f5dee7..334c5ee6dc 100644
--- a/crates/agent_ui/src/agent_configuration.rs
+++ b/crates/agent_ui/src/agent_configuration.rs
@@ -1,3 +1,4 @@
+mod add_llm_provider_modal;
mod configure_context_server_modal;
mod manage_profiles_modal;
mod tool_picker;
@@ -28,7 +29,7 @@ use proto::Plan;
use settings::{Settings, update_settings_file};
use ui::{
Chip, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu,
- Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip, prelude::*,
+ Scrollbar, ScrollbarState, Switch, SwitchColor, SwitchField, Tooltip, prelude::*,
};
use util::ResultExt as _;
use workspace::Workspace;
@@ -37,7 +38,10 @@ use zed_actions::ExtensionCategoryFilter;
pub(crate) use configure_context_server_modal::ConfigureContextServerModal;
pub(crate) use manage_profiles_modal::ManageProfilesModal;
-use crate::AddContextServer;
+use crate::{
+ AddContextServer,
+ agent_configuration::add_llm_provider_modal::{AddLlmProviderModal, LlmCompatibleProvider},
+};
pub struct AgentConfiguration {
fs: Arc,
@@ -304,16 +308,55 @@ impl AgentConfiguration {
v_flex()
.child(
- v_flex()
+ h_flex()
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.pb_0()
.mb_2p5()
- .gap_0p5()
- .child(Headline::new("LLM Providers"))
+ .items_start()
+ .justify_between()
.child(
- Label::new("Add at least one provider to use AI-powered features.")
- .color(Color::Muted),
+ v_flex()
+ .gap_0p5()
+ .child(Headline::new("LLM Providers"))
+ .child(
+ Label::new("Add at least one provider to use AI-powered features.")
+ .color(Color::Muted),
+ ),
+ )
+ .child(
+ PopoverMenu::new("add-provider-popover")
+ .trigger(
+ Button::new("add-provider", "Add Provider")
+ .icon_position(IconPosition::Start)
+ .icon(IconName::Plus)
+ .icon_size(IconSize::Small)
+ .icon_color(Color::Muted)
+ .label_size(LabelSize::Small),
+ )
+ .anchor(gpui::Corner::TopRight)
+ .menu({
+ let workspace = self.workspace.clone();
+ move |window, cx| {
+ Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
+ menu.header("Compatible APIs").entry("OpenAI", None, {
+ let workspace = workspace.clone();
+ move |window, cx| {
+ workspace
+ .update(cx, |workspace, cx| {
+ AddLlmProviderModal::toggle(
+ LlmCompatibleProvider::OpenAi,
+ workspace,
+ window,
+ cx,
+ );
+ })
+ .log_err();
+ }
+ })
+ }))
+ }
+ }),
),
)
.child(
@@ -330,119 +373,74 @@ impl AgentConfiguration {
fn render_command_permission(&mut self, cx: &mut Context) -> impl IntoElement {
let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions;
+ let fs = self.fs.clone();
- h_flex()
- .gap_4()
- .justify_between()
- .flex_wrap()
- .child(
- v_flex()
- .gap_0p5()
- .max_w_5_6()
- .child(Label::new("Allow running editing tools without asking for confirmation"))
- .child(
- Label::new(
- "The agent can perform potentially destructive actions without asking for your confirmation.",
- )
- .color(Color::Muted),
- ),
- )
- .child(
- Switch::new(
- "always-allow-tool-actions-switch",
- always_allow_tool_actions.into(),
- )
- .color(SwitchColor::Accent)
- .on_click({
- let fs = self.fs.clone();
- move |state, _window, cx| {
- let allow = state == &ToggleState::Selected;
- update_settings_file::(
- fs.clone(),
- cx,
- move |settings, _| {
- settings.set_always_allow_tool_actions(allow);
- },
- );
- }
- }),
- )
+ SwitchField::new(
+ "single-file-review",
+ "Enable single-file agent reviews",
+ "Agent edits are also displayed in single-file editors for review.",
+ always_allow_tool_actions,
+ move |state, _window, cx| {
+ let allow = state == &ToggleState::Selected;
+ update_settings_file::(fs.clone(), cx, move |settings, _| {
+ settings.set_always_allow_tool_actions(allow);
+ });
+ },
+ )
}
fn render_single_file_review(&mut self, cx: &mut Context) -> impl IntoElement {
let single_file_review = AgentSettings::get_global(cx).single_file_review;
+ let fs = self.fs.clone();
- h_flex()
- .gap_4()
- .justify_between()
- .flex_wrap()
- .child(
- v_flex()
- .gap_0p5()
- .max_w_5_6()
- .child(Label::new("Enable single-file agent reviews"))
- .child(
- Label::new(
- "Agent edits are also displayed in single-file editors for review.",
- )
- .color(Color::Muted),
- ),
- )
- .child(
- Switch::new("single-file-review-switch", single_file_review.into())
- .color(SwitchColor::Accent)
- .on_click({
- let fs = self.fs.clone();
- move |state, _window, cx| {
- let allow = state == &ToggleState::Selected;
- update_settings_file::(
- fs.clone(),
- cx,
- move |settings, _| {
- settings.set_single_file_review(allow);
- },
- );
- }
- }),
- )
+ SwitchField::new(
+ "single-file-review",
+ "Enable single-file agent reviews",
+ "Agent edits are also displayed in single-file editors for review.",
+ single_file_review,
+ move |state, _window, cx| {
+ let allow = state == &ToggleState::Selected;
+ update_settings_file::(fs.clone(), cx, move |settings, _| {
+ settings.set_single_file_review(allow);
+ });
+ },
+ )
}
fn render_sound_notification(&mut self, cx: &mut Context) -> impl IntoElement {
let play_sound_when_agent_done = AgentSettings::get_global(cx).play_sound_when_agent_done;
+ let fs = self.fs.clone();
- h_flex()
- .gap_4()
- .justify_between()
- .flex_wrap()
- .child(
- v_flex()
- .gap_0p5()
- .max_w_5_6()
- .child(Label::new("Play sound when finished generating"))
- .child(
- Label::new(
- "Hear a notification sound when the agent is done generating changes or needs your input.",
- )
- .color(Color::Muted),
- ),
- )
- .child(
- Switch::new("play-sound-notification-switch", play_sound_when_agent_done.into())
- .color(SwitchColor::Accent)
- .on_click({
- let fs = self.fs.clone();
- move |state, _window, cx| {
- let allow = state == &ToggleState::Selected;
- update_settings_file::(
- fs.clone(),
- cx,
- move |settings, _| {
- settings.set_play_sound_when_agent_done(allow);
- },
- );
- }
- }),
- )
+ SwitchField::new(
+ "sound-notification",
+ "Play sound when finished generating",
+ "Hear a notification sound when the agent is done generating changes or needs your input.",
+ play_sound_when_agent_done,
+ move |state, _window, cx| {
+ let allow = state == &ToggleState::Selected;
+ update_settings_file::(fs.clone(), cx, move |settings, _| {
+ settings.set_play_sound_when_agent_done(allow);
+ });
+ },
+ )
+ }
+
+ fn render_modifier_to_send(&mut self, cx: &mut Context) -> impl IntoElement {
+ let use_modifier_to_send = AgentSettings::get_global(cx).use_modifier_to_send;
+ let fs = self.fs.clone();
+
+ SwitchField::new(
+ "modifier-send",
+ "Use modifier to submit a message",
+ "Make a modifier (cmd-enter on macOS, ctrl-enter on Linux) required to send messages.",
+ use_modifier_to_send,
+ move |state, _window, cx| {
+ let allow = state == &ToggleState::Selected;
+ update_settings_file::(fs.clone(), cx, move |settings, _| {
+ settings.set_use_modifier_to_send(allow);
+ });
+ },
+ )
}
fn render_general_settings_section(&mut self, cx: &mut Context) -> impl IntoElement {
@@ -456,6 +454,7 @@ impl AgentConfiguration {
.child(self.render_command_permission(cx))
.child(self.render_single_file_review(cx))
.child(self.render_sound_notification(cx))
+ .child(self.render_modifier_to_send(cx))
}
fn render_zed_plan_info(&self, plan: Option, cx: &mut Context) -> impl IntoElement {
diff --git a/crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs b/crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs
new file mode 100644
index 0000000000..94b32d156b
--- /dev/null
+++ b/crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs
@@ -0,0 +1,639 @@
+use std::sync::Arc;
+
+use anyhow::Result;
+use collections::HashSet;
+use fs::Fs;
+use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, Task};
+use language_model::LanguageModelRegistry;
+use language_models::{
+ AllLanguageModelSettings, OpenAiCompatibleSettingsContent,
+ provider::open_ai_compatible::AvailableModel,
+};
+use settings::update_settings_file;
+use ui::{Banner, KeyBinding, Modal, ModalFooter, ModalHeader, Section, prelude::*};
+use ui_input::SingleLineInput;
+use workspace::{ModalView, Workspace};
+
+#[derive(Clone, Copy)]
+pub enum LlmCompatibleProvider {
+ OpenAi,
+}
+
+impl LlmCompatibleProvider {
+ fn name(&self) -> &'static str {
+ match self {
+ LlmCompatibleProvider::OpenAi => "OpenAI",
+ }
+ }
+
+ fn api_url(&self) -> &'static str {
+ match self {
+ LlmCompatibleProvider::OpenAi => "https://api.openai.com/v1",
+ }
+ }
+}
+
+struct AddLlmProviderInput {
+ provider_name: Entity,
+ api_url: Entity,
+ api_key: Entity,
+ models: Vec,
+}
+
+impl AddLlmProviderInput {
+ fn new(provider: LlmCompatibleProvider, window: &mut Window, cx: &mut App) -> Self {
+ let provider_name = single_line_input("Provider Name", provider.name(), None, window, cx);
+ let api_url = single_line_input("API URL", provider.api_url(), None, window, cx);
+ let api_key = single_line_input(
+ "API Key",
+ "000000000000000000000000000000000000000000000000",
+ None,
+ window,
+ cx,
+ );
+
+ Self {
+ provider_name,
+ api_url,
+ api_key,
+ models: vec![ModelInput::new(window, cx)],
+ }
+ }
+
+ fn add_model(&mut self, window: &mut Window, cx: &mut App) {
+ self.models.push(ModelInput::new(window, cx));
+ }
+
+ fn remove_model(&mut self, index: usize) {
+ self.models.remove(index);
+ }
+}
+
+struct ModelInput {
+ name: Entity,
+ max_completion_tokens: Entity,
+ max_output_tokens: Entity,
+ max_tokens: Entity,
+}
+
+impl ModelInput {
+ fn new(window: &mut Window, cx: &mut App) -> Self {
+ let model_name = single_line_input(
+ "Model Name",
+ "e.g. gpt-4o, claude-opus-4, gemini-2.5-pro",
+ None,
+ window,
+ cx,
+ );
+ let max_completion_tokens = single_line_input(
+ "Max Completion Tokens",
+ "200000",
+ Some("200000"),
+ window,
+ cx,
+ );
+ let max_output_tokens = single_line_input(
+ "Max Output Tokens",
+ "Max Output Tokens",
+ Some("32000"),
+ window,
+ cx,
+ );
+ let max_tokens = single_line_input("Max Tokens", "Max Tokens", Some("200000"), window, cx);
+ Self {
+ name: model_name,
+ max_completion_tokens,
+ max_output_tokens,
+ max_tokens,
+ }
+ }
+
+ fn parse(&self, cx: &App) -> Result {
+ let name = self.name.read(cx).text(cx);
+ if name.is_empty() {
+ return Err(SharedString::from("Model Name cannot be empty"));
+ }
+ Ok(AvailableModel {
+ name,
+ display_name: None,
+ max_completion_tokens: Some(
+ self.max_completion_tokens
+ .read(cx)
+ .text(cx)
+ .parse::()
+ .map_err(|_| SharedString::from("Max Completion Tokens must be a number"))?,
+ ),
+ max_output_tokens: Some(
+ self.max_output_tokens
+ .read(cx)
+ .text(cx)
+ .parse::()
+ .map_err(|_| SharedString::from("Max Output Tokens must be a number"))?,
+ ),
+ max_tokens: self
+ .max_tokens
+ .read(cx)
+ .text(cx)
+ .parse::()
+ .map_err(|_| SharedString::from("Max Tokens must be a number"))?,
+ })
+ }
+}
+
+fn single_line_input(
+ label: impl Into,
+ placeholder: impl Into,
+ text: Option<&str>,
+ window: &mut Window,
+ cx: &mut App,
+) -> Entity {
+ cx.new(|cx| {
+ let input = SingleLineInput::new(window, cx, placeholder).label(label);
+ if let Some(text) = text {
+ input
+ .editor()
+ .update(cx, |editor, cx| editor.set_text(text, window, cx));
+ }
+ input
+ })
+}
+
+fn save_provider_to_settings(
+ input: &AddLlmProviderInput,
+ cx: &mut App,
+) -> Task> {
+ let provider_name: Arc = input.provider_name.read(cx).text(cx).into();
+ if provider_name.is_empty() {
+ return Task::ready(Err("Provider Name cannot be empty".into()));
+ }
+
+ if LanguageModelRegistry::read_global(cx)
+ .providers()
+ .iter()
+ .any(|provider| {
+ provider.id().0.as_ref() == provider_name.as_ref()
+ || provider.name().0.as_ref() == provider_name.as_ref()
+ })
+ {
+ return Task::ready(Err(
+ "Provider Name is already taken by another provider".into()
+ ));
+ }
+
+ let api_url = input.api_url.read(cx).text(cx);
+ if api_url.is_empty() {
+ return Task::ready(Err("API URL cannot be empty".into()));
+ }
+
+ let api_key = input.api_key.read(cx).text(cx);
+ if api_key.is_empty() {
+ return Task::ready(Err("API Key cannot be empty".into()));
+ }
+
+ let mut models = Vec::new();
+ let mut model_names: HashSet = HashSet::default();
+ for model in &input.models {
+ match model.parse(cx) {
+ Ok(model) => {
+ if !model_names.insert(model.name.clone()) {
+ return Task::ready(Err("Model Names must be unique".into()));
+ }
+ models.push(model)
+ }
+ Err(err) => return Task::ready(Err(err)),
+ }
+ }
+
+ let fs = ::global(cx);
+ let task = cx.write_credentials(&api_url, "Bearer", api_key.as_bytes());
+ cx.spawn(async move |cx| {
+ task.await
+ .map_err(|_| "Failed to write API key to keychain")?;
+ cx.update(|cx| {
+ update_settings_file::(fs, cx, |settings, _cx| {
+ settings.openai_compatible.get_or_insert_default().insert(
+ provider_name,
+ OpenAiCompatibleSettingsContent {
+ api_url,
+ available_models: models,
+ },
+ );
+ });
+ })
+ .ok();
+ Ok(())
+ })
+}
+
+pub struct AddLlmProviderModal {
+ provider: LlmCompatibleProvider,
+ input: AddLlmProviderInput,
+ focus_handle: FocusHandle,
+ last_error: Option,
+}
+
+impl AddLlmProviderModal {
+ pub fn toggle(
+ provider: LlmCompatibleProvider,
+ workspace: &mut Workspace,
+ window: &mut Window,
+ cx: &mut Context,
+ ) {
+ workspace.toggle_modal(window, cx, |window, cx| Self::new(provider, window, cx));
+ }
+
+ fn new(provider: LlmCompatibleProvider, window: &mut Window, cx: &mut Context) -> Self {
+ Self {
+ input: AddLlmProviderInput::new(provider, window, cx),
+ provider,
+ last_error: None,
+ focus_handle: cx.focus_handle(),
+ }
+ }
+
+ fn confirm(&mut self, _: &menu::Confirm, _: &mut Window, cx: &mut Context) {
+ let task = save_provider_to_settings(&self.input, cx);
+ cx.spawn(async move |this, cx| {
+ let result = task.await;
+ this.update(cx, |this, cx| match result {
+ Ok(_) => {
+ cx.emit(DismissEvent);
+ }
+ Err(error) => {
+ this.last_error = Some(error);
+ cx.notify();
+ }
+ })
+ })
+ .detach_and_log_err(cx);
+ }
+
+ fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context) {
+ cx.emit(DismissEvent);
+ }
+
+ fn render_section(&self) -> Section {
+ Section::new()
+ .child(self.input.provider_name.clone())
+ .child(self.input.api_url.clone())
+ .child(self.input.api_key.clone())
+ }
+
+ fn render_model_section(&self, cx: &mut Context) -> Section {
+ Section::new().child(
+ v_flex()
+ .gap_2()
+ .child(
+ h_flex()
+ .justify_between()
+ .child(Label::new("Models").size(LabelSize::Small))
+ .child(
+ Button::new("add-model", "Add Model")
+ .icon(IconName::Plus)
+ .icon_position(IconPosition::Start)
+ .icon_size(IconSize::XSmall)
+ .icon_color(Color::Muted)
+ .label_size(LabelSize::Small)
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.input.add_model(window, cx);
+ cx.notify();
+ })),
+ ),
+ )
+ .children(
+ self.input
+ .models
+ .iter()
+ .enumerate()
+ .map(|(ix, _)| self.render_model(ix, cx)),
+ ),
+ )
+ }
+
+ fn render_model(&self, ix: usize, cx: &mut Context) -> impl IntoElement + use<> {
+ let has_more_than_one_model = self.input.models.len() > 1;
+ let model = &self.input.models[ix];
+
+ v_flex()
+ .p_2()
+ .gap_2()
+ .rounded_sm()
+ .border_1()
+ .border_dashed()
+ .border_color(cx.theme().colors().border.opacity(0.6))
+ .bg(cx.theme().colors().element_active.opacity(0.15))
+ .child(model.name.clone())
+ .child(
+ h_flex()
+ .gap_2()
+ .child(model.max_completion_tokens.clone())
+ .child(model.max_output_tokens.clone()),
+ )
+ .child(model.max_tokens.clone())
+ .when(has_more_than_one_model, |this| {
+ this.child(
+ Button::new(("remove-model", ix), "Remove Model")
+ .icon(IconName::Trash)
+ .icon_position(IconPosition::Start)
+ .icon_size(IconSize::XSmall)
+ .icon_color(Color::Muted)
+ .label_size(LabelSize::Small)
+ .style(ButtonStyle::Outlined)
+ .full_width()
+ .on_click(cx.listener(move |this, _, _window, cx| {
+ this.input.remove_model(ix);
+ cx.notify();
+ })),
+ )
+ })
+ }
+}
+
+impl EventEmitter for AddLlmProviderModal {}
+
+impl Focusable for AddLlmProviderModal {
+ fn focus_handle(&self, _cx: &App) -> FocusHandle {
+ self.focus_handle.clone()
+ }
+}
+
+impl ModalView for AddLlmProviderModal {}
+
+impl Render for AddLlmProviderModal {
+ fn render(&mut self, window: &mut ui::Window, cx: &mut ui::Context) -> impl IntoElement {
+ let focus_handle = self.focus_handle(cx);
+
+ div()
+ .id("add-llm-provider-modal")
+ .key_context("AddLlmProviderModal")
+ .w(rems(34.))
+ .elevation_3(cx)
+ .on_action(cx.listener(Self::cancel))
+ .capture_any_mouse_down(cx.listener(|this, _, window, cx| {
+ this.focus_handle(cx).focus(window);
+ }))
+ .child(
+ Modal::new("configure-context-server", None)
+ .header(ModalHeader::new().headline("Add LLM Provider").description(
+ match self.provider {
+ LlmCompatibleProvider::OpenAi => {
+ "This provider will use an OpenAI compatible API."
+ }
+ },
+ ))
+ .when_some(self.last_error.clone(), |this, error| {
+ this.section(
+ Section::new().child(
+ Banner::new()
+ .severity(ui::Severity::Warning)
+ .child(div().text_xs().child(error)),
+ ),
+ )
+ })
+ .child(
+ v_flex()
+ .id("modal_content")
+ .max_h_128()
+ .overflow_y_scroll()
+ .gap_2()
+ .child(self.render_section())
+ .child(self.render_model_section(cx)),
+ )
+ .footer(
+ ModalFooter::new().end_slot(
+ h_flex()
+ .gap_1()
+ .child(
+ Button::new("cancel", "Cancel")
+ .key_binding(
+ KeyBinding::for_action_in(
+ &menu::Cancel,
+ &focus_handle,
+ window,
+ cx,
+ )
+ .map(|kb| kb.size(rems_from_px(12.))),
+ )
+ .on_click(cx.listener(|this, _event, window, cx| {
+ this.cancel(&menu::Cancel, window, cx)
+ })),
+ )
+ .child(
+ Button::new("save-server", "Save Provider")
+ .key_binding(
+ KeyBinding::for_action_in(
+ &menu::Confirm,
+ &focus_handle,
+ window,
+ cx,
+ )
+ .map(|kb| kb.size(rems_from_px(12.))),
+ )
+ .on_click(cx.listener(|this, _event, window, cx| {
+ this.confirm(&menu::Confirm, window, cx)
+ })),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use editor::EditorSettings;
+ use fs::FakeFs;
+ use gpui::{TestAppContext, VisualTestContext};
+ use language::language_settings;
+ use language_model::{
+ LanguageModelProviderId, LanguageModelProviderName,
+ fake_provider::FakeLanguageModelProvider,
+ };
+ use project::Project;
+ use settings::{Settings as _, SettingsStore};
+ use util::path;
+
+ #[gpui::test]
+ async fn test_save_provider_invalid_inputs(cx: &mut TestAppContext) {
+ let cx = setup_test(cx).await;
+
+ assert_eq!(
+ save_provider_validation_errors("", "someurl", "somekey", vec![], cx,).await,
+ Some("Provider Name cannot be empty".into())
+ );
+
+ assert_eq!(
+ save_provider_validation_errors("someprovider", "", "somekey", vec![], cx,).await,
+ Some("API URL cannot be empty".into())
+ );
+
+ assert_eq!(
+ save_provider_validation_errors("someprovider", "someurl", "", vec![], cx,).await,
+ Some("API Key cannot be empty".into())
+ );
+
+ assert_eq!(
+ save_provider_validation_errors(
+ "someprovider",
+ "someurl",
+ "somekey",
+ vec![("", "200000", "200000", "32000")],
+ cx,
+ )
+ .await,
+ Some("Model Name cannot be empty".into())
+ );
+
+ assert_eq!(
+ save_provider_validation_errors(
+ "someprovider",
+ "someurl",
+ "somekey",
+ vec![("somemodel", "abc", "200000", "32000")],
+ cx,
+ )
+ .await,
+ Some("Max Tokens must be a number".into())
+ );
+
+ assert_eq!(
+ save_provider_validation_errors(
+ "someprovider",
+ "someurl",
+ "somekey",
+ vec![("somemodel", "200000", "abc", "32000")],
+ cx,
+ )
+ .await,
+ Some("Max Completion Tokens must be a number".into())
+ );
+
+ assert_eq!(
+ save_provider_validation_errors(
+ "someprovider",
+ "someurl",
+ "somekey",
+ vec![("somemodel", "200000", "200000", "abc")],
+ cx,
+ )
+ .await,
+ Some("Max Output Tokens must be a number".into())
+ );
+
+ assert_eq!(
+ save_provider_validation_errors(
+ "someprovider",
+ "someurl",
+ "somekey",
+ vec![
+ ("somemodel", "200000", "200000", "32000"),
+ ("somemodel", "200000", "200000", "32000"),
+ ],
+ cx,
+ )
+ .await,
+ Some("Model Names must be unique".into())
+ );
+ }
+
+ #[gpui::test]
+ async fn test_save_provider_name_conflict(cx: &mut TestAppContext) {
+ let cx = setup_test(cx).await;
+
+ cx.update(|_window, cx| {
+ LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
+ registry.register_provider(
+ FakeLanguageModelProvider::new(
+ LanguageModelProviderId::new("someprovider"),
+ LanguageModelProviderName::new("Some Provider"),
+ ),
+ cx,
+ );
+ });
+ });
+
+ assert_eq!(
+ save_provider_validation_errors(
+ "someprovider",
+ "someurl",
+ "someapikey",
+ vec![("somemodel", "200000", "200000", "32000")],
+ cx,
+ )
+ .await,
+ Some("Provider Name is already taken by another provider".into())
+ );
+ }
+
+ async fn setup_test(cx: &mut TestAppContext) -> &mut VisualTestContext {
+ cx.update(|cx| {
+ let store = SettingsStore::test(cx);
+ cx.set_global(store);
+ workspace::init_settings(cx);
+ Project::init_settings(cx);
+ theme::init(theme::LoadThemes::JustBase, cx);
+ language_settings::init(cx);
+ EditorSettings::register(cx);
+ language_model::init_settings(cx);
+ language_models::init_settings(cx);
+ });
+
+ let fs = FakeFs::new(cx.executor());
+ cx.update(|cx| ::set_global(fs.clone(), cx));
+ let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
+ let (_, cx) =
+ cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
+
+ cx
+ }
+
+ async fn save_provider_validation_errors(
+ provider_name: &str,
+ api_url: &str,
+ api_key: &str,
+ models: Vec<(&str, &str, &str, &str)>,
+ cx: &mut VisualTestContext,
+ ) -> Option {
+ fn set_text(
+ input: &Entity,
+ text: &str,
+ window: &mut Window,
+ cx: &mut App,
+ ) {
+ input.update(cx, |input, cx| {
+ input.editor().update(cx, |editor, cx| {
+ editor.set_text(text, window, cx);
+ });
+ });
+ }
+
+ let task = cx.update(|window, cx| {
+ let mut input = AddLlmProviderInput::new(LlmCompatibleProvider::OpenAi, window, cx);
+ set_text(&input.provider_name, provider_name, window, cx);
+ set_text(&input.api_url, api_url, window, cx);
+ set_text(&input.api_key, api_key, window, cx);
+
+ for (i, (name, max_tokens, max_completion_tokens, max_output_tokens)) in
+ models.iter().enumerate()
+ {
+ if i >= input.models.len() {
+ input.models.push(ModelInput::new(window, cx));
+ }
+ let model = &mut input.models[i];
+ set_text(&model.name, name, window, cx);
+ set_text(&model.max_tokens, max_tokens, window, cx);
+ set_text(
+ &model.max_completion_tokens,
+ max_completion_tokens,
+ window,
+ cx,
+ );
+ set_text(&model.max_output_tokens, max_output_tokens, window, cx);
+ }
+ save_provider_to_settings(&input, cx)
+ });
+
+ task.await.err()
+ }
+}
diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs
index e115957d26..7ffe662c1b 100644
--- a/crates/agent_ui/src/agent_panel.rs
+++ b/crates/agent_ui/src/agent_panel.rs
@@ -43,7 +43,7 @@ use anyhow::{Result, anyhow};
use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_tool::ToolWorkingSet;
-use client::{UserStore, zed_urls};
+use client::{DisableAiSettings, UserStore, zed_urls};
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
use feature_flags::{self, FeatureFlagAppExt};
use fs::Fs;
@@ -744,6 +744,7 @@ impl AgentPanel {
if workspace
.panel::(cx)
.is_some_and(|panel| panel.read(cx).enabled(cx))
+ && !DisableAiSettings::get_global(cx).disable_ai
{
workspace.toggle_panel_focus::(window, cx);
}
@@ -1665,7 +1666,10 @@ impl Panel for AgentPanel {
}
fn icon(&self, _window: &Window, cx: &App) -> Option {
- (self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAssistant)
+ (self.enabled(cx)
+ && AgentSettings::get_global(cx).button
+ && !DisableAiSettings::get_global(cx).disable_ai)
+ .then_some(IconName::ZedAssistant)
}
fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs
index 17fa4d1ea8..0032bf90f9 100644
--- a/crates/agent_ui/src/agent_ui.rs
+++ b/crates/agent_ui/src/agent_ui.rs
@@ -31,7 +31,8 @@ use std::sync::Arc;
use agent::{Thread, ThreadId};
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
use assistant_slash_command::SlashCommandRegistry;
-use client::Client;
+use client::{Client, DisableAiSettings};
+use command_palette_hooks::CommandPaletteFilter;
use feature_flags::FeatureFlagAppExt as _;
use fs::Fs;
use gpui::{Action, App, Entity, actions};
@@ -43,6 +44,7 @@ use prompt_store::PromptBuilder;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings as _, SettingsStore};
+use std::any::TypeId;
pub use crate::active_thread::ActiveThread;
use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
@@ -52,6 +54,7 @@ use crate::slash_command_settings::SlashCommandSettings;
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
pub use ui::preview::{all_agent_previews, get_agent_preview};
+use zed_actions;
actions!(
agent,
@@ -243,6 +246,66 @@ pub fn init(
})
.detach();
cx.observe_new(ManageProfilesModal::register).detach();
+
+ // Update command palette filter based on AI settings
+ update_command_palette_filter(cx);
+
+ // Watch for settings changes
+ cx.observe_global::(|app_cx| {
+ // When settings change, update the command palette filter
+ update_command_palette_filter(app_cx);
+ })
+ .detach();
+}
+
+fn update_command_palette_filter(cx: &mut App) {
+ let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
+ CommandPaletteFilter::update_global(cx, |filter, _| {
+ if disable_ai {
+ filter.hide_namespace("agent");
+ filter.hide_namespace("assistant");
+ filter.hide_namespace("zed_predict_onboarding");
+ filter.hide_namespace("edit_prediction");
+
+ use editor::actions::{
+ AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
+ PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
+ };
+ let edit_prediction_actions = [
+ TypeId::of::(),
+ TypeId::of::(),
+ TypeId::of::(),
+ TypeId::of::(),
+ TypeId::of::(),
+ TypeId::of::(),
+ ];
+ filter.hide_action_types(&edit_prediction_actions);
+ filter.hide_action_types(&[TypeId::of::()]);
+ } else {
+ filter.show_namespace("agent");
+ filter.show_namespace("assistant");
+ filter.show_namespace("zed_predict_onboarding");
+
+ filter.show_namespace("edit_prediction");
+
+ use editor::actions::{
+ AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
+ PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
+ };
+ let edit_prediction_actions = [
+ TypeId::of::(),
+ TypeId::of::(),
+ TypeId::of::(),
+ TypeId::of::(),
+ TypeId::of::(),
+ TypeId::of::(),
+ ];
+ filter.show_action_types(edit_prediction_actions.iter());
+
+ filter
+ .show_action_types([TypeId::of::()].iter());
+ }
+ });
}
fn init_language_model_settings(cx: &mut App) {
diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs
index 65b72cbba5..44ec050ae2 100644
--- a/crates/agent_ui/src/inline_assistant.rs
+++ b/crates/agent_ui/src/inline_assistant.rs
@@ -16,7 +16,7 @@ use agent::{
};
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
-use client::telemetry::Telemetry;
+use client::{DisableAiSettings, telemetry::Telemetry};
use collections::{HashMap, HashSet, VecDeque, hash_map};
use editor::SelectionEffects;
use editor::{
@@ -57,6 +57,17 @@ pub fn init(
cx: &mut App,
) {
cx.set_global(InlineAssistant::new(fs, prompt_builder, telemetry));
+
+ cx.observe_global::(|cx| {
+ if DisableAiSettings::get_global(cx).disable_ai {
+ // Hide any active inline assist UI when AI is disabled
+ InlineAssistant::update_global(cx, |assistant, cx| {
+ assistant.cancel_all_active_completions(cx);
+ });
+ }
+ })
+ .detach();
+
cx.observe_new(|_workspace: &mut Workspace, window, cx| {
let Some(window) = window else {
return;
@@ -141,6 +152,26 @@ impl InlineAssistant {
.detach();
}
+ /// Hides all active inline assists when AI is disabled
+ pub fn cancel_all_active_completions(&mut self, cx: &mut App) {
+ // Cancel all active completions in editors
+ for (editor_handle, _) in self.assists_by_editor.iter() {
+ if let Some(editor) = editor_handle.upgrade() {
+ let windows = cx.windows();
+ if !windows.is_empty() {
+ let window = windows[0];
+ let _ = window.update(cx, |_, window, cx| {
+ editor.update(cx, |editor, cx| {
+ if editor.has_active_inline_completion() {
+ editor.cancel(&Default::default(), window, cx);
+ }
+ });
+ });
+ }
+ }
+ }
+ }
+
fn handle_workspace_event(
&mut self,
workspace: Entity,
@@ -176,7 +207,7 @@ impl InlineAssistant {
window: &mut Window,
cx: &mut App,
) {
- let is_assistant2_enabled = true;
+ let is_assistant2_enabled = !DisableAiSettings::get_global(cx).disable_ai;
if let Some(editor) = item.act_as::(cx) {
editor.update(cx, |editor, cx| {
@@ -199,6 +230,13 @@ impl InlineAssistant {
cx,
);
+ if DisableAiSettings::get_global(cx).disable_ai {
+ // Cancel any active completions
+ if editor.has_active_inline_completion() {
+ editor.cancel(&Default::default(), window, cx);
+ }
+ }
+
// Remove the Assistant1 code action provider, as it still might be registered.
editor.remove_code_action_provider("assistant".into(), window, cx);
} else {
@@ -219,7 +257,7 @@ impl InlineAssistant {
cx: &mut Context,
) {
let settings = AgentSettings::get_global(cx);
- if !settings.enabled {
+ if !settings.enabled || DisableAiSettings::get_global(cx).disable_ai {
return;
}
diff --git a/crates/ai_onboarding/src/agent_api_keys_onboarding.rs b/crates/ai_onboarding/src/agent_api_keys_onboarding.rs
index 4f9e20cf77..5f56e4d26e 100644
--- a/crates/ai_onboarding/src/agent_api_keys_onboarding.rs
+++ b/crates/ai_onboarding/src/agent_api_keys_onboarding.rs
@@ -38,10 +38,6 @@ impl ApiKeysWithProviders {
.map(|provider| (provider.icon(), provider.name().0.clone()))
.collect()
}
-
- pub fn has_providers(&self) -> bool {
- !self.configured_providers.is_empty()
- }
}
impl Render for ApiKeysWithProviders {
@@ -53,11 +49,10 @@ impl Render for ApiKeysWithProviders {
.map(|(icon, name)| {
h_flex()
.gap_1p5()
- .child(Icon::new(icon).size(IconSize::Small).color(Color::Muted))
+ .child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted))
.child(Label::new(name))
});
-
- h_flex()
+ div()
.mx_2p5()
.p_1()
.pb_0()
@@ -85,8 +80,24 @@ impl Render for ApiKeysWithProviders {
.border_x_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().panel_background)
- .child(Icon::new(IconName::Info).size(IconSize::XSmall).color(Color::Muted))
- .child(Label::new("Or start now using API keys from your environment for the following providers:").color(Color::Muted))
+ .child(
+ h_flex()
+ .min_w_0()
+ .gap_2()
+ .child(
+ Icon::new(IconName::Info)
+ .size(IconSize::XSmall)
+ .color(Color::Muted)
+ )
+ .child(
+ div()
+ .w_full()
+ .child(
+ Label::new("Start now using API keys from your environment for the following providers:")
+ .color(Color::Muted)
+ )
+ )
+ )
.children(configured_providers_list)
)
}
@@ -118,7 +129,7 @@ impl RenderOnce for ApiKeysWithoutProviders {
.child(Divider::horizontal()),
)
.child(List::new().child(BulletItem::new(
- "You can also use AI in Zed by bringing your own API keys",
+ "Add your own keys to use AI without signing in.",
)))
.child(
Button::new("configure-providers", "Configure Providers")
diff --git a/crates/ai_onboarding/src/ai_onboarding.rs b/crates/ai_onboarding/src/ai_onboarding.rs
index 88c962c1ba..e8ce22ff4e 100644
--- a/crates/ai_onboarding/src/ai_onboarding.rs
+++ b/crates/ai_onboarding/src/ai_onboarding.rs
@@ -141,22 +141,18 @@ impl ZedAiOnboarding {
)
.child(
List::new()
+ .child(BulletItem::new("50 prompts per month with Claude models"))
.child(BulletItem::new(
- "50 prompts per month with the Claude models",
- ))
- .child(BulletItem::new(
- "2000 accepted edit predictions using our open-source Zeta model",
+ "2,000 accepted edit predictions with Zeta, our open-source model",
)),
)
}
fn pro_trial_definition(&self) -> impl IntoElement {
List::new()
+ .child(BulletItem::new("150 prompts with Claude models"))
.child(BulletItem::new(
- "150 prompts per month with the Claude models",
- ))
- .child(BulletItem::new(
- "Unlimited accepted edit predictions using our open-source Zeta model",
+ "Unlimited accepted edit predictions with Zeta, our open-source model",
))
}
@@ -178,12 +174,12 @@ impl ZedAiOnboarding {
List::new()
.child(BulletItem::new("500 prompts per month with Claude models"))
.child(BulletItem::new(
- "Unlimited accepted edit predictions using our open-source Zeta model",
+ "Unlimited accepted edit predictions with Zeta, our open-source model",
))
- .child(BulletItem::new("USD $20 per month")),
+ .child(BulletItem::new("$20 USD per month")),
)
.child(
- Button::new("pro", "Start with Pro")
+ Button::new("pro", "Get Started")
.full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click(move |_, _window, cx| {
@@ -206,11 +202,11 @@ impl ZedAiOnboarding {
List::new()
.child(self.pro_trial_definition())
.child(BulletItem::new(
- "Try it out for 14 days with no charge and no credit card required",
+ "Try it out for 14 days for free, no credit card required",
)),
)
.child(
- Button::new("pro", "Start Pro Trial")
+ Button::new("pro", "Start Free Trial")
.full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click(move |_, _window, cx| {
@@ -225,14 +221,14 @@ impl ZedAiOnboarding {
v_flex()
.gap_1()
.w_full()
- .child(Headline::new("Before starting…"))
+ .child(Headline::new("Accept Terms of Service"))
.child(
- Label::new("Make sure you have read and accepted Zed AI's terms of service.")
+ Label::new("We don’t sell your data, track you across the web, or compromise your privacy.")
.color(Color::Muted)
.mb_2(),
)
.child(
- Button::new("terms_of_service", "View and Read the Terms of Service")
+ Button::new("terms_of_service", "Review Terms of Service")
.full_width()
.style(ButtonStyle::Outlined)
.icon(IconName::ArrowUpRight)
@@ -241,7 +237,7 @@ impl ZedAiOnboarding {
.on_click(move |_, _window, cx| cx.open_url(&zed_urls::terms_of_service(cx))),
)
.child(
- Button::new("accept_terms", "I've read it and accept it")
+ Button::new("accept_terms", "Accept")
.full_width()
.style(ButtonStyle::Tinted(TintColor::Accent))
.on_click({
@@ -259,13 +255,13 @@ impl ZedAiOnboarding {
.gap_1()
.child(Headline::new("Welcome to Zed AI"))
.child(
- Label::new("Sign in to start using AI in Zed with a free trial of the Pro plan, which includes:")
+ Label::new("Sign in to try Zed Pro for 14 days, no credit card required.")
.color(Color::Muted)
.mb_2(),
)
.child(self.pro_trial_definition())
.child(
- Button::new("sign_in", "Sign in to Start Trial")
+ Button::new("sign_in", "Try Zed Pro for Free")
.disabled(signing_in)
.full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
@@ -284,11 +280,6 @@ impl ZedAiOnboarding {
.relative()
.gap_1()
.child(Headline::new("Welcome to Zed AI"))
- .child(
- Label::new("Choose how you want to start.")
- .color(Color::Muted)
- .mb_2(),
- )
.map(|this| {
if self.account_too_young {
this.child(young_account_banner)
@@ -318,7 +309,7 @@ impl ZedAiOnboarding {
v_flex()
.relative()
.gap_1()
- .child(Headline::new("Welcome to the Zed Pro free trial"))
+ .child(Headline::new("Welcome to the Zed Pro Trial"))
.child(
Label::new("Here's what you get for the next 14 days:")
.color(Color::Muted)
diff --git a/crates/ai_onboarding/src/young_account_banner.rs b/crates/ai_onboarding/src/young_account_banner.rs
index 1e1ed3a865..a43625a60e 100644
--- a/crates/ai_onboarding/src/young_account_banner.rs
+++ b/crates/ai_onboarding/src/young_account_banner.rs
@@ -6,7 +6,7 @@ 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, we cannot offer plans to GitHub accounts created fewer than 30 days ago. To request an exception, reach out to billing@zed.dev.";
+ const YOUNG_ACCOUNT_DISCLAIMER: &str = "To prevent abuse of our service, we cannot offer plans to GitHub accounts created fewer than 30 days ago. To request an exception, reach out to billing-support@zed.dev.";
let label = div()
.w_full()
diff --git a/crates/assistant_context/src/assistant_context_tests.rs b/crates/assistant_context/src/assistant_context_tests.rs
index dba3bfde61..f139d525d3 100644
--- a/crates/assistant_context/src/assistant_context_tests.rs
+++ b/crates/assistant_context/src/assistant_context_tests.rs
@@ -1323,7 +1323,7 @@ fn setup_context_editor_with_fake_model(
) -> (Entity, Arc) {
let registry = Arc::new(LanguageRegistry::test(cx.executor().clone()));
- let fake_provider = Arc::new(FakeLanguageModelProvider);
+ let fake_provider = Arc::new(FakeLanguageModelProvider::default());
let fake_model = Arc::new(fake_provider.test_model());
cx.update(|cx| {
diff --git a/crates/assistant_tool/src/action_log.rs b/crates/assistant_tool/src/action_log.rs
index ecbbcc785e..672c048872 100644
--- a/crates/assistant_tool/src/action_log.rs
+++ b/crates/assistant_tool/src/action_log.rs
@@ -51,23 +51,13 @@ impl ActionLog {
Some(self.tracked_buffers.get(buffer)?.snapshot.clone())
}
- pub fn has_unnotified_user_edits(&self) -> bool {
- self.tracked_buffers
- .values()
- .any(|tracked| tracked.has_unnotified_user_edits)
- }
-
/// Return a unified diff patch with user edits made since last read or notification
pub fn unnotified_user_edits(&self, cx: &Context) -> Option {
- if !self.has_unnotified_user_edits() {
- return None;
- }
-
- let unified_diff = self
+ let diffs = self
.tracked_buffers
.values()
.filter_map(|tracked| {
- if !tracked.has_unnotified_user_edits {
+ if !tracked.may_have_unnotified_user_edits {
return None;
}
@@ -95,9 +85,13 @@ impl ActionLog {
Some(result)
})
- .collect::>()
- .join("\n\n");
+ .collect::>();
+ if diffs.is_empty() {
+ return None;
+ }
+
+ let unified_diff = diffs.join("\n\n");
Some(unified_diff)
}
@@ -106,7 +100,7 @@ impl ActionLog {
pub fn flush_unnotified_user_edits(&mut self, cx: &Context) -> Option {
let patch = self.unnotified_user_edits(cx);
self.tracked_buffers.values_mut().for_each(|tracked| {
- tracked.has_unnotified_user_edits = false;
+ tracked.may_have_unnotified_user_edits = false;
tracked.last_seen_base = tracked.diff_base.clone();
});
patch
@@ -185,7 +179,7 @@ impl ActionLog {
version: buffer.read(cx).version(),
diff,
diff_update: diff_update_tx,
- has_unnotified_user_edits: false,
+ may_have_unnotified_user_edits: false,
_open_lsp_handle: open_lsp_handle,
_maintain_diff: cx.spawn({
let buffer = buffer.clone();
@@ -337,27 +331,34 @@ impl ActionLog {
let new_snapshot = buffer_snapshot.clone();
let unreviewed_edits = tracked_buffer.unreviewed_edits.clone();
let edits = diff_snapshots(&old_snapshot, &new_snapshot);
- if let ChangeAuthor::User = author
- && !edits.is_empty()
- {
- tracked_buffer.has_unnotified_user_edits = true;
- }
+ let mut has_user_changes = false;
async move {
if let ChangeAuthor::User = author {
- apply_non_conflicting_edits(
+ has_user_changes = apply_non_conflicting_edits(
&unreviewed_edits,
edits,
&mut base_text,
new_snapshot.as_rope(),
);
}
- (Arc::new(base_text.to_string()), base_text)
+
+ (Arc::new(base_text.to_string()), base_text, has_user_changes)
}
});
anyhow::Ok(rebase)
})??;
- let (new_base_text, new_diff_base) = rebase.await;
+ let (new_base_text, new_diff_base, has_user_changes) = rebase.await;
+
+ this.update(cx, |this, _| {
+ let tracked_buffer = this
+ .tracked_buffers
+ .get_mut(buffer)
+ .context("buffer not tracked")
+ .unwrap();
+ tracked_buffer.may_have_unnotified_user_edits |= has_user_changes;
+ })?;
+
Self::update_diff(
this,
buffer,
@@ -829,11 +830,12 @@ fn apply_non_conflicting_edits(
edits: Vec>,
old_text: &mut Rope,
new_text: &Rope,
-) {
+) -> bool {
let mut old_edits = patch.edits().iter().cloned().peekable();
let mut new_edits = edits.into_iter().peekable();
let mut applied_delta = 0i32;
let mut rebased_delta = 0i32;
+ let mut has_made_changes = false;
while let Some(mut new_edit) = new_edits.next() {
let mut conflict = false;
@@ -883,8 +885,10 @@ fn apply_non_conflicting_edits(
&new_text.chunks_in_range(new_bytes).collect::(),
);
applied_delta += new_edit.new_len() as i32 - new_edit.old_len() as i32;
+ has_made_changes = true;
}
}
+ has_made_changes
}
fn diff_snapshots(
@@ -958,7 +962,7 @@ struct TrackedBuffer {
diff: Entity,
snapshot: text::BufferSnapshot,
diff_update: mpsc::UnboundedSender<(ChangeAuthor, text::BufferSnapshot)>,
- has_unnotified_user_edits: bool,
+ may_have_unnotified_user_edits: bool,
_open_lsp_handle: OpenLspBufferHandle,
_maintain_diff: Task<()>,
_subscription: Subscription,
diff --git a/crates/assistant_tools/Cargo.toml b/crates/assistant_tools/Cargo.toml
index e234b62b14..146800e094 100644
--- a/crates/assistant_tools/Cargo.toml
+++ b/crates/assistant_tools/Cargo.toml
@@ -20,6 +20,7 @@ anyhow.workspace = true
assistant_tool.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
+client.workspace = true
collections.workspace = true
component.workspace = true
derive_more.workspace = true
diff --git a/crates/assistant_tools/src/assistant_tools.rs b/crates/assistant_tools/src/assistant_tools.rs
index eef792f526..57fdc51336 100644
--- a/crates/assistant_tools/src/assistant_tools.rs
+++ b/crates/assistant_tools/src/assistant_tools.rs
@@ -20,14 +20,13 @@ mod thinking_tool;
mod ui;
mod web_search_tool;
-use std::sync::Arc;
-
use assistant_tool::ToolRegistry;
use copy_path_tool::CopyPathTool;
use gpui::{App, Entity};
use http_client::HttpClientWithUrl;
use language_model::LanguageModelRegistry;
use move_path_tool::MovePathTool;
+use std::sync::Arc;
use web_search_tool::WebSearchTool;
pub(crate) use templates::*;
diff --git a/crates/assistant_tools/src/edit_file_tool.rs b/crates/assistant_tools/src/edit_file_tool.rs
index 0423f56145..6413677bd9 100644
--- a/crates/assistant_tools/src/edit_file_tool.rs
+++ b/crates/assistant_tools/src/edit_file_tool.rs
@@ -278,6 +278,9 @@ impl Tool for EditFileTool {
.unwrap_or(false);
if format_on_save_enabled {
+ action_log.update(cx, |log, cx| {
+ log.buffer_edited(buffer.clone(), cx);
+ })?;
let format_task = project.update(cx, |project, cx| {
project.format(
HashSet::from_iter([buffer.clone()]),
diff --git a/crates/assistant_tools/src/project_notifications_tool.rs b/crates/assistant_tools/src/project_notifications_tool.rs
index ec315d9ab1..7567926dca 100644
--- a/crates/assistant_tools/src/project_notifications_tool.rs
+++ b/crates/assistant_tools/src/project_notifications_tool.rs
@@ -200,7 +200,7 @@ mod tests {
// Run the tool before any changes
let tool = Arc::new(ProjectNotificationsTool);
- let provider = Arc::new(FakeLanguageModelProvider);
+ let provider = Arc::new(FakeLanguageModelProvider::default());
let model: Arc = Arc::new(provider.test_model());
let request = Arc::new(LanguageModelRequest::default());
let tool_input = json!({});
diff --git a/crates/aws_http_client/Cargo.toml b/crates/aws_http_client/Cargo.toml
index 3760f70fe0..2749286d4c 100644
--- a/crates/aws_http_client/Cargo.toml
+++ b/crates/aws_http_client/Cargo.toml
@@ -17,7 +17,5 @@ default = []
[dependencies]
aws-smithy-runtime-api.workspace = true
aws-smithy-types.workspace = true
-futures.workspace = true
http_client.workspace = true
-tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
workspace-hack.workspace = true
diff --git a/crates/aws_http_client/src/aws_http_client.rs b/crates/aws_http_client/src/aws_http_client.rs
index 6adb995747..d08c8e64a7 100644
--- a/crates/aws_http_client/src/aws_http_client.rs
+++ b/crates/aws_http_client/src/aws_http_client.rs
@@ -11,14 +11,11 @@ use aws_smithy_runtime_api::client::result::ConnectorError;
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
use aws_smithy_runtime_api::http::{Headers, StatusCode};
use aws_smithy_types::body::SdkBody;
-use futures::AsyncReadExt;
-use http_client::{AsyncBody, Inner};
+use http_client::AsyncBody;
use http_client::{HttpClient, Request};
-use tokio::runtime::Handle;
struct AwsHttpConnector {
client: Arc,
- handle: Handle,
}
impl std::fmt::Debug for AwsHttpConnector {
@@ -42,18 +39,17 @@ impl AwsConnector for AwsHttpConnector {
.client
.send(Request::from_parts(parts, convert_to_async_body(body)));
- let handle = self.handle.clone();
-
HttpConnectorFuture::new(async move {
let response = match response.await {
Ok(response) => response,
Err(err) => return Err(ConnectorError::other(err.into(), None)),
};
let (parts, body) = response.into_parts();
- let body = convert_to_sdk_body(body, handle).await;
- let mut response =
- HttpResponse::new(StatusCode::try_from(parts.status.as_u16()).unwrap(), body);
+ let mut response = HttpResponse::new(
+ StatusCode::try_from(parts.status.as_u16()).unwrap(),
+ convert_to_sdk_body(body),
+ );
let headers = match Headers::try_from(parts.headers) {
Ok(headers) => headers,
@@ -70,7 +66,6 @@ impl AwsConnector for AwsHttpConnector {
#[derive(Clone)]
pub struct AwsHttpClient {
client: Arc,
- handler: Handle,
}
impl std::fmt::Debug for AwsHttpClient {
@@ -80,11 +75,8 @@ impl std::fmt::Debug for AwsHttpClient {
}
impl AwsHttpClient {
- pub fn new(client: Arc, handle: Handle) -> Self {
- Self {
- client,
- handler: handle,
- }
+ pub fn new(client: Arc) -> Self {
+ Self { client }
}
}
@@ -96,25 +88,12 @@ impl AwsClient for AwsHttpClient {
) -> SharedHttpConnector {
SharedHttpConnector::new(AwsHttpConnector {
client: self.client.clone(),
- handle: self.handler.clone(),
})
}
}
-pub async fn convert_to_sdk_body(body: AsyncBody, handle: Handle) -> SdkBody {
- match body.0 {
- Inner::Empty => SdkBody::empty(),
- Inner::Bytes(bytes) => SdkBody::from(bytes.into_inner()),
- Inner::AsyncReader(mut reader) => {
- let buffer = handle.spawn(async move {
- let mut buffer = Vec::new();
- let _ = reader.read_to_end(&mut buffer).await;
- buffer
- });
-
- SdkBody::from(buffer.await.unwrap_or_default())
- }
- }
+pub fn convert_to_sdk_body(body: AsyncBody) -> SdkBody {
+ SdkBody::from_body_1_x(body)
}
pub fn convert_to_async_body(body: SdkBody) -> AsyncBody {
diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs
index ee09fda46e..97f529fe37 100644
--- a/crates/buffer_diff/src/buffer_diff.rs
+++ b/crates/buffer_diff/src/buffer_diff.rs
@@ -343,8 +343,7 @@ impl BufferDiffInner {
..
} in hunks.iter().cloned()
{
- let preceding_pending_hunks =
- old_pending_hunks.slice(&buffer_range.start, Bias::Left, buffer);
+ let preceding_pending_hunks = old_pending_hunks.slice(&buffer_range.start, Bias::Left);
pending_hunks.append(preceding_pending_hunks, buffer);
// Skip all overlapping or adjacent old pending hunks
@@ -355,7 +354,7 @@ impl BufferDiffInner {
.cmp(&buffer_range.end, buffer)
.is_le()
}) {
- old_pending_hunks.next(buffer);
+ old_pending_hunks.next();
}
if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk)
@@ -379,10 +378,10 @@ impl BufferDiffInner {
);
}
// append the remainder
- pending_hunks.append(old_pending_hunks.suffix(buffer), buffer);
+ pending_hunks.append(old_pending_hunks.suffix(), buffer);
let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::(buffer);
- unstaged_hunk_cursor.next(buffer);
+ unstaged_hunk_cursor.next();
// then, iterate over all pending hunks (both new ones and the existing ones) and compute the edits
let mut prev_unstaged_hunk_buffer_end = 0;
@@ -397,8 +396,7 @@ impl BufferDiffInner {
}) = pending_hunks_iter.next()
{
// Advance unstaged_hunk_cursor to skip unstaged hunks before current hunk
- let skipped_unstaged =
- unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left, buffer);
+ let skipped_unstaged = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left);
if let Some(unstaged_hunk) = skipped_unstaged.last() {
prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
@@ -425,7 +423,7 @@ impl BufferDiffInner {
buffer_offset_range.end =
buffer_offset_range.end.max(unstaged_hunk_offset_range.end);
- unstaged_hunk_cursor.next(buffer);
+ unstaged_hunk_cursor.next();
continue;
}
}
@@ -514,7 +512,7 @@ impl BufferDiffInner {
});
let anchor_iter = iter::from_fn(move || {
- cursor.next(buffer);
+ cursor.next();
cursor.item()
})
.flat_map(move |hunk| {
@@ -531,12 +529,12 @@ impl BufferDiffInner {
});
let mut pending_hunks_cursor = self.pending_hunks.cursor::(buffer);
- pending_hunks_cursor.next(buffer);
+ pending_hunks_cursor.next();
let mut secondary_cursor = None;
if let Some(secondary) = secondary.as_ref() {
let mut cursor = secondary.hunks.cursor::(buffer);
- cursor.next(buffer);
+ cursor.next();
secondary_cursor = Some(cursor);
}
@@ -564,7 +562,7 @@ impl BufferDiffInner {
.cmp(&pending_hunks_cursor.start().buffer_range.start, buffer)
.is_gt()
{
- pending_hunks_cursor.seek_forward(&start_anchor, Bias::Left, buffer);
+ pending_hunks_cursor.seek_forward(&start_anchor, Bias::Left);
}
if let Some(pending_hunk) = pending_hunks_cursor.item() {
@@ -590,7 +588,7 @@ impl BufferDiffInner {
.cmp(&secondary_cursor.start().buffer_range.start, buffer)
.is_gt()
{
- secondary_cursor.seek_forward(&start_anchor, Bias::Left, buffer);
+ secondary_cursor.seek_forward(&start_anchor, Bias::Left);
}
if let Some(secondary_hunk) = secondary_cursor.item() {
@@ -635,7 +633,7 @@ impl BufferDiffInner {
});
iter::from_fn(move || {
- cursor.prev(buffer);
+ cursor.prev();
let hunk = cursor.item()?;
let range = hunk.buffer_range.to_point(buffer);
@@ -653,8 +651,8 @@ impl BufferDiffInner {
fn compare(&self, old: &Self, new_snapshot: &text::BufferSnapshot) -> Option> {
let mut new_cursor = self.hunks.cursor::<()>(new_snapshot);
let mut old_cursor = old.hunks.cursor::<()>(new_snapshot);
- old_cursor.next(new_snapshot);
- new_cursor.next(new_snapshot);
+ old_cursor.next();
+ new_cursor.next();
let mut start = None;
let mut end = None;
@@ -669,7 +667,7 @@ impl BufferDiffInner {
Ordering::Less => {
start.get_or_insert(new_hunk.buffer_range.start);
end.replace(new_hunk.buffer_range.end);
- new_cursor.next(new_snapshot);
+ new_cursor.next();
}
Ordering::Equal => {
if new_hunk != old_hunk {
@@ -686,25 +684,25 @@ impl BufferDiffInner {
}
}
- new_cursor.next(new_snapshot);
- old_cursor.next(new_snapshot);
+ new_cursor.next();
+ old_cursor.next();
}
Ordering::Greater => {
start.get_or_insert(old_hunk.buffer_range.start);
end.replace(old_hunk.buffer_range.end);
- old_cursor.next(new_snapshot);
+ old_cursor.next();
}
}
}
(Some(new_hunk), None) => {
start.get_or_insert(new_hunk.buffer_range.start);
end.replace(new_hunk.buffer_range.end);
- new_cursor.next(new_snapshot);
+ new_cursor.next();
}
(None, Some(old_hunk)) => {
start.get_or_insert(old_hunk.buffer_range.start);
end.replace(old_hunk.buffer_range.end);
- old_cursor.next(new_snapshot);
+ old_cursor.next();
}
(None, None) => break,
}
diff --git a/crates/channel/src/channel_chat.rs b/crates/channel/src/channel_chat.rs
index 8394972d43..866e3ccd90 100644
--- a/crates/channel/src/channel_chat.rs
+++ b/crates/channel/src/channel_chat.rs
@@ -333,7 +333,7 @@ impl ChannelChat {
if first_id <= message_id {
let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>(&());
let message_id = ChannelMessageId::Saved(message_id);
- cursor.seek(&message_id, Bias::Left, &());
+ cursor.seek(&message_id, Bias::Left);
return ControlFlow::Break(
if cursor
.item()
@@ -499,7 +499,7 @@ impl ChannelChat {
pub fn message(&self, ix: usize) -> &ChannelMessage {
let mut cursor = self.messages.cursor::(&());
- cursor.seek(&Count(ix), Bias::Right, &());
+ cursor.seek(&Count(ix), Bias::Right);
cursor.item().unwrap()
}
@@ -516,13 +516,13 @@ impl ChannelChat {
pub fn messages_in_range(&self, range: Range) -> impl Iterator- {
let mut cursor = self.messages.cursor::(&());
- cursor.seek(&Count(range.start), Bias::Right, &());
+ cursor.seek(&Count(range.start), Bias::Right);
cursor.take(range.len())
}
pub fn pending_messages(&self) -> impl Iterator
- {
let mut cursor = self.messages.cursor::(&());
- cursor.seek(&ChannelMessageId::Pending(0), Bias::Left, &());
+ cursor.seek(&ChannelMessageId::Pending(0), Bias::Left);
cursor
}
@@ -588,9 +588,9 @@ impl ChannelChat {
.collect::>();
let mut old_cursor = self.messages.cursor::<(ChannelMessageId, Count)>(&());
- let mut new_messages = old_cursor.slice(&first_message.id, Bias::Left, &());
+ let mut new_messages = old_cursor.slice(&first_message.id, Bias::Left);
let start_ix = old_cursor.start().1.0;
- let removed_messages = old_cursor.slice(&last_message.id, Bias::Right, &());
+ let removed_messages = old_cursor.slice(&last_message.id, Bias::Right);
let removed_count = removed_messages.summary().count;
let new_count = messages.summary().count;
let end_ix = start_ix + removed_count;
@@ -599,10 +599,10 @@ impl ChannelChat {
let mut ranges = Vec::>::new();
if new_messages.last().unwrap().is_pending() {
- new_messages.append(old_cursor.suffix(&()), &());
+ new_messages.append(old_cursor.suffix(), &());
} else {
new_messages.append(
- old_cursor.slice(&ChannelMessageId::Pending(0), Bias::Left, &()),
+ old_cursor.slice(&ChannelMessageId::Pending(0), Bias::Left),
&(),
);
@@ -617,7 +617,7 @@ impl ChannelChat {
} else {
new_messages.push(message.clone(), &());
}
- old_cursor.next(&());
+ old_cursor.next();
}
}
@@ -641,12 +641,12 @@ impl ChannelChat {
fn message_removed(&mut self, id: u64, cx: &mut Context) {
let mut cursor = self.messages.cursor::(&());
- let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left, &());
+ let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left);
if let Some(item) = cursor.item() {
if item.id == ChannelMessageId::Saved(id) {
let deleted_message_ix = messages.summary().count;
- cursor.next(&());
- messages.append(cursor.suffix(&()), &());
+ cursor.next();
+ messages.append(cursor.suffix(), &());
drop(cursor);
self.messages = messages;
@@ -680,7 +680,7 @@ impl ChannelChat {
cx: &mut Context,
) {
let mut cursor = self.messages.cursor::(&());
- let mut messages = cursor.slice(&id, Bias::Left, &());
+ let mut messages = cursor.slice(&id, Bias::Left);
let ix = messages.summary().count;
if let Some(mut message_to_update) = cursor.item().cloned() {
@@ -688,10 +688,10 @@ impl ChannelChat {
message_to_update.mentions = mentions;
message_to_update.edited_at = edited_at;
messages.push(message_to_update, &());
- cursor.next(&());
+ cursor.next();
}
- messages.append(cursor.suffix(&()), &());
+ messages.append(cursor.suffix(), &());
drop(cursor);
self.messages = messages;
diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs
index 1be8ffdb55..81bb95b514 100644
--- a/crates/client/src/client.rs
+++ b/crates/client/src/client.rs
@@ -151,6 +151,7 @@ impl Settings for ProxySettings {
pub fn init_settings(cx: &mut App) {
TelemetrySettings::register(cx);
+ DisableAiSettings::register(cx);
ClientSettings::register(cx);
ProxySettings::register(cx);
}
@@ -548,6 +549,33 @@ impl settings::Settings for TelemetrySettings {
}
}
+/// Whether to disable all AI features in Zed.
+///
+/// Default: false
+#[derive(Copy, Clone, Debug)]
+pub struct DisableAiSettings {
+ pub disable_ai: bool,
+}
+
+impl settings::Settings for DisableAiSettings {
+ const KEY: Option<&'static str> = Some("disable_ai");
+
+ type FileContent = Option;
+
+ fn load(sources: SettingsSources, _: &mut App) -> Result {
+ Ok(Self {
+ disable_ai: sources
+ .user
+ .or(sources.server)
+ .copied()
+ .flatten()
+ .unwrap_or(sources.default.ok_or_else(Self::missing_default)?),
+ })
+ }
+
+ fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
+}
+
impl Client {
pub fn new(
clock: Arc,
diff --git a/crates/collab/src/stripe_billing.rs b/crates/collab/src/stripe_billing.rs
index 3d52dea0e3..50accf9557 100644
--- a/crates/collab/src/stripe_billing.rs
+++ b/crates/collab/src/stripe_billing.rs
@@ -17,7 +17,7 @@ use crate::stripe_client::{
StripeCreateCheckoutSessionSubscriptionData, StripeCreateMeterEventParams,
StripeCreateMeterEventPayload, StripeCreateSubscriptionItems, StripeCreateSubscriptionParams,
StripeCustomerId, StripeCustomerUpdate, StripeCustomerUpdateAddress, StripeCustomerUpdateName,
- StripeMeter, StripePrice, StripePriceId, StripeSubscription, StripeSubscriptionId,
+ StripePrice, StripePriceId, StripeSubscription, StripeSubscriptionId,
StripeSubscriptionTrialSettings, StripeSubscriptionTrialSettingsEndBehavior,
StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod, StripeTaxIdCollection,
UpdateSubscriptionItems, UpdateSubscriptionParams,
@@ -30,8 +30,6 @@ pub struct StripeBilling {
#[derive(Default)]
struct StripeBillingState {
- meters_by_event_name: HashMap,
- price_ids_by_meter_id: HashMap,
prices_by_lookup_key: HashMap,
}
@@ -60,24 +58,11 @@ impl StripeBilling {
let mut state = self.state.write().await;
- let (meters, prices) =
- futures::try_join!(self.client.list_meters(), self.client.list_prices())?;
-
- for meter in meters {
- state
- .meters_by_event_name
- .insert(meter.event_name.clone(), meter);
- }
+ let prices = self.client.list_prices().await?;
for price in prices {
if let Some(lookup_key) = price.lookup_key.clone() {
- state.prices_by_lookup_key.insert(lookup_key, price.clone());
- }
-
- if let Some(recurring) = price.recurring {
- if let Some(meter) = recurring.meter {
- state.price_ids_by_meter_id.insert(meter, price.id);
- }
+ state.prices_by_lookup_key.insert(lookup_key, price);
}
}
diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs
index 1966d1a389..e11242cb15 100644
--- a/crates/copilot/src/copilot.rs
+++ b/crates/copilot/src/copilot.rs
@@ -6,6 +6,7 @@ mod sign_in;
use crate::sign_in::initiate_sign_in_within_workspace;
use ::fs::Fs;
use anyhow::{Context as _, Result, anyhow};
+use client::DisableAiSettings;
use collections::{HashMap, HashSet};
use command_palette_hooks::CommandPaletteFilter;
use futures::{Future, FutureExt, TryFutureExt, channel::oneshot, future::Shared};
@@ -25,6 +26,7 @@ use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use request::StatusNotification;
use serde_json::json;
+use settings::Settings;
use settings::SettingsStore;
use sign_in::{reinstall_and_sign_in_within_workspace, sign_out_within_workspace};
use std::collections::hash_map::Entry;
@@ -93,26 +95,34 @@ pub fn init(
let copilot_auth_action_types = [TypeId::of::()];
let copilot_no_auth_action_types = [TypeId::of::()];
let status = handle.read(cx).status();
+
+ let is_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
let filter = CommandPaletteFilter::global_mut(cx);
- match status {
- Status::Disabled => {
- filter.hide_action_types(&copilot_action_types);
- filter.hide_action_types(&copilot_auth_action_types);
- filter.hide_action_types(&copilot_no_auth_action_types);
- }
- Status::Authorized => {
- filter.hide_action_types(&copilot_no_auth_action_types);
- filter.show_action_types(
- copilot_action_types
- .iter()
- .chain(&copilot_auth_action_types),
- );
- }
- _ => {
- filter.hide_action_types(&copilot_action_types);
- filter.hide_action_types(&copilot_auth_action_types);
- filter.show_action_types(copilot_no_auth_action_types.iter());
+ if is_ai_disabled {
+ filter.hide_action_types(&copilot_action_types);
+ filter.hide_action_types(&copilot_auth_action_types);
+ filter.hide_action_types(&copilot_no_auth_action_types);
+ } else {
+ match status {
+ Status::Disabled => {
+ filter.hide_action_types(&copilot_action_types);
+ filter.hide_action_types(&copilot_auth_action_types);
+ filter.hide_action_types(&copilot_no_auth_action_types);
+ }
+ Status::Authorized => {
+ filter.hide_action_types(&copilot_no_auth_action_types);
+ filter.show_action_types(
+ copilot_action_types
+ .iter()
+ .chain(&copilot_auth_action_types),
+ );
+ }
+ _ => {
+ filter.hide_action_types(&copilot_action_types);
+ filter.hide_action_types(&copilot_auth_action_types);
+ filter.show_action_types(copilot_no_auth_action_types.iter());
+ }
}
}
})
diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs
index c761e0d69c..85495a2611 100644
--- a/crates/editor/src/display_map/block_map.rs
+++ b/crates/editor/src/display_map/block_map.rs
@@ -524,10 +524,10 @@ 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() {
if transform.summary.input_rows > 0
- && cursor.end(&()) == old_start
+ && cursor.end() == old_start
&& transform
.block
.as_ref()
@@ -535,13 +535,13 @@ impl BlockMap {
{
// Preserve the transform (push and next)
new_transforms.push(transform.clone(), &());
- cursor.next(&());
+ cursor.next();
// Preserve below blocks at end of edit
while let Some(transform) = cursor.item() {
if transform.block.as_ref().map_or(false, |b| b.place_below()) {
new_transforms.push(transform.clone(), &());
- cursor.next(&());
+ cursor.next();
} else {
break;
}
@@ -579,8 +579,8 @@ impl BlockMap {
let mut new_end = WrapRow(edit.new.end);
loop {
// Seek to the transform starting at or after the end of the edit
- cursor.seek(&old_end, Bias::Left, &());
- cursor.next(&());
+ cursor.seek(&old_end, Bias::Left);
+ cursor.next();
// Extend edit to the end of the discarded transform so it is reconstructed in full
let transform_rows_after_edit = cursor.start().0 - old_end.0;
@@ -592,8 +592,8 @@ impl BlockMap {
if next_edit.old.start <= cursor.start().0 {
old_end = WrapRow(next_edit.old.end);
new_end = WrapRow(next_edit.new.end);
- cursor.seek(&old_end, Bias::Left, &());
- cursor.next(&());
+ cursor.seek(&old_end, Bias::Left);
+ cursor.next();
edits.next();
} else {
break;
@@ -608,7 +608,7 @@ impl BlockMap {
// Discard below blocks at the end of the edit. They'll be reconstructed.
while let Some(transform) = cursor.item() {
if transform.block.as_ref().map_or(false, |b| b.place_below()) {
- cursor.next(&());
+ cursor.next();
} else {
break;
}
@@ -720,7 +720,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
@@ -971,7 +971,7 @@ impl BlockMapReader<'_> {
);
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
- cursor.seek(&start_wrap_row, Bias::Left, &());
+ cursor.seek(&start_wrap_row, Bias::Left);
while let Some(transform) = cursor.item() {
if cursor.start().0 > end_wrap_row {
break;
@@ -982,7 +982,7 @@ impl BlockMapReader<'_> {
return Some(cursor.start().1);
}
}
- cursor.next(&());
+ cursor.next();
}
None
@@ -1293,7 +1293,7 @@ impl BlockSnapshot {
let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
- cursor.seek(&BlockRow(rows.start), Bias::Right, &());
+ cursor.seek(&BlockRow(rows.start), Bias::Right);
let transform_output_start = cursor.start().0.0;
let transform_input_start = cursor.start().1.0;
@@ -1325,7 +1325,7 @@ impl BlockSnapshot {
pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
- cursor.seek(&start_row, Bias::Right, &());
+ cursor.seek(&start_row, Bias::Right);
let (output_start, input_start) = cursor.start();
let overshoot = if cursor
.item()
@@ -1346,9 +1346,9 @@ impl BlockSnapshot {
pub fn blocks_in_range(&self, rows: Range) -> impl Iterator
- {
let mut cursor = self.transforms.cursor::(&());
- cursor.seek(&BlockRow(rows.start), Bias::Left, &());
- while cursor.start().0 < rows.start && cursor.end(&()).0 <= rows.start {
- cursor.next(&());
+ cursor.seek(&BlockRow(rows.start), Bias::Left);
+ while cursor.start().0 < rows.start && cursor.end().0 <= rows.start {
+ cursor.next();
}
std::iter::from_fn(move || {
@@ -1364,10 +1364,10 @@ impl BlockSnapshot {
break;
}
if let Some(block) = &transform.block {
- cursor.next(&());
+ cursor.next();
return Some((start_row, block));
} else {
- cursor.next(&());
+ cursor.next();
}
}
None
@@ -1377,7 +1377,7 @@ impl BlockSnapshot {
pub fn sticky_header_excerpt(&self, position: f32) -> Option> {
let top_row = position as u32;
let mut cursor = self.transforms.cursor::(&());
- cursor.seek(&BlockRow(top_row), Bias::Right, &());
+ cursor.seek(&BlockRow(top_row), Bias::Right);
while let Some(transform) = cursor.item() {
match &transform.block {
@@ -1386,7 +1386,7 @@ impl BlockSnapshot {
}
Some(block) if block.is_buffer_header() => return None,
_ => {
- cursor.prev(&());
+ cursor.prev();
continue;
}
}
@@ -1414,7 +1414,7 @@ impl BlockSnapshot {
let wrap_row = WrapRow(wrap_point.row());
let mut cursor = self.transforms.cursor::(&());
- cursor.seek(&wrap_row, Bias::Left, &());
+ cursor.seek(&wrap_row, Bias::Left);
while let Some(transform) = cursor.item() {
if let Some(block) = transform.block.as_ref() {
@@ -1425,7 +1425,7 @@ impl BlockSnapshot {
break;
}
- cursor.next(&());
+ cursor.next();
}
None
@@ -1442,7 +1442,7 @@ impl BlockSnapshot {
pub fn longest_row_in_range(&self, range: Range) -> BlockRow {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
- cursor.seek(&range.start, Bias::Right, &());
+ cursor.seek(&range.start, Bias::Right);
let mut longest_row = range.start;
let mut longest_row_chars = 0;
@@ -1453,7 +1453,7 @@ impl BlockSnapshot {
let wrap_start_row = input_start.0 + overshoot;
let wrap_end_row = cmp::min(
input_start.0 + (range.end.0 - output_start.0),
- cursor.end(&()).1.0,
+ cursor.end().1.0,
);
let summary = self
.wrap_snapshot
@@ -1461,12 +1461,12 @@ impl BlockSnapshot {
longest_row = BlockRow(range.start.0 + summary.longest_row);
longest_row_chars = summary.longest_row_chars;
}
- cursor.next(&());
+ cursor.next();
}
let cursor_start_row = cursor.start().0;
if range.end > cursor_start_row {
- let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right, &());
+ let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right);
if summary.longest_row_chars > longest_row_chars {
longest_row = BlockRow(cursor_start_row.0 + summary.longest_row);
longest_row_chars = summary.longest_row_chars;
@@ -1493,7 +1493,7 @@ impl BlockSnapshot {
pub(super) fn line_len(&self, row: BlockRow) -> u32 {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
- cursor.seek(&BlockRow(row.0), Bias::Right, &());
+ cursor.seek(&BlockRow(row.0), Bias::Right);
if let Some(transform) = cursor.item() {
let (output_start, input_start) = cursor.start();
let overshoot = row.0 - output_start.0;
@@ -1511,13 +1511,13 @@ impl BlockSnapshot {
pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
- cursor.seek(&row, Bias::Right, &());
+ cursor.seek(&row, Bias::Right);
cursor.item().map_or(false, |t| t.block.is_some())
}
pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
- cursor.seek(&row, Bias::Right, &());
+ cursor.seek(&row, Bias::Right);
let Some(transform) = cursor.item() else {
return false;
};
@@ -1529,7 +1529,7 @@ impl BlockSnapshot {
.wrap_snapshot
.make_wrap_point(Point::new(row.0, 0), Bias::Left);
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
- cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
+ cursor.seek(&WrapRow(wrap_point.row()), Bias::Right);
cursor.item().map_or(false, |transform| {
transform
.block
@@ -1540,17 +1540,17 @@ impl BlockSnapshot {
pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
- cursor.seek(&BlockRow(point.row), Bias::Right, &());
+ cursor.seek(&BlockRow(point.row), Bias::Right);
let max_input_row = WrapRow(self.transforms.summary().input_rows);
let mut search_left =
- (bias == Bias::Left && cursor.start().1.0 > 0) || cursor.end(&()).1 == max_input_row;
+ (bias == Bias::Left && cursor.start().1.0 > 0) || cursor.end().1 == max_input_row;
let mut reversed = false;
loop {
if let Some(transform) = cursor.item() {
let (output_start_row, input_start_row) = cursor.start();
- let (output_end_row, input_end_row) = cursor.end(&());
+ let (output_end_row, input_end_row) = cursor.end();
let output_start = Point::new(output_start_row.0, 0);
let input_start = Point::new(input_start_row.0, 0);
let input_end = Point::new(input_end_row.0, 0);
@@ -1584,23 +1584,23 @@ impl BlockSnapshot {
}
if search_left {
- cursor.prev(&());
+ cursor.prev();
} else {
- cursor.next(&());
+ cursor.next();
}
} else if reversed {
return self.max_point();
} else {
reversed = true;
search_left = !search_left;
- cursor.seek(&BlockRow(point.row), Bias::Right, &());
+ cursor.seek(&BlockRow(point.row), Bias::Right);
}
}
}
pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
- cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
+ cursor.seek(&WrapRow(wrap_point.row()), Bias::Right);
if let Some(transform) = cursor.item() {
if transform.block.is_some() {
BlockPoint::new(cursor.start().1.0, 0)
@@ -1618,7 +1618,7 @@ impl BlockSnapshot {
pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
- cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
+ cursor.seek(&BlockRow(block_point.row), Bias::Right);
if let Some(transform) = cursor.item() {
match transform.block.as_ref() {
Some(block) => {
@@ -1630,7 +1630,7 @@ impl BlockSnapshot {
} else if bias == Bias::Left {
WrapPoint::new(cursor.start().1.0, 0)
} else {
- let wrap_row = cursor.end(&()).1.0 - 1;
+ let wrap_row = cursor.end().1.0 - 1;
WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
}
}
@@ -1650,14 +1650,14 @@ impl BlockChunks<'_> {
/// Go to the next transform
fn advance(&mut self) {
self.input_chunk = Chunk::default();
- self.transforms.next(&());
+ self.transforms.next();
while let Some(transform) = self.transforms.item() {
if transform
.block
.as_ref()
.map_or(false, |block| block.height() == 0)
{
- self.transforms.next(&());
+ self.transforms.next();
} else {
break;
}
@@ -1672,7 +1672,7 @@ impl BlockChunks<'_> {
let start_output_row = self.transforms.start().0.0;
if start_output_row < self.max_output_row {
let end_input_row = cmp::min(
- self.transforms.end(&()).1.0,
+ self.transforms.end().1.0,
start_input_row + (self.max_output_row - start_output_row),
);
self.input_chunks.seek(start_input_row..end_input_row);
@@ -1696,7 +1696,7 @@ impl<'a> Iterator for BlockChunks<'a> {
let transform = self.transforms.item()?;
if transform.block.is_some() {
let block_start = self.transforms.start().0.0;
- let mut block_end = self.transforms.end(&()).0.0;
+ let mut block_end = self.transforms.end().0.0;
self.advance();
if self.transforms.item().is_none() {
block_end -= 1;
@@ -1731,7 +1731,7 @@ impl<'a> Iterator for BlockChunks<'a> {
}
}
- let transform_end = self.transforms.end(&()).0.0;
+ let transform_end = self.transforms.end().0.0;
let (prefix_rows, prefix_bytes) =
offset_for_row(self.input_chunk.text, transform_end - self.output_row);
self.output_row += prefix_rows;
@@ -1770,15 +1770,15 @@ impl Iterator for BlockRows<'_> {
self.started = true;
}
- if self.output_row.0 >= self.transforms.end(&()).0.0 {
- self.transforms.next(&());
+ if self.output_row.0 >= self.transforms.end().0.0 {
+ self.transforms.next();
while let Some(transform) = self.transforms.item() {
if transform
.block
.as_ref()
.map_or(false, |block| block.height() == 0)
{
- self.transforms.next(&());
+ self.transforms.next();
} else {
break;
}
diff --git a/crates/editor/src/display_map/crease_map.rs b/crates/editor/src/display_map/crease_map.rs
index e6fe4270ec..bdac982fa7 100644
--- a/crates/editor/src/display_map/crease_map.rs
+++ b/crates/editor/src/display_map/crease_map.rs
@@ -52,15 +52,15 @@ impl CreaseSnapshot {
) -> Option<&'a Crease> {
let start = snapshot.anchor_before(Point::new(row.0, 0));
let mut cursor = self.creases.cursor::(snapshot);
- cursor.seek(&start, Bias::Left, snapshot);
+ cursor.seek(&start, Bias::Left);
while let Some(item) = cursor.item() {
match Ord::cmp(&item.crease.range().start.to_point(snapshot).row, &row.0) {
- Ordering::Less => cursor.next(snapshot),
+ Ordering::Less => cursor.next(),
Ordering::Equal => {
if item.crease.range().start.is_valid(snapshot) {
return Some(&item.crease);
} else {
- cursor.next(snapshot);
+ cursor.next();
}
}
Ordering::Greater => break,
@@ -76,11 +76,11 @@ impl CreaseSnapshot {
) -> impl 'a + Iterator
- > {
let start = snapshot.anchor_before(Point::new(range.start.0, 0));
let mut cursor = self.creases.cursor::(snapshot);
- cursor.seek(&start, Bias::Left, snapshot);
+ cursor.seek(&start, Bias::Left);
std::iter::from_fn(move || {
while let Some(item) = cursor.item() {
- cursor.next(snapshot);
+ cursor.next();
let crease_range = item.crease.range();
let crease_start = crease_range.start.to_point(snapshot);
let crease_end = crease_range.end.to_point(snapshot);
@@ -102,13 +102,13 @@ impl CreaseSnapshot {
let mut cursor = self.creases.cursor::(snapshot);
let mut results = Vec::new();
- cursor.next(snapshot);
+ cursor.next();
while let Some(item) = cursor.item() {
let crease_range = item.crease.range();
let start_point = crease_range.start.to_point(snapshot);
let end_point = crease_range.end.to_point(snapshot);
results.push((item.id, start_point..end_point));
- cursor.next(snapshot);
+ cursor.next();
}
results
@@ -298,7 +298,7 @@ impl CreaseMap {
let mut cursor = self.snapshot.creases.cursor::(snapshot);
for crease in creases {
let crease_range = crease.range().clone();
- new_creases.append(cursor.slice(&crease_range, Bias::Left, snapshot), snapshot);
+ new_creases.append(cursor.slice(&crease_range, Bias::Left), snapshot);
let id = self.next_id;
self.next_id.0 += 1;
@@ -306,7 +306,7 @@ impl CreaseMap {
new_creases.push(CreaseItem { crease, id }, snapshot);
new_ids.push(id);
}
- new_creases.append(cursor.suffix(snapshot), snapshot);
+ new_creases.append(cursor.suffix(), snapshot);
new_creases
};
new_ids
@@ -332,9 +332,9 @@ impl CreaseMap {
let mut cursor = self.snapshot.creases.cursor::(snapshot);
for (id, range) in &removals {
- new_creases.append(cursor.slice(range, Bias::Left, snapshot), snapshot);
+ new_creases.append(cursor.slice(range, Bias::Left), snapshot);
while let Some(item) = cursor.item() {
- cursor.next(snapshot);
+ cursor.next();
if item.id == *id {
break;
} else {
@@ -343,7 +343,7 @@ impl CreaseMap {
}
}
- new_creases.append(cursor.suffix(snapshot), snapshot);
+ new_creases.append(cursor.suffix(), snapshot);
new_creases
};
diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs
index f37e7063e7..829d34ff58 100644
--- a/crates/editor/src/display_map/fold_map.rs
+++ b/crates/editor/src/display_map/fold_map.rs
@@ -99,7 +99,7 @@ impl FoldPoint {
pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint {
let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
- cursor.seek(&self, Bias::Right, &());
+ cursor.seek(&self, Bias::Right);
let overshoot = self.0 - cursor.start().0.0;
InlayPoint(cursor.start().1.0 + overshoot)
}
@@ -108,7 +108,7 @@ impl FoldPoint {
let mut cursor = snapshot
.transforms
.cursor::<(FoldPoint, TransformSummary)>(&());
- cursor.seek(&self, Bias::Right, &());
+ cursor.seek(&self, Bias::Right);
let overshoot = self.0 - cursor.start().1.output.lines;
let mut offset = cursor.start().1.output.len;
if !overshoot.is_zero() {
@@ -187,10 +187,10 @@ impl FoldMapWriter<'_> {
width: None,
},
);
- new_tree.append(cursor.slice(&fold.range, Bias::Right, buffer), buffer);
+ new_tree.append(cursor.slice(&fold.range, Bias::Right), buffer);
new_tree.push(fold, buffer);
}
- new_tree.append(cursor.suffix(buffer), buffer);
+ new_tree.append(cursor.suffix(), buffer);
new_tree
};
@@ -252,7 +252,7 @@ impl FoldMapWriter<'_> {
fold_ixs_to_delete.push(*folds_cursor.start());
self.0.snapshot.fold_metadata_by_id.remove(&fold.id);
}
- folds_cursor.next(buffer);
+ folds_cursor.next();
}
}
@@ -263,10 +263,10 @@ impl FoldMapWriter<'_> {
let mut cursor = self.0.snapshot.folds.cursor::(buffer);
let mut folds = SumTree::new(buffer);
for fold_ix in fold_ixs_to_delete {
- folds.append(cursor.slice(&fold_ix, Bias::Right, buffer), buffer);
- cursor.next(buffer);
+ folds.append(cursor.slice(&fold_ix, Bias::Right), buffer);
+ cursor.next();
}
- folds.append(cursor.suffix(buffer), buffer);
+ folds.append(cursor.suffix(), buffer);
folds
};
@@ -412,7 +412,7 @@ impl FoldMap {
let mut new_transforms = SumTree::::default();
let mut cursor = self.snapshot.transforms.cursor::(&());
- cursor.seek(&InlayOffset(0), Bias::Right, &());
+ cursor.seek(&InlayOffset(0), Bias::Right);
while let Some(mut edit) = inlay_edits_iter.next() {
if let Some(item) = cursor.item() {
@@ -421,19 +421,19 @@ impl FoldMap {
|transform| {
if !transform.is_fold() {
transform.summary.add_summary(&item.summary, &());
- cursor.next(&());
+ 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();
- cursor.seek(&edit.old.end, Bias::Right, &());
- cursor.next(&());
+ cursor.seek(&edit.old.end, Bias::Right);
+ cursor.next();
let mut delta = edit.new_len().0 as isize - edit.old_len().0 as isize;
loop {
@@ -449,8 +449,8 @@ impl FoldMap {
if next_edit.old.end >= edit.old.end {
edit.old.end = next_edit.old.end;
- cursor.seek(&edit.old.end, Bias::Right, &());
- cursor.next(&());
+ cursor.seek(&edit.old.end, Bias::Right);
+ cursor.next();
}
} else {
break;
@@ -467,11 +467,7 @@ impl FoldMap {
.snapshot
.folds
.cursor::(&inlay_snapshot.buffer);
- folds_cursor.seek(
- &FoldRange(anchor..Anchor::max()),
- Bias::Left,
- &inlay_snapshot.buffer,
- );
+ folds_cursor.seek(&FoldRange(anchor..Anchor::max()), Bias::Left);
let mut folds = iter::from_fn({
let inlay_snapshot = &inlay_snapshot;
@@ -485,7 +481,7 @@ impl FoldMap {
..inlay_snapshot.to_inlay_offset(buffer_end),
)
});
- folds_cursor.next(&inlay_snapshot.buffer);
+ folds_cursor.next();
item
}
})
@@ -558,7 +554,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);
@@ -575,31 +571,31 @@ impl FoldMap {
let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>(&());
for mut edit in inlay_edits {
- old_transforms.seek(&edit.old.start, Bias::Left, &());
+ old_transforms.seek(&edit.old.start, Bias::Left);
if old_transforms.item().map_or(false, |t| t.is_fold()) {
edit.old.start = old_transforms.start().0;
}
let old_start =
old_transforms.start().1.0 + (edit.old.start - old_transforms.start().0).0;
- old_transforms.seek_forward(&edit.old.end, Bias::Right, &());
+ old_transforms.seek_forward(&edit.old.end, Bias::Right);
if old_transforms.item().map_or(false, |t| t.is_fold()) {
- old_transforms.next(&());
+ old_transforms.next();
edit.old.end = old_transforms.start().0;
}
let old_end =
old_transforms.start().1.0 + (edit.old.end - old_transforms.start().0).0;
- new_transforms.seek(&edit.new.start, Bias::Left, &());
+ new_transforms.seek(&edit.new.start, Bias::Left);
if new_transforms.item().map_or(false, |t| t.is_fold()) {
edit.new.start = new_transforms.start().0;
}
let new_start =
new_transforms.start().1.0 + (edit.new.start - new_transforms.start().0).0;
- new_transforms.seek_forward(&edit.new.end, Bias::Right, &());
+ new_transforms.seek_forward(&edit.new.end, Bias::Right);
if new_transforms.item().map_or(false, |t| t.is_fold()) {
- new_transforms.next(&());
+ new_transforms.next();
edit.new.end = new_transforms.start().0;
}
let new_end =
@@ -656,10 +652,10 @@ impl FoldSnapshot {
let mut summary = TextSummary::default();
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
- cursor.seek(&range.start, Bias::Right, &());
+ cursor.seek(&range.start, Bias::Right);
if let Some(transform) = cursor.item() {
let start_in_transform = range.start.0 - cursor.start().0.0;
- let end_in_transform = cmp::min(range.end, cursor.end(&()).0).0 - cursor.start().0.0;
+ let end_in_transform = cmp::min(range.end, cursor.end().0).0 - cursor.start().0.0;
if let Some(placeholder) = transform.placeholder.as_ref() {
summary = TextSummary::from(
&placeholder.text
@@ -678,10 +674,10 @@ impl FoldSnapshot {
}
}
- if range.end > cursor.end(&()).0 {
- cursor.next(&());
+ if range.end > cursor.end().0 {
+ cursor.next();
summary += &cursor
- .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
+ .summary::<_, TransformSummary>(&range.end, Bias::Right)
.output;
if let Some(transform) = cursor.item() {
let end_in_transform = range.end.0 - cursor.start().0.0;
@@ -705,19 +701,16 @@ impl FoldSnapshot {
pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(&());
- cursor.seek(&point, Bias::Right, &());
+ cursor.seek(&point, Bias::Right);
if cursor.item().map_or(false, |t| t.is_fold()) {
if bias == Bias::Left || point == cursor.start().0 {
cursor.start().1
} else {
- cursor.end(&()).1
+ cursor.end().1
}
} else {
let overshoot = point.0 - cursor.start().0.0;
- FoldPoint(cmp::min(
- cursor.start().1.0 + overshoot,
- cursor.end(&()).1.0,
- ))
+ FoldPoint(cmp::min(cursor.start().1.0 + overshoot, cursor.end().1.0))
}
}
@@ -742,7 +735,7 @@ impl FoldSnapshot {
let fold_point = FoldPoint::new(start_row, 0);
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
- cursor.seek(&fold_point, Bias::Left, &());
+ cursor.seek(&fold_point, Bias::Left);
let overshoot = fold_point.0 - cursor.start().0.0;
let inlay_point = InlayPoint(cursor.start().1.0 + overshoot);
@@ -773,7 +766,7 @@ impl FoldSnapshot {
let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false);
iter::from_fn(move || {
let item = folds.item();
- folds.next(&self.inlay_snapshot.buffer);
+ folds.next();
item
})
}
@@ -785,7 +778,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::(&());
- cursor.seek(&inlay_offset, Bias::Right, &());
+ cursor.seek(&inlay_offset, Bias::Right);
cursor.item().map_or(false, |t| t.placeholder.is_some())
}
@@ -794,7 +787,7 @@ impl FoldSnapshot {
.inlay_snapshot
.to_inlay_point(Point::new(buffer_row.0, 0));
let mut cursor = self.transforms.cursor::(&());
- cursor.seek(&inlay_point, Bias::Right, &());
+ cursor.seek(&inlay_point, Bias::Right);
loop {
match cursor.item() {
Some(transform) => {
@@ -808,11 +801,11 @@ impl FoldSnapshot {
None => return false,
}
- if cursor.end(&()).row() == inlay_point.row() {
- cursor.next(&());
+ if cursor.end().row() == inlay_point.row() {
+ cursor.next();
} else {
inlay_point.0 += Point::new(1, 0);
- cursor.seek(&inlay_point, Bias::Right, &());
+ cursor.seek(&inlay_point, Bias::Right);
}
}
}
@@ -824,14 +817,14 @@ impl FoldSnapshot {
highlights: Highlights<'a>,
) -> FoldChunks<'a> {
let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(&());
- transform_cursor.seek(&range.start, Bias::Right, &());
+ transform_cursor.seek(&range.start, Bias::Right);
let inlay_start = {
let overshoot = range.start.0 - transform_cursor.start().0.0;
transform_cursor.start().1 + InlayOffset(overshoot)
};
- let transform_end = transform_cursor.end(&());
+ let transform_end = transform_cursor.end();
let inlay_end = if transform_cursor
.item()
@@ -879,14 +872,14 @@ impl FoldSnapshot {
pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint {
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
- cursor.seek(&point, Bias::Right, &());
+ cursor.seek(&point, Bias::Right);
if let Some(transform) = cursor.item() {
let transform_start = cursor.start().0.0;
if transform.placeholder.is_some() {
if point.0 == transform_start || matches!(bias, Bias::Left) {
FoldPoint(transform_start)
} else {
- FoldPoint(cursor.end(&()).0.0)
+ FoldPoint(cursor.end().0.0)
}
} else {
let overshoot = InlayPoint(point.0 - transform_start);
@@ -945,7 +938,7 @@ fn intersecting_folds<'a>(
start_cmp == Ordering::Less && end_cmp == Ordering::Greater
}
});
- cursor.next(buffer);
+ cursor.next();
cursor
}
@@ -1211,7 +1204,7 @@ pub struct FoldRows<'a> {
impl FoldRows<'_> {
pub(crate) fn seek(&mut self, row: u32) {
let fold_point = FoldPoint::new(row, 0);
- self.cursor.seek(&fold_point, Bias::Left, &());
+ self.cursor.seek(&fold_point, Bias::Left);
let overshoot = fold_point.0 - self.cursor.start().0.0;
let inlay_point = InlayPoint(self.cursor.start().1.0 + overshoot);
self.input_rows.seek(inlay_point.row());
@@ -1224,8 +1217,8 @@ impl Iterator for FoldRows<'_> {
fn next(&mut self) -> Option {
let mut traversed_fold = false;
- while self.fold_point > self.cursor.end(&()).0 {
- self.cursor.next(&());
+ while self.fold_point > self.cursor.end().0 {
+ self.cursor.next();
traversed_fold = true;
if self.cursor.item().is_none() {
break;
@@ -1330,14 +1323,14 @@ pub struct FoldChunks<'a> {
impl FoldChunks<'_> {
pub(crate) fn seek(&mut self, range: Range) {
- self.transform_cursor.seek(&range.start, Bias::Right, &());
+ self.transform_cursor.seek(&range.start, Bias::Right);
let inlay_start = {
let overshoot = range.start.0 - self.transform_cursor.start().0.0;
self.transform_cursor.start().1 + InlayOffset(overshoot)
};
- let transform_end = self.transform_cursor.end(&());
+ let transform_end = self.transform_cursor.end();
let inlay_end = if self
.transform_cursor
@@ -1376,10 +1369,10 @@ impl<'a> Iterator for FoldChunks<'a> {
self.inlay_chunk.take();
self.inlay_offset += InlayOffset(transform.summary.input.len);
- while self.inlay_offset >= self.transform_cursor.end(&()).1
+ while self.inlay_offset >= self.transform_cursor.end().1
&& self.transform_cursor.item().is_some()
{
- self.transform_cursor.next(&());
+ self.transform_cursor.next();
}
self.output_offset.0 += placeholder.text.len();
@@ -1396,7 +1389,7 @@ impl<'a> Iterator for FoldChunks<'a> {
&& self.inlay_chunks.offset() != self.inlay_offset
{
let transform_start = self.transform_cursor.start();
- let transform_end = self.transform_cursor.end(&());
+ let transform_end = self.transform_cursor.end();
let inlay_end = if self.max_output_offset < transform_end.0 {
let overshoot = self.max_output_offset.0 - transform_start.0.0;
transform_start.1 + InlayOffset(overshoot)
@@ -1417,14 +1410,14 @@ impl<'a> Iterator for FoldChunks<'a> {
if let Some((buffer_chunk_start, mut inlay_chunk)) = self.inlay_chunk.clone() {
let chunk = &mut inlay_chunk.chunk;
let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
- let transform_end = self.transform_cursor.end(&()).1;
+ let transform_end = self.transform_cursor.end().1;
let chunk_end = buffer_chunk_end.min(transform_end);
chunk.text = &chunk.text
[(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0];
if chunk_end == transform_end {
- self.transform_cursor.next(&());
+ self.transform_cursor.next();
} else if chunk_end == buffer_chunk_end {
self.inlay_chunk.take();
}
@@ -1456,7 +1449,7 @@ impl FoldOffset {
let mut cursor = snapshot
.transforms
.cursor::<(FoldOffset, TransformSummary)>(&());
- cursor.seek(&self, Bias::Right, &());
+ cursor.seek(&self, Bias::Right);
let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) {
Point::new(0, (self.0 - cursor.start().0.0) as u32)
} else {
@@ -1470,7 +1463,7 @@ impl FoldOffset {
#[cfg(test)]
pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset {
let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(&());
- cursor.seek(&self, Bias::Right, &());
+ cursor.seek(&self, Bias::Right);
let overshoot = self.0 - cursor.start().0.0;
InlayOffset(cursor.start().1.0 + overshoot)
}
diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs
index f7a696860a..a36d18ff6d 100644
--- a/crates/editor/src/display_map/inlay_map.rs
+++ b/crates/editor/src/display_map/inlay_map.rs
@@ -263,7 +263,7 @@ pub struct InlayChunk<'a> {
impl InlayChunks<'_> {
pub fn seek(&mut self, new_range: Range) {
- self.transforms.seek(&new_range.start, Bias::Right, &());
+ self.transforms.seek(&new_range.start, Bias::Right);
let buffer_range = self.snapshot.to_buffer_offset(new_range.start)
..self.snapshot.to_buffer_offset(new_range.end);
@@ -296,12 +296,12 @@ impl<'a> Iterator for InlayChunks<'a> {
*chunk = self.buffer_chunks.next().unwrap();
}
- let desired_bytes = self.transforms.end(&()).0.0 - self.output_offset.0;
+ let desired_bytes = self.transforms.end().0.0 - self.output_offset.0;
// If we're already at the transform boundary, skip to the next transform
if desired_bytes == 0 {
self.inlay_chunks = None;
- self.transforms.next(&());
+ self.transforms.next();
return self.next();
}
@@ -397,7 +397,7 @@ impl<'a> Iterator for InlayChunks<'a> {
let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| {
let start = offset_in_inlay;
- let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0)
+ let end = cmp::min(self.max_output_offset, self.transforms.end().0)
- self.transforms.start().0;
inlay.text.chunks_in_range(start.0..end.0)
});
@@ -441,9 +441,9 @@ impl<'a> Iterator for InlayChunks<'a> {
}
};
- if self.output_offset >= self.transforms.end(&()).0 {
+ if self.output_offset >= self.transforms.end().0 {
self.inlay_chunks = None;
- self.transforms.next(&());
+ self.transforms.next();
}
Some(chunk)
@@ -453,7 +453,7 @@ impl<'a> Iterator for InlayChunks<'a> {
impl InlayBufferRows<'_> {
pub fn seek(&mut self, row: u32) {
let inlay_point = InlayPoint::new(row, 0);
- self.transforms.seek(&inlay_point, Bias::Left, &());
+ self.transforms.seek(&inlay_point, Bias::Left);
let mut buffer_point = self.transforms.start().1;
let buffer_row = MultiBufferRow(if row == 0 {
@@ -487,7 +487,7 @@ impl Iterator for InlayBufferRows<'_> {
self.inlay_row += 1;
self.transforms
- .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &());
+ .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left);
Some(buffer_row)
}
@@ -556,18 +556,18 @@ impl InlayMap {
let mut cursor = snapshot.transforms.cursor::<(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() {
- if cursor.end(&()).0 == buffer_edit.old.start {
+ if cursor.end().0 == buffer_edit.old.start {
push_isomorphic(&mut new_transforms, *transform);
- cursor.next(&());
+ cursor.next();
}
}
// Remove all the inlays and transforms contained by the edit.
let old_start =
cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0);
- cursor.seek(&buffer_edit.old.end, Bias::Right, &());
+ cursor.seek(&buffer_edit.old.end, Bias::Right);
let old_end =
cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0);
@@ -625,20 +625,20 @@ impl InlayMap {
// we can push its remainder.
if buffer_edits_iter
.peek()
- .map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
+ .map_or(true, |edit| edit.old.start >= cursor.end().0)
{
let transform_start = new_transforms.summary().input.len;
let transform_end =
- buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end);
+ buffer_edit.new.end + (cursor.end().0 - buffer_edit.old.end);
push_isomorphic(
&mut new_transforms,
buffer_snapshot.text_summary_for_range(transform_start..transform_end),
);
- cursor.next(&());
+ cursor.next();
}
}
- new_transforms.append(cursor.suffix(&()), &());
+ new_transforms.append(cursor.suffix(), &());
if new_transforms.is_empty() {
new_transforms.push(Transform::Isomorphic(Default::default()), &());
}
@@ -773,7 +773,7 @@ impl InlaySnapshot {
let mut cursor = self
.transforms
.cursor::<(InlayOffset, (InlayPoint, usize))>(&());
- cursor.seek(&offset, Bias::Right, &());
+ cursor.seek(&offset, Bias::Right);
let overshoot = offset.0 - cursor.start().0.0;
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
@@ -803,7 +803,7 @@ impl InlaySnapshot {
let mut cursor = self
.transforms
.cursor::<(InlayPoint, (InlayOffset, Point))>(&());
- cursor.seek(&point, Bias::Right, &());
+ cursor.seek(&point, Bias::Right);
let overshoot = point.0 - cursor.start().0.0;
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
@@ -822,7 +822,7 @@ impl InlaySnapshot {
}
pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
- cursor.seek(&point, Bias::Right, &());
+ cursor.seek(&point, Bias::Right);
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
let overshoot = point.0 - cursor.start().0.0;
@@ -834,7 +834,7 @@ impl InlaySnapshot {
}
pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
- cursor.seek(&offset, Bias::Right, &());
+ cursor.seek(&offset, Bias::Right);
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
let overshoot = offset - cursor.start().0;
@@ -847,19 +847,19 @@ impl InlaySnapshot {
pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>(&());
- cursor.seek(&offset, Bias::Left, &());
+ cursor.seek(&offset, Bias::Left);
loop {
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
- if offset == cursor.end(&()).0 {
+ if offset == cursor.end().0 {
while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
if inlay.position.bias() == Bias::Right {
break;
} else {
- cursor.next(&());
+ cursor.next();
}
}
- return cursor.end(&()).1;
+ return cursor.end().1;
} else {
let overshoot = offset - cursor.start().0;
return InlayOffset(cursor.start().1.0 + overshoot);
@@ -867,7 +867,7 @@ impl InlaySnapshot {
}
Some(Transform::Inlay(inlay)) => {
if inlay.position.bias() == Bias::Left {
- cursor.next(&());
+ cursor.next();
} else {
return cursor.start().1;
}
@@ -880,19 +880,19 @@ impl InlaySnapshot {
}
pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(&());
- cursor.seek(&point, Bias::Left, &());
+ cursor.seek(&point, Bias::Left);
loop {
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
- if point == cursor.end(&()).0 {
+ if point == cursor.end().0 {
while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
if inlay.position.bias() == Bias::Right {
break;
} else {
- cursor.next(&());
+ cursor.next();
}
}
- return cursor.end(&()).1;
+ return cursor.end().1;
} else {
let overshoot = point - cursor.start().0;
return InlayPoint(cursor.start().1.0 + overshoot);
@@ -900,7 +900,7 @@ impl InlaySnapshot {
}
Some(Transform::Inlay(inlay)) => {
if inlay.position.bias() == Bias::Left {
- cursor.next(&());
+ cursor.next();
} else {
return cursor.start().1;
}
@@ -914,7 +914,7 @@ impl InlaySnapshot {
pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
- cursor.seek(&point, Bias::Left, &());
+ cursor.seek(&point, Bias::Left);
loop {
match cursor.item() {
Some(Transform::Isomorphic(transform)) => {
@@ -923,7 +923,7 @@ impl InlaySnapshot {
if inlay.position.bias() == Bias::Left {
return point;
} else if bias == Bias::Left {
- cursor.prev(&());
+ cursor.prev();
} else if transform.first_line_chars == 0 {
point.0 += Point::new(1, 0);
} else {
@@ -932,12 +932,12 @@ impl InlaySnapshot {
} else {
return point;
}
- } else if cursor.end(&()).0 == point {
+ } else if cursor.end().0 == point {
if let Some(Transform::Inlay(inlay)) = cursor.next_item() {
if inlay.position.bias() == Bias::Right {
return point;
} else if bias == Bias::Right {
- cursor.next(&());
+ cursor.next();
} else if point.0.column == 0 {
point.0.row -= 1;
point.0.column = self.line_len(point.0.row);
@@ -970,7 +970,7 @@ impl InlaySnapshot {
}
_ => return point,
}
- } else if point == cursor.end(&()).0 && inlay.position.bias() == Bias::Left {
+ } else if point == cursor.end().0 && inlay.position.bias() == Bias::Left {
match cursor.next_item() {
Some(Transform::Inlay(inlay)) => {
if inlay.position.bias() == Bias::Right {
@@ -983,9 +983,9 @@ impl InlaySnapshot {
if bias == Bias::Left {
point = cursor.start().0;
- cursor.prev(&());
+ cursor.prev();
} else {
- cursor.next(&());
+ cursor.next();
point = cursor.start().0;
}
}
@@ -993,9 +993,9 @@ impl InlaySnapshot {
bias = bias.invert();
if bias == Bias::Left {
point = cursor.start().0;
- cursor.prev(&());
+ cursor.prev();
} else {
- cursor.next(&());
+ cursor.next();
point = cursor.start().0;
}
}
@@ -1011,7 +1011,7 @@ impl InlaySnapshot {
let mut summary = TextSummary::default();
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
- cursor.seek(&range.start, Bias::Right, &());
+ cursor.seek(&range.start, Bias::Right);
let overshoot = range.start.0 - cursor.start().0.0;
match cursor.item() {
@@ -1019,22 +1019,22 @@ impl InlaySnapshot {
let buffer_start = cursor.start().1;
let suffix_start = buffer_start + overshoot;
let suffix_end =
- buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0.0);
+ buffer_start + (cmp::min(cursor.end().0, range.end).0 - cursor.start().0.0);
summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
- cursor.next(&());
+ cursor.next();
}
Some(Transform::Inlay(inlay)) => {
let suffix_start = overshoot;
- let suffix_end = cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0.0;
+ let suffix_end = cmp::min(cursor.end().0, range.end).0 - cursor.start().0.0;
summary = inlay.text.cursor(suffix_start).summary(suffix_end);
- cursor.next(&());
+ cursor.next();
}
None => {}
}
if range.end > cursor.start().0 {
summary += cursor
- .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
+ .summary::<_, TransformSummary>(&range.end, Bias::Right)
.output;
let overshoot = range.end.0 - cursor.start().0.0;
@@ -1060,7 +1060,7 @@ impl InlaySnapshot {
pub fn row_infos(&self, row: u32) -> InlayBufferRows<'_> {
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
let inlay_point = InlayPoint::new(row, 0);
- cursor.seek(&inlay_point, Bias::Left, &());
+ cursor.seek(&inlay_point, Bias::Left);
let max_buffer_row = self.buffer.max_row();
let mut buffer_point = cursor.start().1;
@@ -1101,7 +1101,7 @@ impl InlaySnapshot {
highlights: Highlights<'a>,
) -> InlayChunks<'a> {
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
- cursor.seek(&range.start, Bias::Right, &());
+ cursor.seek(&range.start, Bias::Right);
let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
let buffer_chunks = CustomHighlightsChunks::new(
diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs
index a29bf53882..d55577826e 100644
--- a/crates/editor/src/display_map/wrap_map.rs
+++ b/crates/editor/src/display_map/wrap_map.rs
@@ -72,7 +72,7 @@ pub struct WrapRows<'a> {
impl WrapRows<'_> {
pub(crate) fn seek(&mut self, start_row: u32) {
self.transforms
- .seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
+ .seek(&WrapPoint::new(start_row, 0), Bias::Left);
let mut input_row = self.transforms.start().1.row();
if self.transforms.item().map_or(false, |t| t.is_isomorphic()) {
input_row += start_row - self.transforms.start().0.row();
@@ -340,7 +340,7 @@ impl WrapSnapshot {
let mut tab_edits_iter = tab_edits.iter().peekable();
new_transforms =
- old_cursor.slice(&tab_edits_iter.peek().unwrap().old.start, Bias::Right, &());
+ old_cursor.slice(&tab_edits_iter.peek().unwrap().old.start, Bias::Right);
while let Some(edit) = tab_edits_iter.next() {
if edit.new.start > TabPoint::from(new_transforms.summary().input.lines) {
@@ -356,31 +356,29 @@ impl WrapSnapshot {
));
}
- old_cursor.seek_forward(&edit.old.end, Bias::Right, &());
+ old_cursor.seek_forward(&edit.old.end, Bias::Right);
if let Some(next_edit) = tab_edits_iter.peek() {
- if next_edit.old.start > old_cursor.end(&()) {
- if old_cursor.end(&()) > edit.old.end {
+ if next_edit.old.start > old_cursor.end() {
+ if old_cursor.end() > edit.old.end {
let summary = self
.tab_snapshot
- .text_summary_for_range(edit.old.end..old_cursor.end(&()));
+ .text_summary_for_range(edit.old.end..old_cursor.end());
new_transforms.push_or_extend(Transform::isomorphic(summary));
}
- old_cursor.next(&());
- new_transforms.append(
- old_cursor.slice(&next_edit.old.start, Bias::Right, &()),
- &(),
- );
+ old_cursor.next();
+ new_transforms
+ .append(old_cursor.slice(&next_edit.old.start, Bias::Right), &());
}
} else {
- if old_cursor.end(&()) > edit.old.end {
+ if old_cursor.end() > edit.old.end {
let summary = self
.tab_snapshot
- .text_summary_for_range(edit.old.end..old_cursor.end(&()));
+ .text_summary_for_range(edit.old.end..old_cursor.end());
new_transforms.push_or_extend(Transform::isomorphic(summary));
}
- old_cursor.next(&());
- new_transforms.append(old_cursor.suffix(&()), &());
+ old_cursor.next();
+ new_transforms.append(old_cursor.suffix(), &());
}
}
}
@@ -441,7 +439,6 @@ impl WrapSnapshot {
new_transforms = old_cursor.slice(
&TabPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
Bias::Right,
- &(),
);
while let Some(edit) = row_edits.next() {
@@ -516,34 +513,31 @@ impl WrapSnapshot {
}
new_transforms.extend(edit_transforms, &());
- old_cursor.seek_forward(&TabPoint::new(edit.old_rows.end, 0), Bias::Right, &());
+ old_cursor.seek_forward(&TabPoint::new(edit.old_rows.end, 0), Bias::Right);
if let Some(next_edit) = row_edits.peek() {
- if next_edit.old_rows.start > old_cursor.end(&()).row() {
- if old_cursor.end(&()) > TabPoint::new(edit.old_rows.end, 0) {
+ if next_edit.old_rows.start > old_cursor.end().row() {
+ if old_cursor.end() > TabPoint::new(edit.old_rows.end, 0) {
let summary = self.tab_snapshot.text_summary_for_range(
- TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()),
+ TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(),
);
new_transforms.push_or_extend(Transform::isomorphic(summary));
}
- old_cursor.next(&());
+ old_cursor.next();
new_transforms.append(
- old_cursor.slice(
- &TabPoint::new(next_edit.old_rows.start, 0),
- Bias::Right,
- &(),
- ),
+ old_cursor
+ .slice(&TabPoint::new(next_edit.old_rows.start, 0), Bias::Right),
&(),
);
}
} else {
- if old_cursor.end(&()) > TabPoint::new(edit.old_rows.end, 0) {
+ if old_cursor.end() > TabPoint::new(edit.old_rows.end, 0) {
let summary = self.tab_snapshot.text_summary_for_range(
- TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()),
+ TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(),
);
new_transforms.push_or_extend(Transform::isomorphic(summary));
}
- old_cursor.next(&());
- new_transforms.append(old_cursor.suffix(&()), &());
+ old_cursor.next();
+ new_transforms.append(old_cursor.suffix(), &());
}
}
}
@@ -570,19 +564,19 @@ impl WrapSnapshot {
tab_edit.new.start.0.column = 0;
tab_edit.new.end.0 += Point::new(1, 0);
- old_cursor.seek(&tab_edit.old.start, Bias::Right, &());
+ old_cursor.seek(&tab_edit.old.start, Bias::Right);
let mut old_start = old_cursor.start().output.lines;
old_start += tab_edit.old.start.0 - old_cursor.start().input.lines;
- old_cursor.seek(&tab_edit.old.end, Bias::Right, &());
+ old_cursor.seek(&tab_edit.old.end, Bias::Right);
let mut old_end = old_cursor.start().output.lines;
old_end += tab_edit.old.end.0 - old_cursor.start().input.lines;
- new_cursor.seek(&tab_edit.new.start, Bias::Right, &());
+ new_cursor.seek(&tab_edit.new.start, Bias::Right);
let mut new_start = new_cursor.start().output.lines;
new_start += tab_edit.new.start.0 - new_cursor.start().input.lines;
- new_cursor.seek(&tab_edit.new.end, Bias::Right, &());
+ new_cursor.seek(&tab_edit.new.end, Bias::Right);
let mut new_end = new_cursor.start().output.lines;
new_end += tab_edit.new.end.0 - new_cursor.start().input.lines;
@@ -605,7 +599,7 @@ impl WrapSnapshot {
let output_start = WrapPoint::new(rows.start, 0);
let output_end = WrapPoint::new(rows.end, 0);
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
- transforms.seek(&output_start, Bias::Right, &());
+ transforms.seek(&output_start, Bias::Right);
let mut input_start = TabPoint(transforms.start().1.0);
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
input_start.0 += output_start.0 - transforms.start().0.0;
@@ -633,7 +627,7 @@ impl WrapSnapshot {
pub fn line_len(&self, row: u32) -> u32 {
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
- cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left, &());
+ cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left);
if cursor
.item()
.map_or(false, |transform| transform.is_isomorphic())
@@ -658,10 +652,10 @@ impl WrapSnapshot {
let end = WrapPoint::new(rows.end, 0);
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
- cursor.seek(&start, Bias::Right, &());
+ cursor.seek(&start, Bias::Right);
if let Some(transform) = cursor.item() {
let start_in_transform = start.0 - cursor.start().0.0;
- let end_in_transform = cmp::min(end, cursor.end(&()).0).0 - cursor.start().0.0;
+ let end_in_transform = cmp::min(end, cursor.end().0).0 - cursor.start().0.0;
if transform.is_isomorphic() {
let tab_start = TabPoint(cursor.start().1.0 + start_in_transform);
let tab_end = TabPoint(cursor.start().1.0 + end_in_transform);
@@ -678,12 +672,12 @@ impl WrapSnapshot {
};
}
- cursor.next(&());
+ cursor.next();
}
if rows.end > cursor.start().0.row() {
summary += &cursor
- .summary::<_, TransformSummary>(&WrapPoint::new(rows.end, 0), Bias::Right, &())
+ .summary::<_, TransformSummary>(&WrapPoint::new(rows.end, 0), Bias::Right)
.output;
if let Some(transform) = cursor.item() {
@@ -712,7 +706,7 @@ impl WrapSnapshot {
pub fn soft_wrap_indent(&self, row: u32) -> Option {
let mut cursor = self.transforms.cursor::(&());
- cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &());
+ cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right);
cursor.item().and_then(|transform| {
if transform.is_isomorphic() {
None
@@ -728,7 +722,7 @@ impl WrapSnapshot {
pub fn row_infos(&self, start_row: u32) -> WrapRows<'_> {
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
- transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
+ transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left);
let mut input_row = transforms.start().1.row();
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
input_row += start_row - transforms.start().0.row();
@@ -748,7 +742,7 @@ impl WrapSnapshot {
pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint {
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
- cursor.seek(&point, Bias::Right, &());
+ cursor.seek(&point, Bias::Right);
let mut tab_point = cursor.start().1.0;
if cursor.item().map_or(false, |t| t.is_isomorphic()) {
tab_point += point.0 - cursor.start().0.0;
@@ -766,14 +760,14 @@ impl WrapSnapshot {
pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint {
let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>(&());
- cursor.seek(&point, Bias::Right, &());
+ 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::(&());
- cursor.seek(&point, Bias::Right, &());
+ cursor.seek(&point, Bias::Right);
if cursor.item().map_or(false, |t| !t.is_isomorphic()) {
point = *cursor.start();
*point.column_mut() -= 1;
@@ -791,16 +785,16 @@ impl WrapSnapshot {
*point.column_mut() = 0;
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
- cursor.seek(&point, Bias::Right, &());
+ cursor.seek(&point, Bias::Right);
if cursor.item().is_none() {
- cursor.prev(&());
+ cursor.prev();
}
while let Some(transform) = cursor.item() {
if transform.is_isomorphic() && cursor.start().1.column() == 0 {
- return cmp::min(cursor.end(&()).0.row(), point.row());
+ return cmp::min(cursor.end().0.row(), point.row());
} else {
- cursor.prev(&());
+ cursor.prev();
}
}
@@ -811,12 +805,12 @@ impl WrapSnapshot {
point.0 += Point::new(1, 0);
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
- cursor.seek(&point, Bias::Right, &());
+ cursor.seek(&point, Bias::Right);
while let Some(transform) = cursor.item() {
if transform.is_isomorphic() && cursor.start().1.column() == 0 {
return Some(cmp::max(cursor.start().0.row(), point.row()));
} else {
- cursor.next(&());
+ cursor.next();
}
}
@@ -889,7 +883,7 @@ impl WrapChunks<'_> {
pub(crate) fn seek(&mut self, rows: Range) {
let output_start = WrapPoint::new(rows.start, 0);
let output_end = WrapPoint::new(rows.end, 0);
- self.transforms.seek(&output_start, Bias::Right, &());
+ self.transforms.seek(&output_start, Bias::Right);
let mut input_start = TabPoint(self.transforms.start().1.0);
if self.transforms.item().map_or(false, |t| t.is_isomorphic()) {
input_start.0 += output_start.0 - self.transforms.start().0.0;
@@ -930,7 +924,7 @@ impl<'a> Iterator for WrapChunks<'a> {
}
self.output_position.0 += summary;
- self.transforms.next(&());
+ self.transforms.next();
return Some(Chunk {
text: &display_text[start_ix..end_ix],
..Default::default()
@@ -942,7 +936,7 @@ impl<'a> Iterator for WrapChunks<'a> {
}
let mut input_len = 0;
- let transform_end = self.transforms.end(&()).0;
+ let transform_end = self.transforms.end().0;
for c in self.input_chunk.text.chars() {
let char_len = c.len_utf8();
input_len += char_len;
@@ -954,7 +948,7 @@ impl<'a> Iterator for WrapChunks<'a> {
}
if self.output_position >= transform_end {
- self.transforms.next(&());
+ self.transforms.next();
break;
}
}
@@ -982,7 +976,7 @@ impl Iterator for WrapRows<'_> {
self.output_row += 1;
self.transforms
- .seek_forward(&WrapPoint::new(self.output_row, 0), Bias::Left, &());
+ .seek_forward(&WrapPoint::new(self.output_row, 0), Bias::Left);
if self.transforms.item().map_or(false, |t| t.is_isomorphic()) {
self.input_buffer_row = self.input_buffer_rows.next().unwrap();
self.soft_wrapped = false;
diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs
index 4efb052c71..fbb877796c 100644
--- a/crates/editor/src/editor_tests.rs
+++ b/crates/editor/src/editor_tests.rs
@@ -9570,6 +9570,74 @@ async fn test_document_format_during_save(cx: &mut TestAppContext) {
}
}
+#[gpui::test]
+async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
+ init_test(cx, |settings| {
+ settings.defaults.ensure_final_newline_on_save = Some(false);
+ });
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_file(path!("/file.txt"), "foo".into()).await;
+
+ let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
+
+ let buffer = project
+ .update(cx, |project, cx| {
+ project.open_local_buffer(path!("/file.txt"), cx)
+ })
+ .await
+ .unwrap();
+
+ let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
+ let (editor, cx) = cx.add_window_view(|window, cx| {
+ build_editor_with_project(project.clone(), buffer, window, cx)
+ });
+ editor.update_in(cx, |editor, window, cx| {
+ editor.change_selections(SelectionEffects::default(), window, cx, |s| {
+ s.select_ranges([0..0])
+ });
+ });
+ assert!(!cx.read(|cx| editor.is_dirty(cx)));
+
+ editor.update_in(cx, |editor, window, cx| {
+ editor.handle_input("\n", window, cx)
+ });
+ cx.run_until_parked();
+ save(&editor, &project, cx).await;
+ assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
+
+ editor.update_in(cx, |editor, window, cx| {
+ editor.undo(&Default::default(), window, cx);
+ });
+ save(&editor, &project, cx).await;
+ assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
+
+ editor.update_in(cx, |editor, window, cx| {
+ editor.redo(&Default::default(), window, cx);
+ });
+ cx.run_until_parked();
+ assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
+
+ async fn save(editor: &Entity, project: &Entity, cx: &mut VisualTestContext) {
+ let save = editor
+ .update_in(cx, |editor, window, cx| {
+ editor.save(
+ SaveOptions {
+ format: true,
+ autosave: false,
+ },
+ project.clone(),
+ window,
+ cx,
+ )
+ })
+ .unwrap();
+ cx.executor().start_waiting();
+ save.await;
+ assert!(!cx.read(|cx| editor.is_dirty(cx)));
+ }
+}
+
#[gpui::test]
async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -22708,7 +22776,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
workspace::init_settings(cx);
crate::init(cx);
});
-
+ zlog::init_test();
update_test_language_settings(cx, f);
}
diff --git a/crates/editor/src/git/blame.rs b/crates/editor/src/git/blame.rs
index d4c9e37895..fc350a5a15 100644
--- a/crates/editor/src/git/blame.rs
+++ b/crates/editor/src/git/blame.rs
@@ -296,7 +296,7 @@ impl GitBlame {
let row = info
.buffer_row
.filter(|_| info.buffer_id == Some(buffer_id))?;
- cursor.seek_forward(&row, Bias::Right, &());
+ cursor.seek_forward(&row, Bias::Right);
cursor.item()?.blame.clone()
})
}
@@ -389,7 +389,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(
@@ -401,7 +401,7 @@ impl GitBlame {
);
}
- cursor.seek(&edit.old.end, Bias::Right, &());
+ cursor.seek(&edit.old.end, Bias::Right);
if !edit.new.is_empty() {
new_entries.push(
GitBlameEntry {
@@ -412,7 +412,7 @@ impl GitBlame {
);
}
- let old_end = cursor.end(&());
+ let old_end = cursor.end();
if row_edits
.peek()
.map_or(true, |next_edit| next_edit.old.start >= old_end)
@@ -421,18 +421,18 @@ impl GitBlame {
if old_end > edit.old.end {
new_entries.push(
GitBlameEntry {
- rows: cursor.end(&()) - edit.old.end,
+ rows: cursor.end() - edit.old.end,
blame: entry.blame.clone(),
},
&(),
);
}
- cursor.next(&());
+ cursor.next();
}
}
}
- new_entries.append(cursor.suffix(&()), &());
+ new_entries.append(cursor.suffix(), &());
drop(cursor);
self.buffer_snapshot = new_snapshot;
diff --git a/crates/git_ui/Cargo.toml b/crates/git_ui/Cargo.toml
index 6e04dcb656..2fb80b7e73 100644
--- a/crates/git_ui/Cargo.toml
+++ b/crates/git_ui/Cargo.toml
@@ -23,6 +23,7 @@ askpass.workspace = true
buffer_diff.workspace = true
call.workspace = true
chrono.workspace = true
+client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
component.workspace = true
diff --git a/crates/git_ui/src/commit_modal.rs b/crates/git_ui/src/commit_modal.rs
index ac3d24e3eb..b99f628806 100644
--- a/crates/git_ui/src/commit_modal.rs
+++ b/crates/git_ui/src/commit_modal.rs
@@ -1,8 +1,10 @@
use crate::branch_picker::{self, BranchList};
use crate::git_panel::{GitPanel, commit_message_editor};
+use client::DisableAiSettings;
use git::repository::CommitOptions;
use git::{Amend, Commit, GenerateCommitMessage, Signoff};
use panel::{panel_button, panel_editor_style};
+use settings::Settings;
use ui::{
ContextMenu, KeybindingHint, PopoverMenu, PopoverMenuHandle, SplitButton, Tooltip, prelude::*,
};
@@ -569,11 +571,13 @@ impl Render for CommitModal {
.on_action(cx.listener(Self::dismiss))
.on_action(cx.listener(Self::commit))
.on_action(cx.listener(Self::amend))
- .on_action(cx.listener(|this, _: &GenerateCommitMessage, _, cx| {
- this.git_panel.update(cx, |panel, cx| {
- panel.generate_commit_message(cx);
- })
- }))
+ .when(!DisableAiSettings::get_global(cx).disable_ai, |this| {
+ this.on_action(cx.listener(|this, _: &GenerateCommitMessage, _, cx| {
+ this.git_panel.update(cx, |panel, cx| {
+ panel.generate_commit_message(cx);
+ })
+ }))
+ })
.on_action(
cx.listener(|this, _: &zed_actions::git::Branch, window, cx| {
this.toggle_branch_selector(window, cx);
diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs
index e998586af4..061833a6c7 100644
--- a/crates/git_ui/src/git_panel.rs
+++ b/crates/git_ui/src/git_panel.rs
@@ -12,6 +12,7 @@ use crate::{
use agent_settings::AgentSettings;
use anyhow::Context as _;
use askpass::AskPassDelegate;
+use client::DisableAiSettings;
use db::kvp::KEY_VALUE_STORE;
use editor::{
Editor, EditorElement, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar,
@@ -53,7 +54,7 @@ use project::{
git_store::{GitStoreEvent, Repository},
};
use serde::{Deserialize, Serialize};
-use settings::{Settings as _, SettingsStore};
+use settings::{Settings, SettingsStore};
use std::future::Future;
use std::ops::Range;
use std::path::{Path, PathBuf};
@@ -464,9 +465,14 @@ impl GitPanel {
};
let mut assistant_enabled = AgentSettings::get_global(cx).enabled;
+ let mut was_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
let _settings_subscription = cx.observe_global::(move |_, cx| {
- if assistant_enabled != AgentSettings::get_global(cx).enabled {
+ let is_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
+ if assistant_enabled != AgentSettings::get_global(cx).enabled
+ || was_ai_disabled != is_ai_disabled
+ {
assistant_enabled = AgentSettings::get_global(cx).enabled;
+ was_ai_disabled = is_ai_disabled;
cx.notify();
}
});
@@ -1806,7 +1812,7 @@ impl GitPanel {
/// Generates a commit message using an LLM.
pub fn generate_commit_message(&mut self, cx: &mut Context) {
- if !self.can_commit() {
+ if !self.can_commit() || DisableAiSettings::get_global(cx).disable_ai {
return;
}
@@ -4305,8 +4311,10 @@ impl GitPanel {
}
fn current_language_model(cx: &Context<'_, GitPanel>) -> Option> {
- agent_settings::AgentSettings::get_global(cx)
- .enabled
+ let is_enabled = agent_settings::AgentSettings::get_global(cx).enabled
+ && !DisableAiSettings::get_global(cx).disable_ai;
+
+ is_enabled
.then(|| {
let ConfiguredModel { provider, model } =
LanguageModelRegistry::read_global(cx).commit_message_model()?;
@@ -5037,6 +5045,7 @@ mod tests {
language::init(cx);
editor::init(cx);
Project::init_settings(cx);
+ client::DisableAiSettings::register(cx);
crate::init(cx);
});
}
diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs
index a9ccaf7160..02b9c243fb 100644
--- a/crates/git_ui/src/git_ui.rs
+++ b/crates/git_ui/src/git_ui.rs
@@ -501,7 +501,7 @@ mod remote_button {
)
.into_any_element();
- SplitButton { left, right }
+ SplitButton::new(left, right)
}
}
diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml
index 878794647a..68c0ea89c7 100644
--- a/crates/gpui/Cargo.toml
+++ b/crates/gpui/Cargo.toml
@@ -121,7 +121,7 @@ smallvec.workspace = true
smol.workspace = true
strum.workspace = true
sum_tree.workspace = true
-taffy = "=0.5.1"
+taffy = "=0.8.3"
thiserror.workspace = true
util.workspace = true
uuid.workspace = true
diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs
index f24d38794f..328a6a4cc1 100644
--- a/crates/gpui/src/elements/list.rs
+++ b/crates/gpui/src/elements/list.rs
@@ -249,8 +249,8 @@ impl ListState {
let state = &mut *self.0.borrow_mut();
let mut old_items = state.items.cursor::(&());
- let mut new_items = old_items.slice(&Count(old_range.start), Bias::Right, &());
- old_items.seek_forward(&Count(old_range.end), Bias::Right, &());
+ let mut new_items = old_items.slice(&Count(old_range.start), Bias::Right);
+ old_items.seek_forward(&Count(old_range.end), Bias::Right);
let mut spliced_count = 0;
new_items.extend(
@@ -260,7 +260,7 @@ impl ListState {
}),
&(),
);
- new_items.append(old_items.suffix(&()), &());
+ new_items.append(old_items.suffix(), &());
drop(old_items);
state.items = new_items;
@@ -300,14 +300,14 @@ impl ListState {
let current_offset = self.logical_scroll_top();
let state = &mut *self.0.borrow_mut();
let mut cursor = state.items.cursor::(&());
- cursor.seek(&Count(current_offset.item_ix), Bias::Right, &());
+ cursor.seek(&Count(current_offset.item_ix), Bias::Right);
let start_pixel_offset = cursor.start().height + current_offset.offset_in_item;
let new_pixel_offset = (start_pixel_offset + distance).max(px(0.));
if new_pixel_offset > start_pixel_offset {
- cursor.seek_forward(&Height(new_pixel_offset), Bias::Right, &());
+ cursor.seek_forward(&Height(new_pixel_offset), Bias::Right);
} else {
- cursor.seek(&Height(new_pixel_offset), Bias::Right, &());
+ cursor.seek(&Height(new_pixel_offset), Bias::Right);
}
state.logical_scroll_top = Some(ListOffset {
@@ -343,11 +343,11 @@ impl ListState {
scroll_top.offset_in_item = px(0.);
} else {
let mut cursor = state.items.cursor::(&());
- cursor.seek(&Count(ix + 1), Bias::Right, &());
+ cursor.seek(&Count(ix + 1), Bias::Right);
let bottom = cursor.start().height + padding.top;
let goal_top = px(0.).max(bottom - height + padding.bottom);
- cursor.seek(&Height(goal_top), Bias::Left, &());
+ cursor.seek(&Height(goal_top), Bias::Left);
let start_ix = cursor.start().count;
let start_item_top = cursor.start().height;
@@ -372,11 +372,11 @@ impl ListState {
}
let mut cursor = state.items.cursor::<(Count, Height)>(&());
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
+ cursor.seek(&Count(scroll_top.item_ix), Bias::Right);
let scroll_top = cursor.start().1.0 + scroll_top.offset_in_item;
- cursor.seek_forward(&Count(ix), Bias::Right, &());
+ cursor.seek_forward(&Count(ix), Bias::Right);
if let Some(&ListItem::Measured { size, .. }) = cursor.item() {
let &(Count(count), Height(top)) = cursor.start();
if count == ix {
@@ -431,7 +431,7 @@ impl ListState {
let mut cursor = state.items.cursor::(&());
let summary: ListItemSummary =
- cursor.summary(&Count(logical_scroll_top.item_ix), Bias::Right, &());
+ cursor.summary(&Count(logical_scroll_top.item_ix), Bias::Right);
let content_height = state.items.summary().height;
let drag_offset =
// if dragging the scrollbar, we want to offset the point if the height changed
@@ -450,9 +450,9 @@ impl ListState {
impl StateInner {
fn visible_range(&self, height: Pixels, scroll_top: &ListOffset) -> Range {
let mut cursor = self.items.cursor::(&());
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
+ cursor.seek(&Count(scroll_top.item_ix), Bias::Right);
let start_y = cursor.start().height + scroll_top.offset_in_item;
- cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
+ cursor.seek_forward(&Height(start_y + height), Bias::Left);
scroll_top.item_ix..cursor.start().count + 1
}
@@ -482,7 +482,7 @@ impl StateInner {
self.logical_scroll_top = None;
} else {
let mut cursor = self.items.cursor::(&());
- cursor.seek(&Height(new_scroll_top), Bias::Right, &());
+ cursor.seek(&Height(new_scroll_top), Bias::Right);
let item_ix = cursor.start().count;
let offset_in_item = new_scroll_top - cursor.start().height;
self.logical_scroll_top = Some(ListOffset {
@@ -523,7 +523,7 @@ impl StateInner {
fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels {
let mut cursor = self.items.cursor::(&());
- cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
+ cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right);
cursor.start().height + logical_scroll_top.offset_in_item
}
@@ -553,7 +553,7 @@ impl StateInner {
let mut cursor = old_items.cursor::(&());
// Render items after the scroll top, including those in the trailing overdraw
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
+ cursor.seek(&Count(scroll_top.item_ix), Bias::Right);
for (ix, item) in cursor.by_ref().enumerate() {
let visible_height = rendered_height - scroll_top.offset_in_item;
if visible_height >= available_height + self.overdraw {
@@ -592,13 +592,13 @@ impl StateInner {
rendered_height += padding.bottom;
// Prepare to start walking upward from the item at the scroll top.
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
+ cursor.seek(&Count(scroll_top.item_ix), Bias::Right);
// If the rendered items do not fill the visible region, then adjust
// the scroll top upward.
if rendered_height - scroll_top.offset_in_item < available_height {
while rendered_height < available_height {
- cursor.prev(&());
+ cursor.prev();
if let Some(item) = cursor.item() {
let item_index = cursor.start().0;
let mut element = (self.render_item)(item_index, window, cx);
@@ -645,7 +645,7 @@ impl StateInner {
// Measure items in the leading overdraw
let mut leading_overdraw = scroll_top.offset_in_item;
while leading_overdraw < self.overdraw {
- cursor.prev(&());
+ cursor.prev();
if let Some(item) = cursor.item() {
let size = if let ListItem::Measured { size, .. } = item {
*size
@@ -666,10 +666,10 @@ impl StateInner {
let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len());
let mut cursor = old_items.cursor::(&());
- let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &());
+ let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right);
new_items.extend(measured_items, &());
- cursor.seek(&Count(measured_range.end), Bias::Right, &());
- new_items.append(cursor.suffix(&()), &());
+ cursor.seek(&Count(measured_range.end), Bias::Right);
+ new_items.append(cursor.suffix(), &());
self.items = new_items;
// If none of the visible items are focused, check if an off-screen item is focused
@@ -679,7 +679,7 @@ impl StateInner {
let mut cursor = self
.items
.filter::<_, Count>(&(), |summary| summary.has_focus_handles);
- cursor.next(&());
+ cursor.next();
while let Some(item) = cursor.item() {
if item.contains_focused(window, cx) {
let item_index = cursor.start().0;
@@ -692,7 +692,7 @@ impl StateInner {
});
break;
}
- cursor.next(&());
+ cursor.next();
}
}
@@ -741,7 +741,7 @@ impl StateInner {
});
} else if autoscroll_bounds.bottom() > bounds.bottom() {
let mut cursor = self.items.cursor::(&());
- cursor.seek(&Count(item.index), Bias::Right, &());
+ cursor.seek(&Count(item.index), Bias::Right);
let mut height = bounds.size.height - padding.top - padding.bottom;
// Account for the height of the element down until the autoscroll bottom.
@@ -749,7 +749,7 @@ impl StateInner {
// Keep decreasing the scroll top until we fill all the available space.
while height > Pixels::ZERO {
- cursor.prev(&());
+ cursor.prev();
let Some(item) = cursor.item() else { break };
let size = item.size().unwrap_or_else(|| {
@@ -806,7 +806,7 @@ impl StateInner {
self.logical_scroll_top = None;
} else {
let mut cursor = self.items.cursor::(&());
- cursor.seek(&Height(new_scroll_top), Bias::Right, &());
+ cursor.seek(&Height(new_scroll_top), Bias::Right);
let item_ix = cursor.start().count;
let offset_in_item = new_scroll_top - cursor.start().height;
diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs
index a290a132c3..cc6ebb9b08 100644
--- a/crates/gpui/src/key_dispatch.rs
+++ b/crates/gpui/src/key_dispatch.rs
@@ -50,8 +50,8 @@
/// KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane"))
///
use crate::{
- Action, ActionRegistry, App, BindingIndex, DispatchPhase, EntityId, FocusId, KeyBinding,
- KeyContext, Keymap, Keystroke, ModifiersChangedEvent, Window,
+ Action, ActionRegistry, App, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, Keymap,
+ Keystroke, ModifiersChangedEvent, Window,
};
use collections::FxHashMap;
use smallvec::SmallVec;
@@ -406,16 +406,11 @@ impl DispatchTree {
// methods, but this can't be done very cleanly since keymap must be borrowed.
let keymap = self.keymap.borrow();
keymap
- .bindings_for_action_with_indices(action)
- .filter(|(binding_index, binding)| {
- Self::binding_matches_predicate_and_not_shadowed(
- &keymap,
- *binding_index,
- &binding.keystrokes,
- context_stack,
- )
+ .bindings_for_action(action)
+ .filter(|binding| {
+ Self::binding_matches_predicate_and_not_shadowed(&keymap, &binding, context_stack)
})
- .map(|(_, binding)| binding.clone())
+ .cloned()
.collect()
}
@@ -428,28 +423,22 @@ impl DispatchTree {
) -> Option {
let keymap = self.keymap.borrow();
keymap
- .bindings_for_action_with_indices(action)
+ .bindings_for_action(action)
.rev()
- .find_map(|(binding_index, binding)| {
- let found = Self::binding_matches_predicate_and_not_shadowed(
- &keymap,
- binding_index,
- &binding.keystrokes,
- context_stack,
- );
- if found { Some(binding.clone()) } else { None }
+ .find(|binding| {
+ Self::binding_matches_predicate_and_not_shadowed(&keymap, &binding, context_stack)
})
+ .cloned()
}
fn binding_matches_predicate_and_not_shadowed(
keymap: &Keymap,
- binding_index: BindingIndex,
- keystrokes: &[Keystroke],
+ binding: &KeyBinding,
context_stack: &[KeyContext],
) -> bool {
- let (bindings, _) = keymap.bindings_for_input_with_indices(&keystrokes, context_stack);
- if let Some((highest_precedence_index, _)) = bindings.iter().next() {
- binding_index == *highest_precedence_index
+ let (bindings, _) = keymap.bindings_for_input(&binding.keystrokes, context_stack);
+ if let Some(found) = bindings.iter().next() {
+ found.action.partial_eq(binding.action.as_ref())
} else {
false
}
diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs
index 174dbc80f0..83d7479a04 100644
--- a/crates/gpui/src/keymap.rs
+++ b/crates/gpui/src/keymap.rs
@@ -5,7 +5,7 @@ pub use binding::*;
pub use context::*;
use crate::{Action, Keystroke, is_no_action};
-use collections::HashMap;
+use collections::{HashMap, HashSet};
use smallvec::SmallVec;
use std::any::TypeId;
@@ -77,15 +77,6 @@ impl Keymap {
&'a self,
action: &'a dyn Action,
) -> impl 'a + DoubleEndedIterator
- {
- self.bindings_for_action_with_indices(action)
- .map(|(_, binding)| binding)
- }
-
- /// Like `bindings_for_action_with_indices`, but also returns the binding indices.
- pub fn bindings_for_action_with_indices<'a>(
- &'a self,
- action: &'a dyn Action,
- ) -> impl 'a + DoubleEndedIterator
- {
let action_id = action.type_id();
let binding_indices = self
.binding_indices_by_action_id
@@ -118,7 +109,7 @@ impl Keymap {
}
}
- Some((BindingIndex(*ix), binding))
+ Some(binding)
})
}
@@ -153,90 +144,53 @@ impl Keymap {
input: &[Keystroke],
context_stack: &[KeyContext],
) -> (SmallVec<[KeyBinding; 1]>, bool) {
- let (bindings, pending) = self.bindings_for_input_with_indices(input, context_stack);
- let bindings = bindings
- .into_iter()
- .map(|(_, binding)| binding)
- .collect::>();
- (bindings, pending)
- }
+ let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new();
+ let mut pending_bindings = SmallVec::<[(BindingIndex, &KeyBinding); 1]>::new();
- /// Like `bindings_for_input`, but also returns the binding indices.
- pub fn bindings_for_input_with_indices(
- &self,
- input: &[Keystroke],
- context_stack: &[KeyContext],
- ) -> (SmallVec<[(BindingIndex, KeyBinding); 1]>, bool) {
- let mut possibilities = self
- .bindings()
- .enumerate()
- .rev()
- .filter_map(|(ix, binding)| {
- let depth = self.binding_enabled(binding, &context_stack)?;
- let pending = binding.match_keystrokes(input)?;
- Some((depth, BindingIndex(ix), binding, pending))
- })
- .collect::>();
- possibilities.sort_by(|(depth_a, ix_a, _, _), (depth_b, ix_b, _, _)| {
+ for (ix, binding) in self.bindings().enumerate().rev() {
+ let Some(depth) = self.binding_enabled(binding, &context_stack) else {
+ continue;
+ };
+ let Some(pending) = binding.match_keystrokes(input) else {
+ continue;
+ };
+
+ if !pending {
+ matched_bindings.push((depth, BindingIndex(ix), binding));
+ } else {
+ pending_bindings.push((BindingIndex(ix), binding));
+ }
+ }
+
+ matched_bindings.sort_by(|(depth_a, ix_a, _), (depth_b, ix_b, _)| {
depth_b.cmp(depth_a).then(ix_b.cmp(ix_a))
});
- let mut bindings: SmallVec<[(BindingIndex, KeyBinding, usize); 1]> = SmallVec::new();
-
- // (pending, is_no_action, depth, keystrokes)
- let mut pending_info_opt: Option<(bool, bool, usize, &[Keystroke])> = None;
-
- 'outer: for (depth, binding_index, binding, pending) in possibilities {
- let is_no_action = is_no_action(&*binding.action);
- // We only want to consider a binding pending if it has an action
- // This, however, means that if we have both a NoAction binding and a binding
- // with an action at the same depth, we should still set is_pending to true.
- if let Some(pending_info) = pending_info_opt.as_mut() {
- let (already_pending, pending_is_no_action, pending_depth, pending_keystrokes) =
- *pending_info;
-
- // We only want to change the pending status if it's not already pending AND if
- // the existing pending status was set by a NoAction binding. This avoids a NoAction
- // binding erroneously setting the pending status to true when a binding with an action
- // already set it to false
- //
- // We also want to change the pending status if the keystrokes don't match,
- // meaning it's different keystrokes than the NoAction that set pending to false
- if pending
- && !already_pending
- && pending_is_no_action
- && (pending_depth == depth || pending_keystrokes != binding.keystrokes())
- {
- pending_info.0 = !is_no_action;
- }
- } else {
- pending_info_opt = Some((
- pending && !is_no_action,
- is_no_action,
- depth,
- binding.keystrokes(),
- ));
- }
-
- if !pending {
- bindings.push((binding_index, binding.clone(), depth));
- continue 'outer;
+ let mut bindings: SmallVec<[_; 1]> = SmallVec::new();
+ let mut first_binding_index = None;
+ for (_, ix, binding) in matched_bindings {
+ if is_no_action(&*binding.action) {
+ break;
}
+ bindings.push(binding.clone());
+ first_binding_index.get_or_insert(ix);
}
- // sort by descending depth
- bindings.sort_by(|a, b| a.2.cmp(&b.2).reverse());
- let bindings = bindings
- .into_iter()
- .map_while(|(binding_index, binding, _)| {
- if is_no_action(&*binding.action) {
- None
- } else {
- Some((binding_index, binding))
- }
- })
- .collect();
- (bindings, pending_info_opt.unwrap_or_default().0)
+ let mut pending = HashSet::default();
+ for (ix, binding) in pending_bindings.into_iter().rev() {
+ if let Some(binding_ix) = first_binding_index
+ && binding_ix > ix
+ {
+ continue;
+ }
+ if is_no_action(&*binding.action) {
+ pending.remove(&&binding.keystrokes);
+ continue;
+ }
+ pending.insert(&binding.keystrokes);
+ }
+
+ (bindings, !pending.is_empty())
}
/// Check if the given binding is enabled, given a certain key context.
@@ -302,6 +256,30 @@ mod tests {
);
}
+ #[test]
+ fn test_depth_precedence() {
+ let bindings = [
+ KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
+ KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor")),
+ ];
+
+ let mut keymap = Keymap::default();
+ keymap.add_bindings(bindings.clone());
+
+ let (result, pending) = keymap.bindings_for_input(
+ &[Keystroke::parse("ctrl-a").unwrap()],
+ &[
+ KeyContext::parse("pane").unwrap(),
+ KeyContext::parse("editor").unwrap(),
+ ],
+ );
+
+ assert!(!pending);
+ assert_eq!(result.len(), 2);
+ assert!(result[0].action.partial_eq(&ActionGamma {}));
+ assert!(result[1].action.partial_eq(&ActionBeta {}));
+ }
+
#[test]
fn test_keymap_disabled() {
let bindings = [
@@ -453,6 +431,193 @@ mod tests {
assert_eq!(space_editor.1, true);
}
+ #[test]
+ fn test_override_multikey() {
+ let bindings = [
+ KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
+ KeyBinding::new("ctrl-w", NoAction {}, Some("editor")),
+ ];
+
+ let mut keymap = Keymap::default();
+ keymap.add_bindings(bindings.clone());
+
+ // Ensure `space` results in pending input on the workspace, but not editor
+ let (result, pending) = keymap.bindings_for_input(
+ &[Keystroke::parse("ctrl-w").unwrap()],
+ &[KeyContext::parse("editor").unwrap()],
+ );
+ assert!(result.is_empty());
+ assert_eq!(pending, true);
+
+ let bindings = [
+ KeyBinding::new("ctrl-w left", ActionAlpha {}, Some("editor")),
+ KeyBinding::new("ctrl-w", ActionBeta {}, Some("editor")),
+ ];
+
+ let mut keymap = Keymap::default();
+ keymap.add_bindings(bindings.clone());
+
+ // Ensure `space` results in pending input on the workspace, but not editor
+ let (result, pending) = keymap.bindings_for_input(
+ &[Keystroke::parse("ctrl-w").unwrap()],
+ &[KeyContext::parse("editor").unwrap()],
+ );
+ assert_eq!(result.len(), 1);
+ assert_eq!(pending, false);
+ }
+
+ #[test]
+ fn test_simple_disable() {
+ let bindings = [
+ KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
+ KeyBinding::new("ctrl-x", NoAction {}, Some("editor")),
+ ];
+
+ let mut keymap = Keymap::default();
+ keymap.add_bindings(bindings.clone());
+
+ // Ensure `space` results in pending input on the workspace, but not editor
+ let (result, pending) = keymap.bindings_for_input(
+ &[Keystroke::parse("ctrl-x").unwrap()],
+ &[KeyContext::parse("editor").unwrap()],
+ );
+ assert!(result.is_empty());
+ assert_eq!(pending, false);
+ }
+
+ #[test]
+ fn test_fail_to_disable() {
+ // disabled at the wrong level
+ let bindings = [
+ KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")),
+ KeyBinding::new("ctrl-x", NoAction {}, Some("workspace")),
+ ];
+
+ let mut keymap = Keymap::default();
+ keymap.add_bindings(bindings.clone());
+
+ // Ensure `space` results in pending input on the workspace, but not editor
+ let (result, pending) = keymap.bindings_for_input(
+ &[Keystroke::parse("ctrl-x").unwrap()],
+ &[
+ KeyContext::parse("workspace").unwrap(),
+ KeyContext::parse("editor").unwrap(),
+ ],
+ );
+ assert_eq!(result.len(), 1);
+ assert_eq!(pending, false);
+ }
+
+ #[test]
+ fn test_disable_deeper() {
+ let bindings = [
+ KeyBinding::new("ctrl-x", ActionAlpha {}, Some("workspace")),
+ KeyBinding::new("ctrl-x", NoAction {}, Some("editor")),
+ ];
+
+ let mut keymap = Keymap::default();
+ keymap.add_bindings(bindings.clone());
+
+ // Ensure `space` results in pending input on the workspace, but not editor
+ let (result, pending) = keymap.bindings_for_input(
+ &[Keystroke::parse("ctrl-x").unwrap()],
+ &[
+ KeyContext::parse("workspace").unwrap(),
+ KeyContext::parse("editor").unwrap(),
+ ],
+ );
+ assert_eq!(result.len(), 0);
+ assert_eq!(pending, false);
+ }
+
+ #[test]
+ fn test_pending_match_enabled() {
+ let bindings = [
+ KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
+ KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
+ ];
+ let mut keymap = Keymap::default();
+ keymap.add_bindings(bindings.clone());
+
+ let matched = keymap.bindings_for_input(
+ &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
+ &[
+ KeyContext::parse("Workspace"),
+ KeyContext::parse("Pane"),
+ KeyContext::parse("Editor vim_mode=normal"),
+ ]
+ .map(Result::unwrap),
+ );
+ assert_eq!(matched.0.len(), 1);
+ assert!(matched.0[0].action.partial_eq(&ActionBeta));
+ assert!(matched.1);
+ }
+
+ #[test]
+ fn test_pending_match_enabled_extended() {
+ let bindings = [
+ KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
+ KeyBinding::new("ctrl-x 0", NoAction, Some("Workspace")),
+ ];
+ let mut keymap = Keymap::default();
+ keymap.add_bindings(bindings.clone());
+
+ let matched = keymap.bindings_for_input(
+ &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
+ &[
+ KeyContext::parse("Workspace"),
+ KeyContext::parse("Pane"),
+ KeyContext::parse("Editor vim_mode=normal"),
+ ]
+ .map(Result::unwrap),
+ );
+ assert_eq!(matched.0.len(), 1);
+ assert!(matched.0[0].action.partial_eq(&ActionBeta));
+ assert!(!matched.1);
+ let bindings = [
+ KeyBinding::new("ctrl-x", ActionBeta, Some("Workspace")),
+ KeyBinding::new("ctrl-x 0", NoAction, Some("vim_mode == normal")),
+ ];
+ let mut keymap = Keymap::default();
+ keymap.add_bindings(bindings.clone());
+
+ let matched = keymap.bindings_for_input(
+ &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
+ &[
+ KeyContext::parse("Workspace"),
+ KeyContext::parse("Pane"),
+ KeyContext::parse("Editor vim_mode=normal"),
+ ]
+ .map(Result::unwrap),
+ );
+ assert_eq!(matched.0.len(), 1);
+ assert!(matched.0[0].action.partial_eq(&ActionBeta));
+ assert!(!matched.1);
+ }
+
+ #[test]
+ fn test_overriding_prefix() {
+ let bindings = [
+ KeyBinding::new("ctrl-x 0", ActionAlpha, Some("Workspace")),
+ KeyBinding::new("ctrl-x", ActionBeta, Some("vim_mode == normal")),
+ ];
+ let mut keymap = Keymap::default();
+ keymap.add_bindings(bindings.clone());
+
+ let matched = keymap.bindings_for_input(
+ &[Keystroke::parse("ctrl-x")].map(Result::unwrap),
+ &[
+ KeyContext::parse("Workspace"),
+ KeyContext::parse("Pane"),
+ KeyContext::parse("Editor vim_mode=normal"),
+ ]
+ .map(Result::unwrap),
+ );
+ assert_eq!(matched.0.len(), 1);
+ assert!(matched.0[0].action.partial_eq(&ActionBeta));
+ assert!(!matched.1);
+ }
+
#[test]
fn test_bindings_for_action() {
let bindings = [
diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs
index 6228a60490..f7fa54256d 100644
--- a/crates/gpui/src/taffy.rs
+++ b/crates/gpui/src/taffy.rs
@@ -283,7 +283,7 @@ impl ToTaffy for Length {
fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
match self {
Length::Definite(length) => length.to_taffy(rem_size),
- Length::Auto => taffy::prelude::LengthPercentageAuto::Auto,
+ Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
}
}
}
@@ -292,7 +292,7 @@ impl ToTaffy for Length {
fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::Dimension {
match self {
Length::Definite(length) => length.to_taffy(rem_size),
- Length::Auto => taffy::prelude::Dimension::Auto,
+ Length::Auto => taffy::prelude::Dimension::auto(),
}
}
}
@@ -302,14 +302,14 @@ impl ToTaffy for DefiniteLength {
match self {
DefiniteLength::Absolute(length) => match length {
AbsoluteLength::Pixels(pixels) => {
- taffy::style::LengthPercentage::Length(pixels.into())
+ taffy::style::LengthPercentage::length(pixels.into())
}
AbsoluteLength::Rems(rems) => {
- taffy::style::LengthPercentage::Length((*rems * rem_size).into())
+ taffy::style::LengthPercentage::length((*rems * rem_size).into())
}
},
DefiniteLength::Fraction(fraction) => {
- taffy::style::LengthPercentage::Percent(*fraction)
+ taffy::style::LengthPercentage::percent(*fraction)
}
}
}
@@ -320,14 +320,14 @@ impl ToTaffy for DefiniteLength {
match self {
DefiniteLength::Absolute(length) => match length {
AbsoluteLength::Pixels(pixels) => {
- taffy::style::LengthPercentageAuto::Length(pixels.into())
+ taffy::style::LengthPercentageAuto::length(pixels.into())
}
AbsoluteLength::Rems(rems) => {
- taffy::style::LengthPercentageAuto::Length((*rems * rem_size).into())
+ taffy::style::LengthPercentageAuto::length((*rems * rem_size).into())
}
},
DefiniteLength::Fraction(fraction) => {
- taffy::style::LengthPercentageAuto::Percent(*fraction)
+ taffy::style::LengthPercentageAuto::percent(*fraction)
}
}
}
@@ -337,12 +337,12 @@ impl ToTaffy for DefiniteLength {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Dimension {
match self {
DefiniteLength::Absolute(length) => match length {
- AbsoluteLength::Pixels(pixels) => taffy::style::Dimension::Length(pixels.into()),
+ AbsoluteLength::Pixels(pixels) => taffy::style::Dimension::length(pixels.into()),
AbsoluteLength::Rems(rems) => {
- taffy::style::Dimension::Length((*rems * rem_size).into())
+ taffy::style::Dimension::length((*rems * rem_size).into())
}
},
- DefiniteLength::Fraction(fraction) => taffy::style::Dimension::Percent(*fraction),
+ DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
}
}
}
@@ -350,9 +350,9 @@ impl ToTaffy for DefiniteLength {
impl ToTaffy for AbsoluteLength {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
match self {
- AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(pixels.into()),
+ AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::length(pixels.into()),
AbsoluteLength::Rems(rems) => {
- taffy::style::LengthPercentage::Length((*rems * rem_size).into())
+ taffy::style::LengthPercentage::length((*rems * rem_size).into())
}
}
}
diff --git a/crates/http_client/Cargo.toml b/crates/http_client/Cargo.toml
index 2b114f240a..2045708ff2 100644
--- a/crates/http_client/Cargo.toml
+++ b/crates/http_client/Cargo.toml
@@ -21,6 +21,7 @@ anyhow.workspace = true
derive_more.workspace = true
futures.workspace = true
http.workspace = true
+http-body.workspace = true
log.workspace = true
serde.workspace = true
serde_json.workspace = true
diff --git a/crates/http_client/src/async_body.rs b/crates/http_client/src/async_body.rs
index caf8089d0f..88972d279c 100644
--- a/crates/http_client/src/async_body.rs
+++ b/crates/http_client/src/async_body.rs
@@ -6,6 +6,7 @@ use std::{
use bytes::Bytes;
use futures::AsyncRead;
+use http_body::{Body, Frame};
/// Based on the implementation of AsyncBody in
/// .
@@ -114,3 +115,24 @@ impl futures::AsyncRead for AsyncBody {
}
}
}
+
+impl Body for AsyncBody {
+ type Data = Bytes;
+ type Error = std::io::Error;
+
+ fn poll_frame(
+ mut self: Pin<&mut Self>,
+ cx: &mut std::task::Context<'_>,
+ ) -> Poll