Compare commits
1 Commits
pixel-wip
...
update-use
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd676ffc4d |
48
Cargo.lock
generated
48
Cargo.lock
generated
@@ -1166,7 +1166,7 @@ version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09f7e37c0ed80b2a977691c47dae8625cfb21e205827106c64f7c588766b2e50"
|
||||
dependencies = [
|
||||
"async-lock 3.4.1",
|
||||
"async-lock",
|
||||
"blocking",
|
||||
"futures-lite 2.6.0",
|
||||
]
|
||||
@@ -1180,7 +1180,7 @@ dependencies = [
|
||||
"async-channel 2.3.1",
|
||||
"async-executor",
|
||||
"async-io",
|
||||
"async-lock 3.4.1",
|
||||
"async-lock",
|
||||
"blocking",
|
||||
"futures-lite 2.6.0",
|
||||
"once_cell",
|
||||
@@ -1192,7 +1192,7 @@ version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca"
|
||||
dependencies = [
|
||||
"async-lock 3.4.1",
|
||||
"async-lock",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
@@ -1204,15 +1204,6 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
|
||||
dependencies = [
|
||||
"event-listener 2.5.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.1"
|
||||
@@ -1252,7 +1243,7 @@ checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
|
||||
dependencies = [
|
||||
"async-channel 2.3.1",
|
||||
"async-io",
|
||||
"async-lock 3.4.1",
|
||||
"async-lock",
|
||||
"async-signal",
|
||||
"async-task",
|
||||
"blocking",
|
||||
@@ -1281,7 +1272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock 3.4.1",
|
||||
"async-lock",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
@@ -1302,7 +1293,7 @@ dependencies = [
|
||||
"async-channel 1.9.0",
|
||||
"async-global-executor",
|
||||
"async-io",
|
||||
"async-lock 3.4.1",
|
||||
"async-lock",
|
||||
"async-process",
|
||||
"crossbeam-utils",
|
||||
"futures-channel",
|
||||
@@ -6537,15 +6528,6 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_benchmarks"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fs",
|
||||
"gpui",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
@@ -11052,7 +11034,7 @@ dependencies = [
|
||||
"ashpd 0.12.0",
|
||||
"async-fs",
|
||||
"async-io",
|
||||
"async-lock 3.4.1",
|
||||
"async-lock",
|
||||
"blocking",
|
||||
"cbc",
|
||||
"cipher",
|
||||
@@ -15753,7 +15735,7 @@ dependencies = [
|
||||
"async-executor",
|
||||
"async-fs",
|
||||
"async-io",
|
||||
"async-lock 3.4.1",
|
||||
"async-lock",
|
||||
"async-net",
|
||||
"async-process",
|
||||
"blocking",
|
||||
@@ -20867,7 +20849,6 @@ name = "worktree"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-lock 2.8.0",
|
||||
"clock",
|
||||
"collections",
|
||||
"fs",
|
||||
@@ -20898,17 +20879,6 @@ dependencies = [
|
||||
"zlog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "worktree_benchmarks"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fs",
|
||||
"gpui",
|
||||
"settings",
|
||||
"workspace-hack",
|
||||
"worktree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "write16"
|
||||
version = "1.0.0"
|
||||
@@ -21180,7 +21150,7 @@ dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
"async-io",
|
||||
"async-lock 3.4.1",
|
||||
"async-lock",
|
||||
"async-process",
|
||||
"async-recursion",
|
||||
"async-task",
|
||||
|
||||
@@ -222,7 +222,7 @@ members = [
|
||||
|
||||
"tooling/perf",
|
||||
"tooling/workspace-hack",
|
||||
"tooling/xtask", "crates/fs_benchmarks", "crates/worktree_benchmarks",
|
||||
"tooling/xtask",
|
||||
]
|
||||
default-members = ["crates/zed"]
|
||||
|
||||
@@ -455,7 +455,6 @@ async-compat = "0.2.1"
|
||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||
async-dispatcher = "0.1"
|
||||
async-fs = "2.1"
|
||||
async-lock = "2.1"
|
||||
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
|
||||
async-recursion = "1.0.0"
|
||||
async-tar = "0.5.0"
|
||||
|
||||
@@ -491,8 +491,8 @@
|
||||
"bindings": {
|
||||
"ctrl-[": "editor::Outdent",
|
||||
"ctrl-]": "editor::Indent",
|
||||
"shift-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }], // Insert Cursor Above
|
||||
"shift-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }], // Insert Cursor Below
|
||||
"shift-alt-up": "editor::AddSelectionAbove", // Insert Cursor Above
|
||||
"shift-alt-down": "editor::AddSelectionBelow", // Insert Cursor Below
|
||||
"ctrl-shift-k": "editor::DeleteLine",
|
||||
"alt-up": "editor::MoveLineUp",
|
||||
"alt-down": "editor::MoveLineDown",
|
||||
|
||||
@@ -539,10 +539,10 @@
|
||||
"bindings": {
|
||||
"cmd-[": "editor::Outdent",
|
||||
"cmd-]": "editor::Indent",
|
||||
"cmd-ctrl-p": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }], // Insert cursor above
|
||||
"cmd-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }],
|
||||
"cmd-ctrl-n": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }], // Insert cursor below
|
||||
"cmd-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }],
|
||||
"cmd-ctrl-p": "editor::AddSelectionAbove", // Insert cursor above
|
||||
"cmd-alt-up": "editor::AddSelectionAbove",
|
||||
"cmd-ctrl-n": "editor::AddSelectionBelow", // Insert cursor below
|
||||
"cmd-alt-down": "editor::AddSelectionBelow",
|
||||
"cmd-shift-k": "editor::DeleteLine",
|
||||
"alt-up": "editor::MoveLineUp",
|
||||
"alt-down": "editor::MoveLineDown",
|
||||
|
||||
@@ -500,8 +500,8 @@
|
||||
"bindings": {
|
||||
"ctrl-[": "editor::Outdent",
|
||||
"ctrl-]": "editor::Indent",
|
||||
"ctrl-shift-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }], // Insert Cursor Above
|
||||
"ctrl-shift-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }], // Insert Cursor Below
|
||||
"ctrl-shift-alt-up": "editor::AddSelectionAbove", // Insert Cursor Above
|
||||
"ctrl-shift-alt-down": "editor::AddSelectionBelow", // Insert Cursor Below
|
||||
"ctrl-shift-k": "editor::DeleteLine",
|
||||
"alt-up": "editor::MoveLineUp",
|
||||
"alt-down": "editor::MoveLineDown",
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
"ctrl-<": "editor::ScrollCursorCenter", // editor:scroll-to-cursor
|
||||
"f3": ["editor::SelectNext", { "replace_newest": true }], // find-and-replace:find-next
|
||||
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
|
||||
"alt-shift-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }], // editor:add-selection-below
|
||||
"alt-shift-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }], // editor:add-selection-above
|
||||
"alt-shift-down": "editor::AddSelectionBelow", // editor:add-selection-below
|
||||
"alt-shift-up": "editor::AddSelectionAbove", // editor:add-selection-above
|
||||
"ctrl-j": "editor::JoinLines", // editor:join-lines
|
||||
"ctrl-shift-d": "editor::DuplicateLineDown", // editor:duplicate-lines
|
||||
"ctrl-up": "editor::MoveLineUp", // editor:move-line-up
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }],
|
||||
"ctrl-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }],
|
||||
"ctrl-alt-up": "editor::AddSelectionAbove",
|
||||
"ctrl-alt-down": "editor::AddSelectionBelow",
|
||||
"ctrl-shift-up": "editor::MoveLineUp",
|
||||
"ctrl-shift-down": "editor::MoveLineDown",
|
||||
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
"cmd-<": "editor::ScrollCursorCenter",
|
||||
"cmd-g": ["editor::SelectNext", { "replace_newest": true }],
|
||||
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
|
||||
"ctrl-shift-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }],
|
||||
"ctrl-shift-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }],
|
||||
"ctrl-shift-down": "editor::AddSelectionBelow",
|
||||
"ctrl-shift-up": "editor::AddSelectionAbove",
|
||||
"alt-enter": "editor::Newline",
|
||||
"cmd-shift-d": "editor::DuplicateLineDown",
|
||||
"ctrl-cmd-up": "editor::MoveLineUp",
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-shift-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }],
|
||||
"ctrl-shift-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }],
|
||||
"ctrl-shift-up": "editor::AddSelectionAbove",
|
||||
"ctrl-shift-down": "editor::AddSelectionBelow",
|
||||
"cmd-ctrl-up": "editor::MoveLineUp",
|
||||
"cmd-ctrl-down": "editor::MoveLineDown",
|
||||
"cmd-shift-space": "editor::SelectAll",
|
||||
|
||||
@@ -95,6 +95,8 @@
|
||||
"g g": "vim::StartOfDocument",
|
||||
"g h": "editor::Hover",
|
||||
"g B": "editor::BlameHover",
|
||||
"g t": "vim::GoToTab",
|
||||
"g shift-t": "vim::GoToPreviousTab",
|
||||
"g d": "editor::GoToDefinition",
|
||||
"g shift-d": "editor::GoToDeclaration",
|
||||
"g y": "editor::GoToTypeDefinition",
|
||||
@@ -498,8 +500,8 @@
|
||||
"ctrl-c": "editor::ToggleComments",
|
||||
"d": "vim::HelixDelete",
|
||||
"c": "vim::Substitute",
|
||||
"shift-c": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }],
|
||||
"alt-shift-c": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }]
|
||||
"shift-c": "editor::AddSelectionBelow",
|
||||
"alt-shift-c": "editor::AddSelectionAbove"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -809,7 +811,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "VimControl && !menu || !Editor && !Terminal",
|
||||
"context": "VimControl || !Editor && !Terminal",
|
||||
"bindings": {
|
||||
// window related commands (ctrl-w X)
|
||||
"ctrl-w": null,
|
||||
@@ -863,9 +865,7 @@
|
||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
|
||||
"ctrl-w n": "workspace::NewFileSplitHorizontal",
|
||||
"g t": "vim::GoToTab",
|
||||
"g shift-t": "vim::GoToPreviousTab"
|
||||
"ctrl-w n": "workspace::NewFileSplitHorizontal"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -724,9 +724,7 @@
|
||||
// Whether to hide the root entry when only one folder is open in the window.
|
||||
"hide_root": false,
|
||||
// Whether to hide the hidden entries in the project panel.
|
||||
"hide_hidden": false,
|
||||
// Whether to automatically open files when pasting them in the project panel.
|
||||
"open_file_on_paste": true
|
||||
"hide_hidden": false
|
||||
},
|
||||
"outline_panel": {
|
||||
// Whether to show the outline panel button in the status bar
|
||||
|
||||
@@ -174,16 +174,11 @@ impl Render for ModeSelector {
|
||||
|
||||
let this = cx.entity();
|
||||
|
||||
let icon = if self.menu_handle.is_deployed() {
|
||||
IconName::ChevronUp
|
||||
} else {
|
||||
IconName::ChevronDown
|
||||
};
|
||||
|
||||
let trigger_button = Button::new("mode-selector-trigger", current_mode_name)
|
||||
.label_size(LabelSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.color(Color::Muted)
|
||||
.icon(icon)
|
||||
.icon(IconName::ChevronDown)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon_color(Color::Muted)
|
||||
|
||||
@@ -5,12 +5,12 @@ use anyhow::Result;
|
||||
use collections::IndexMap;
|
||||
use futures::FutureExt;
|
||||
use fuzzy::{StringMatchCandidate, match_strings};
|
||||
use gpui::{AsyncWindowContext, BackgroundExecutor, DismissEvent, Task, WeakEntity};
|
||||
use gpui::{Action, AsyncWindowContext, BackgroundExecutor, DismissEvent, Task, WeakEntity};
|
||||
use ordered_float::OrderedFloat;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use ui::{
|
||||
DocumentationAside, DocumentationEdge, DocumentationSide, IntoElement, ListItem,
|
||||
ListItemSpacing, prelude::*,
|
||||
AnyElement, App, Context, DocumentationAside, DocumentationEdge, DocumentationSide,
|
||||
IntoElement, ListItem, ListItemSpacing, SharedString, Window, prelude::*, rems,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
@@ -278,6 +278,36 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_footer(
|
||||
&self,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<gpui::AnyElement> {
|
||||
Some(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.p_1()
|
||||
.gap_4()
|
||||
.justify_between()
|
||||
.child(
|
||||
Button::new("configure", "Configure")
|
||||
.icon(IconName::Settings)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(
|
||||
zed_actions::agent::OpenSettings.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}),
|
||||
)
|
||||
.into_any(),
|
||||
)
|
||||
}
|
||||
|
||||
fn documentation_aside(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
@@ -287,7 +317,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
||||
let description = description.clone();
|
||||
DocumentationAside::new(
|
||||
DocumentationSide::Left,
|
||||
DocumentationEdge::Top,
|
||||
DocumentationEdge::Bottom,
|
||||
Rc::new(move |_| Label::new(description.clone()).into_any_element()),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -57,26 +57,30 @@ impl Render for AcpModelSelectorPopover {
|
||||
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
let (color, icon) = if self.menu_handle.is_deployed() {
|
||||
(Color::Accent, IconName::ChevronUp)
|
||||
let color = if self.menu_handle.is_deployed() {
|
||||
Color::Accent
|
||||
} else {
|
||||
(Color::Muted, IconName::ChevronDown)
|
||||
Color::Muted
|
||||
};
|
||||
|
||||
PickerPopoverMenu::new(
|
||||
self.selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.when_some(model_icon, |this, icon| {
|
||||
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
|
||||
})
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.color(color)
|
||||
.size(LabelSize::Small)
|
||||
.ml_0p5(),
|
||||
)
|
||||
.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
|
||||
@@ -292,8 +292,6 @@ pub struct AcpThreadView {
|
||||
resume_thread_metadata: Option<DbThreadMetadata>,
|
||||
_cancel_task: Option<Task<()>>,
|
||||
_subscriptions: [Subscription; 5],
|
||||
#[cfg(target_os = "windows")]
|
||||
show_codex_windows_warning: bool,
|
||||
}
|
||||
|
||||
enum ThreadState {
|
||||
@@ -399,10 +397,6 @@ impl AcpThreadView {
|
||||
),
|
||||
];
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let show_codex_windows_warning = crate::ExternalAgent::parse_built_in(agent.as_ref())
|
||||
== Some(crate::ExternalAgent::Codex);
|
||||
|
||||
Self {
|
||||
agent: agent.clone(),
|
||||
workspace: workspace.clone(),
|
||||
@@ -445,8 +439,6 @@ impl AcpThreadView {
|
||||
focus_handle: cx.focus_handle(),
|
||||
new_server_version_available: None,
|
||||
resume_thread_metadata: resume_thread,
|
||||
#[cfg(target_os = "windows")]
|
||||
show_codex_windows_warning,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5036,49 +5028,6 @@ impl AcpThreadView {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn render_codex_windows_warning(&self, cx: &mut Context<Self>) -> Option<Callout> {
|
||||
if self.show_codex_windows_warning {
|
||||
Some(
|
||||
Callout::new()
|
||||
.icon(IconName::Warning)
|
||||
.severity(Severity::Warning)
|
||||
.title("Codex on Windows")
|
||||
.description(
|
||||
"For best performance, run Codex in Windows Subsystem for Linux (WSL2)",
|
||||
)
|
||||
.actions_slot(
|
||||
Button::new("open-wsl-modal", "Open in WSL")
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(cx.listener({
|
||||
move |_, _, window, cx| {
|
||||
window.dispatch_action(
|
||||
zed_actions::wsl_actions::OpenWsl::default().boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
cx.notify();
|
||||
}
|
||||
})),
|
||||
)
|
||||
.dismiss_action(
|
||||
IconButton::new("dismiss", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(Tooltip::text("Dismiss Warning"))
|
||||
.on_click(cx.listener({
|
||||
move |this, _, _, cx| {
|
||||
this.show_codex_windows_warning = false;
|
||||
cx.notify();
|
||||
}
|
||||
})),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
|
||||
let content = match self.thread_error.as_ref()? {
|
||||
ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx),
|
||||
@@ -5566,16 +5515,6 @@ impl Render for AcpThreadView {
|
||||
_ => this,
|
||||
})
|
||||
.children(self.render_thread_retry_status_callout(window, cx))
|
||||
.children({
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
self.render_codex_windows_warning(cx)
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Vec::<Empty>::new()
|
||||
}
|
||||
})
|
||||
.children(self.render_thread_error(window, cx))
|
||||
.when_some(
|
||||
self.new_server_version_available.as_ref().filter(|_| {
|
||||
|
||||
@@ -7,7 +7,7 @@ use gpui::{Entity, FocusHandle, SharedString};
|
||||
use picker::popover_menu::PickerPopoverMenu;
|
||||
use settings::update_settings_file;
|
||||
use std::sync::Arc;
|
||||
use ui::{ButtonLike, PopoverMenuHandle, TintColor, Tooltip, prelude::*};
|
||||
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
|
||||
use zed_actions::agent::ToggleModelSelector;
|
||||
|
||||
pub struct AgentModelSelector {
|
||||
@@ -70,11 +70,6 @@ impl Render for AgentModelSelector {
|
||||
.unwrap_or_else(|| SharedString::from("Select a Model"));
|
||||
|
||||
let provider_icon = model.as_ref().map(|model| model.provider.icon());
|
||||
let color = if self.menu_handle.is_deployed() {
|
||||
Color::Accent
|
||||
} else {
|
||||
Color::Muted
|
||||
};
|
||||
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
@@ -82,18 +77,17 @@ impl Render for AgentModelSelector {
|
||||
self.selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
.when_some(provider_icon, |this, icon| {
|
||||
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
|
||||
this.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall))
|
||||
})
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.color(color)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.ml_0p5(),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(color)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
move |window, cx| {
|
||||
@@ -105,14 +99,10 @@ impl Render for AgentModelSelector {
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::TopRight,
|
||||
gpui::Corner::BottomRight,
|
||||
cx,
|
||||
)
|
||||
.with_handle(self.menu_handle.clone())
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(2.0),
|
||||
})
|
||||
.render(window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,11 +222,12 @@ enum WhichFontSize {
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum AgentType {
|
||||
#[default]
|
||||
NativeAgent,
|
||||
Zed,
|
||||
TextThread,
|
||||
Gemini,
|
||||
ClaudeCode,
|
||||
Codex,
|
||||
NativeAgent,
|
||||
Custom {
|
||||
name: SharedString,
|
||||
command: AgentServerCommand,
|
||||
@@ -236,7 +237,8 @@ pub enum AgentType {
|
||||
impl AgentType {
|
||||
fn label(&self) -> SharedString {
|
||||
match self {
|
||||
Self::NativeAgent | Self::TextThread => "Zed Agent".into(),
|
||||
Self::Zed | Self::TextThread => "Zed Agent".into(),
|
||||
Self::NativeAgent => "Agent 2".into(),
|
||||
Self::Gemini => "Gemini CLI".into(),
|
||||
Self::ClaudeCode => "Claude Code".into(),
|
||||
Self::Codex => "Codex".into(),
|
||||
@@ -246,7 +248,7 @@ impl AgentType {
|
||||
|
||||
fn icon(&self) -> Option<IconName> {
|
||||
match self {
|
||||
Self::NativeAgent | Self::TextThread => None,
|
||||
Self::Zed | Self::NativeAgent | Self::TextThread => None,
|
||||
Self::Gemini => Some(IconName::AiGemini),
|
||||
Self::ClaudeCode => Some(IconName::AiClaude),
|
||||
Self::Codex => Some(IconName::AiOpenAi),
|
||||
@@ -811,7 +813,7 @@ impl AgentPanel {
|
||||
|
||||
const LAST_USED_EXTERNAL_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
struct LastUsedExternalAgent {
|
||||
agent: crate::ExternalAgent,
|
||||
}
|
||||
@@ -852,18 +854,18 @@ impl AgentPanel {
|
||||
.and_then(|value| {
|
||||
serde_json::from_str::<LastUsedExternalAgent>(&value).log_err()
|
||||
})
|
||||
.map(|agent| agent.agent)
|
||||
.unwrap_or(ExternalAgent::NativeAgent)
|
||||
.unwrap_or_default()
|
||||
.agent
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let server = ext_agent.server(fs, history);
|
||||
|
||||
if !loading {
|
||||
telemetry::event!("Agent Thread Started", agent = server.telemetry_id());
|
||||
telemetry::event!("Agent Thread Started", agent = ext_agent.name());
|
||||
}
|
||||
|
||||
let server = ext_agent.server(fs, history);
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
let selected_agent = ext_agent.into();
|
||||
if this.selected_agent != selected_agent {
|
||||
@@ -1343,6 +1345,15 @@ impl AgentPanel {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match agent {
|
||||
AgentType::Zed => {
|
||||
window.dispatch_action(
|
||||
NewThread {
|
||||
from_thread_id: None,
|
||||
}
|
||||
.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
AgentType::TextThread => {
|
||||
window.dispatch_action(NewTextThread.boxed_clone(), cx);
|
||||
}
|
||||
|
||||
@@ -161,9 +161,10 @@ pub struct NewNativeAgentThreadFromSummary {
|
||||
}
|
||||
|
||||
// TODO unify this with AgentType
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExternalAgent {
|
||||
enum ExternalAgent {
|
||||
#[default]
|
||||
Gemini,
|
||||
ClaudeCode,
|
||||
Codex,
|
||||
@@ -183,13 +184,13 @@ fn placeholder_command() -> AgentServerCommand {
|
||||
}
|
||||
|
||||
impl ExternalAgent {
|
||||
pub fn parse_built_in(server: &dyn agent_servers::AgentServer) -> Option<Self> {
|
||||
match server.telemetry_id() {
|
||||
"gemini-cli" => Some(Self::Gemini),
|
||||
"claude-code" => Some(Self::ClaudeCode),
|
||||
"codex" => Some(Self::Codex),
|
||||
"zed" => Some(Self::NativeAgent),
|
||||
_ => None,
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::NativeAgent => "zed",
|
||||
Self::Gemini => "gemini-cli",
|
||||
Self::ClaudeCode => "claude-code",
|
||||
Self::Codex => "codex",
|
||||
Self::Custom { .. } => "custom",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,16 +144,10 @@ impl Render for ProfileSelector {
|
||||
.unwrap_or_else(|| "Unknown".into());
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
let icon = if self.picker_handle.is_deployed() {
|
||||
IconName::ChevronUp
|
||||
} else {
|
||||
IconName::ChevronDown
|
||||
};
|
||||
|
||||
let trigger_button = Button::new("profile-selector", selected_profile)
|
||||
.label_size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.icon(icon)
|
||||
.icon(IconName::ChevronDown)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon_color(Color::Muted)
|
||||
|
||||
@@ -1977,9 +1977,7 @@ impl TextThreadEditor {
|
||||
cx.entity().downgrade(),
|
||||
IconButton::new("trigger", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.selected_icon_color(Color::Accent)
|
||||
.selected_style(ButtonStyle::Filled),
|
||||
.icon_color(Color::Muted),
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Add Context",
|
||||
@@ -2054,27 +2052,30 @@ impl TextThreadEditor {
|
||||
};
|
||||
|
||||
let focus_handle = self.editor().focus_handle(cx);
|
||||
let (color, icon) = if self.language_model_selector_menu_handle.is_deployed() {
|
||||
(Color::Accent, IconName::ChevronUp)
|
||||
} else {
|
||||
(Color::Muted, IconName::ChevronDown)
|
||||
};
|
||||
|
||||
PickerPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(Icon::new(provider_icon).color(color).size(IconSize::XSmall))
|
||||
.child(
|
||||
Icon::new(provider_icon)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.color(color)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.ml_0p5(),
|
||||
)
|
||||
.child(Icon::new(icon).color(color).size(IconSize::XSmall)),
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
@@ -2085,7 +2086,7 @@ impl TextThreadEditor {
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::BottomRight,
|
||||
gpui::Corner::BottomLeft,
|
||||
cx,
|
||||
)
|
||||
.with_handle(self.language_model_selector_menu_handle.clone())
|
||||
|
||||
@@ -322,9 +322,6 @@ pub struct LanguageModel {
|
||||
pub supports_images: bool,
|
||||
pub supports_thinking: bool,
|
||||
pub supports_max_mode: bool,
|
||||
// only used by OpenAI and xAI
|
||||
#[serde(default)]
|
||||
pub supports_parallel_tool_calls: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
||||
@@ -47,13 +47,13 @@ pub struct PredictEditsRequest {
|
||||
pub enum PromptFormat {
|
||||
MarkedExcerpt,
|
||||
LabeledSections,
|
||||
NumLinesUniDiff,
|
||||
NumberedLines,
|
||||
/// Prompt format intended for use via zeta_cli
|
||||
OnlySnippets,
|
||||
}
|
||||
|
||||
impl PromptFormat {
|
||||
pub const DEFAULT: PromptFormat = PromptFormat::NumLinesUniDiff;
|
||||
pub const DEFAULT: PromptFormat = PromptFormat::NumberedLines;
|
||||
}
|
||||
|
||||
impl Default for PromptFormat {
|
||||
@@ -74,7 +74,7 @@ impl std::fmt::Display for PromptFormat {
|
||||
PromptFormat::MarkedExcerpt => write!(f, "Marked Excerpt"),
|
||||
PromptFormat::LabeledSections => write!(f, "Labeled Sections"),
|
||||
PromptFormat::OnlySnippets => write!(f, "Only Snippets"),
|
||||
PromptFormat::NumLinesUniDiff => write!(f, "Numbered Lines / Unified Diff"),
|
||||
PromptFormat::NumberedLines => write!(f, "Numbered Lines"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,30 +15,27 @@ use strum::{EnumIter, IntoEnumIterator};
|
||||
|
||||
pub const DEFAULT_MAX_PROMPT_BYTES: usize = 10 * 1024;
|
||||
|
||||
pub const CURSOR_MARKER: &str = "<|user_cursor|>";
|
||||
pub const CURSOR_MARKER: &str = "<|cursor_position|>";
|
||||
/// NOTE: Differs from zed version of constant - includes a newline
|
||||
pub const EDITABLE_REGION_START_MARKER_WITH_NEWLINE: &str = "<|editable_region_start|>\n";
|
||||
/// NOTE: Differs from zed version of constant - includes a newline
|
||||
pub const EDITABLE_REGION_END_MARKER_WITH_NEWLINE: &str = "<|editable_region_end|>\n";
|
||||
|
||||
// TODO: use constants for markers?
|
||||
const MARKED_EXCERPT_INSTRUCTIONS: &str = indoc! {"
|
||||
const MARKED_EXCERPT_SYSTEM_PROMPT: &str = indoc! {"
|
||||
You are a code completion assistant and your task is to analyze user edits and then rewrite an excerpt that the user provides, suggesting the appropriate edits within the excerpt, taking into account the cursor location.
|
||||
|
||||
The excerpt to edit will be wrapped in markers <|editable_region_start|> and <|editable_region_end|>. The cursor position is marked with <|user_cursor|>. Please respond with edited code for that region.
|
||||
The excerpt to edit will be wrapped in markers <|editable_region_start|> and <|editable_region_end|>. The cursor position is marked with <|cursor_position|>. Please respond with edited code for that region.
|
||||
|
||||
Other code is provided for context, and `…` indicates when code has been skipped.
|
||||
|
||||
# Edit History:
|
||||
|
||||
"};
|
||||
|
||||
const LABELED_SECTIONS_INSTRUCTIONS: &str = indoc! {r#"
|
||||
const LABELED_SECTIONS_SYSTEM_PROMPT: &str = indoc! {r#"
|
||||
You are a code completion assistant and your task is to analyze user edits, and suggest an edit to one of the provided sections of code.
|
||||
|
||||
Sections of code are grouped by file and then labeled by `<|section_N|>` (e.g `<|section_8|>`).
|
||||
|
||||
The cursor position is marked with `<|user_cursor|>` and it will appear within a special section labeled `<|current_section|>`. Prefer editing the current section until no more changes are needed within it.
|
||||
The cursor position is marked with `<|cursor_position|>` and it will appear within a special section labeled `<|current_section|>`. Prefer editing the current section until no more changes are needed within it.
|
||||
|
||||
Respond ONLY with the name of the section to edit on a single line, followed by all of the code that should replace that section. For example:
|
||||
|
||||
@@ -46,12 +43,9 @@ const LABELED_SECTIONS_INSTRUCTIONS: &str = indoc! {r#"
|
||||
for i in 0..16 {
|
||||
println!("{i}");
|
||||
}
|
||||
|
||||
# Edit History:
|
||||
|
||||
"#};
|
||||
|
||||
const NUMBERED_LINES_INSTRUCTIONS: &str = indoc! {r#"
|
||||
const NUMBERED_LINES_SYSTEM_PROMPT: &str = indoc! {r#"
|
||||
# Instructions
|
||||
|
||||
You are a code completion assistant helping a programmer finish their work. Your task is to:
|
||||
@@ -77,27 +71,16 @@ const NUMBERED_LINES_INSTRUCTIONS: &str = indoc! {r#"
|
||||
# Example output:
|
||||
|
||||
```
|
||||
--- a/src/myapp/cli.py
|
||||
+++ b/src/myapp/cli.py
|
||||
--- a/distill-claude/tmp-outs/edits_history.txt
|
||||
+++ b/distill-claude/tmp-outs/edits_history.txt
|
||||
@@ -1,3 +1,3 @@
|
||||
-
|
||||
-
|
||||
-import sys
|
||||
+import json
|
||||
```
|
||||
|
||||
# Edit History:
|
||||
|
||||
"#};
|
||||
|
||||
const UNIFIED_DIFF_REMINDER: &str = indoc! {"
|
||||
---
|
||||
|
||||
Please analyze the edit history and the files, then provide the unified diff for your predicted edits.
|
||||
Do not include the cursor marker in your output.
|
||||
If you're editing multiple files, be sure to reflect filename in the hunk's header.
|
||||
"};
|
||||
|
||||
pub struct PlannedPrompt<'a> {
|
||||
request: &'a predict_edits_v3::PredictEditsRequest,
|
||||
/// Snippets to include in the prompt. These may overlap - they are merged / deduplicated in
|
||||
@@ -106,6 +89,16 @@ pub struct PlannedPrompt<'a> {
|
||||
budget_used: usize,
|
||||
}
|
||||
|
||||
pub fn system_prompt(format: PromptFormat) -> &'static str {
|
||||
match format {
|
||||
PromptFormat::MarkedExcerpt => MARKED_EXCERPT_SYSTEM_PROMPT,
|
||||
PromptFormat::LabeledSections => LABELED_SECTIONS_SYSTEM_PROMPT,
|
||||
PromptFormat::NumberedLines => NUMBERED_LINES_SYSTEM_PROMPT,
|
||||
// only intended for use via zeta_cli
|
||||
PromptFormat::OnlySnippets => "",
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PlannedSnippet<'a> {
|
||||
path: Arc<Path>,
|
||||
@@ -404,63 +397,21 @@ impl<'a> PlannedPrompt<'a> {
|
||||
),
|
||||
],
|
||||
PromptFormat::LabeledSections => vec![(self.request.cursor_point, CURSOR_MARKER)],
|
||||
PromptFormat::NumLinesUniDiff => {
|
||||
vec![(self.request.cursor_point, CURSOR_MARKER)]
|
||||
}
|
||||
PromptFormat::NumberedLines => vec![(self.request.cursor_point, CURSOR_MARKER)],
|
||||
PromptFormat::OnlySnippets => vec![],
|
||||
};
|
||||
|
||||
let mut prompt = match self.request.prompt_format {
|
||||
PromptFormat::MarkedExcerpt => MARKED_EXCERPT_INSTRUCTIONS.to_string(),
|
||||
PromptFormat::LabeledSections => LABELED_SECTIONS_INSTRUCTIONS.to_string(),
|
||||
PromptFormat::NumLinesUniDiff => NUMBERED_LINES_INSTRUCTIONS.to_string(),
|
||||
// only intended for use via zeta_cli
|
||||
PromptFormat::OnlySnippets => String::new(),
|
||||
};
|
||||
|
||||
let mut prompt = String::new();
|
||||
prompt.push_str("## User Edits\n\n");
|
||||
if self.request.events.is_empty() {
|
||||
prompt.push_str("No edits yet.\n\n");
|
||||
prompt.push_str("No edits yet.\n");
|
||||
} else {
|
||||
prompt.push_str(
|
||||
"The following are the latest edits made by the user, from earlier to later.\n\n",
|
||||
);
|
||||
Self::push_events(&mut prompt, &self.request.events);
|
||||
}
|
||||
|
||||
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
|
||||
if self.request.referenced_declarations.is_empty() {
|
||||
prompt.push_str(indoc! {"
|
||||
# File under the cursor:
|
||||
|
||||
The cursor marker <|user_cursor|> indicates the current user cursor position.
|
||||
The file is in current state, edits from edit history have been applied.
|
||||
We prepend line numbers (e.g., `123|<actual line>`); they are not part of the file.
|
||||
|
||||
"});
|
||||
} else {
|
||||
// Note: This hasn't been trained on yet
|
||||
prompt.push_str(indoc! {"
|
||||
# Code Excerpts:
|
||||
|
||||
The cursor marker <|user_cursor|> indicates the current user cursor position.
|
||||
Other excerpts of code from the project have been included as context based on their similarity to the code under the cursor.
|
||||
Context excerpts are not guaranteed to be relevant, so use your own judgement.
|
||||
Files are in their current state, edits from edit history have been applied.
|
||||
We prepend line numbers (e.g., `123|<actual line>`); they are not part of the file.
|
||||
|
||||
"});
|
||||
}
|
||||
} else {
|
||||
prompt.push_str("\n## Code\n\n");
|
||||
}
|
||||
|
||||
prompt.push_str("\n## Code\n\n");
|
||||
let section_labels =
|
||||
self.push_file_snippets(&mut prompt, &mut excerpt_file_insertions, file_snippets)?;
|
||||
|
||||
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
|
||||
prompt.push_str(UNIFIED_DIFF_REMINDER);
|
||||
}
|
||||
|
||||
Ok((prompt, section_labels))
|
||||
}
|
||||
|
||||
@@ -551,7 +502,7 @@ impl<'a> PlannedPrompt<'a> {
|
||||
match self.request.prompt_format {
|
||||
PromptFormat::MarkedExcerpt
|
||||
| PromptFormat::OnlySnippets
|
||||
| PromptFormat::NumLinesUniDiff => {
|
||||
| PromptFormat::NumberedLines => {
|
||||
if range.start.0 > 0 && !skipped_last_snippet {
|
||||
output.push_str("…\n");
|
||||
}
|
||||
@@ -569,7 +520,7 @@ impl<'a> PlannedPrompt<'a> {
|
||||
}
|
||||
|
||||
let push_full_snippet = |output: &mut String| {
|
||||
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
|
||||
if self.request.prompt_format == PromptFormat::NumberedLines {
|
||||
for (i, line) in snippet.text.lines().enumerate() {
|
||||
writeln!(output, "{}|{}", i as u32 + range.start.0 + 1, line)?;
|
||||
}
|
||||
@@ -592,7 +543,7 @@ impl<'a> PlannedPrompt<'a> {
|
||||
} else if !excerpt_file_insertions.is_empty() {
|
||||
let lines = snippet.text.lines().collect::<Vec<_>>();
|
||||
let push_line = |output: &mut String, line_ix: usize| {
|
||||
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
|
||||
if self.request.prompt_format == PromptFormat::NumberedLines {
|
||||
write!(output, "{}|", line_ix as u32 + range.start.0 + 1)?;
|
||||
}
|
||||
anyhow::Ok(writeln!(output, "{}", lines[line_ix])?)
|
||||
@@ -609,7 +560,7 @@ impl<'a> PlannedPrompt<'a> {
|
||||
push_line(output, line_ix)?;
|
||||
}
|
||||
if let Some(next_line) = lines.get(insertion_line_ix) {
|
||||
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
|
||||
if self.request.prompt_format == PromptFormat::NumberedLines {
|
||||
write!(
|
||||
output,
|
||||
"{}|",
|
||||
|
||||
@@ -262,15 +262,11 @@ impl TransportDelegate {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up logs by trimming unnecessary whitespace/newlines before inserting into log.
|
||||
let line = line.trim();
|
||||
|
||||
log::debug!("stderr: {line}");
|
||||
|
||||
for (kind, handler) in log_handlers.lock().iter_mut() {
|
||||
if matches!(kind, LogKind::Adapter) {
|
||||
handler(iokind, None, line);
|
||||
handler(iokind, None, line.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{path::PathBuf, sync::OnceLock};
|
||||
use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use async_trait::async_trait;
|
||||
@@ -377,12 +377,6 @@ impl DebugAdapter for CodeLldbDebugAdapter {
|
||||
command = Some(path);
|
||||
};
|
||||
let mut json_config = config.config.clone();
|
||||
|
||||
// Enable info level for CodeLLDB by default.
|
||||
// Logs can then be viewed in our DAP logs.
|
||||
let mut envs = collections::HashMap::default();
|
||||
envs.insert("RUST_LOG".to_string(), "info".to_string());
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: Some(command.unwrap()),
|
||||
cwd: Some(delegate.worktree_root_path().to_path_buf()),
|
||||
@@ -407,7 +401,7 @@ impl DebugAdapter for CodeLldbDebugAdapter {
|
||||
request_args: self
|
||||
.request_args(delegate, json_config, &config.label)
|
||||
.await?,
|
||||
envs,
|
||||
envs: HashMap::default(),
|
||||
connection: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ pub use predict_edits_v3::Line;
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct EditPredictionContextOptions {
|
||||
pub use_imports: bool,
|
||||
pub use_references: bool,
|
||||
pub excerpt: EditPredictionExcerptOptions,
|
||||
pub score: EditPredictionScoreOptions,
|
||||
}
|
||||
@@ -117,23 +116,19 @@ impl EditPredictionContext {
|
||||
index_state,
|
||||
)?;
|
||||
let excerpt_text = excerpt.text(buffer);
|
||||
let excerpt_occurrences = text_similarity::Occurrences::within_string(&excerpt_text.body);
|
||||
|
||||
let declarations = if options.use_references
|
||||
&& let Some(index_state) = index_state
|
||||
{
|
||||
let excerpt_occurrences =
|
||||
text_similarity::Occurrences::within_string(&excerpt_text.body);
|
||||
let adjacent_start = Point::new(cursor_point.row.saturating_sub(2), 0);
|
||||
let adjacent_end = Point::new(cursor_point.row + 1, 0);
|
||||
let adjacent_occurrences = text_similarity::Occurrences::within_string(
|
||||
&buffer
|
||||
.text_for_range(adjacent_start..adjacent_end)
|
||||
.collect::<String>(),
|
||||
);
|
||||
|
||||
let adjacent_start = Point::new(cursor_point.row.saturating_sub(2), 0);
|
||||
let adjacent_end = Point::new(cursor_point.row + 1, 0);
|
||||
let adjacent_occurrences = text_similarity::Occurrences::within_string(
|
||||
&buffer
|
||||
.text_for_range(adjacent_start..adjacent_end)
|
||||
.collect::<String>(),
|
||||
);
|
||||
|
||||
let cursor_offset_in_file = cursor_point.to_offset(buffer);
|
||||
let cursor_offset_in_file = cursor_point.to_offset(buffer);
|
||||
|
||||
let declarations = if let Some(index_state) = index_state {
|
||||
let references = get_references(&excerpt, &excerpt_text, buffer);
|
||||
|
||||
scored_declarations(
|
||||
@@ -200,7 +195,6 @@ mod tests {
|
||||
buffer_snapshot,
|
||||
EditPredictionContextOptions {
|
||||
use_imports: true,
|
||||
use_references: true,
|
||||
excerpt: EditPredictionExcerptOptions {
|
||||
max_bytes: 60,
|
||||
min_bytes: 10,
|
||||
|
||||
@@ -318,24 +318,6 @@ pub struct GoToPreviousDiagnostic {
|
||||
pub severity: GoToDiagnosticSeverityFilter,
|
||||
}
|
||||
|
||||
/// Adds a cursor above the current selection.
|
||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = editor)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AddSelectionAbove {
|
||||
#[serde(default = "default_true")]
|
||||
pub skip_soft_wrap: bool,
|
||||
}
|
||||
|
||||
/// Adds a cursor below the current selection.
|
||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = editor)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AddSelectionBelow {
|
||||
#[serde(default = "default_true")]
|
||||
pub skip_soft_wrap: bool,
|
||||
}
|
||||
|
||||
actions!(
|
||||
debugger,
|
||||
[
|
||||
@@ -363,6 +345,10 @@ actions!(
|
||||
/// Accepts a partial edit prediction.
|
||||
#[action(deprecated_aliases = ["editor::AcceptPartialCopilotSuggestion"])]
|
||||
AcceptPartialEditPrediction,
|
||||
/// Adds a cursor above the current selection.
|
||||
AddSelectionAbove,
|
||||
/// Adds a cursor below the current selection.
|
||||
AddSelectionBelow,
|
||||
/// Applies all diff hunks in the editor.
|
||||
ApplyAllDiffHunks,
|
||||
/// Applies the diff hunk at the current position.
|
||||
|
||||
@@ -1401,26 +1401,6 @@ impl DisplaySnapshot {
|
||||
pub fn excerpt_header_height(&self) -> u32 {
|
||||
self.block_snapshot.excerpt_header_height
|
||||
}
|
||||
|
||||
/// Given a `DisplayPoint`, returns another `DisplayPoint` corresponding to
|
||||
/// the start of the buffer row that is a given number of buffer rows away
|
||||
/// from the provided point.
|
||||
///
|
||||
/// This moves by buffer rows instead of display rows, a distinction that is
|
||||
/// important when soft wrapping is enabled.
|
||||
pub fn start_of_relative_buffer_row(&self, point: DisplayPoint, times: isize) -> DisplayPoint {
|
||||
let start = self.display_point_to_fold_point(point, Bias::Left);
|
||||
let target = start.row() as isize + times;
|
||||
let new_row = (target.max(0) as u32).min(self.fold_snapshot().max_point().row());
|
||||
|
||||
self.clip_point(
|
||||
self.fold_point_to_display_point(
|
||||
self.fold_snapshot()
|
||||
.clip_point(FoldPoint::new(new_row, 0), Bias::Right),
|
||||
),
|
||||
Bias::Right,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
|
||||
@@ -3899,9 +3899,6 @@ impl Editor {
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if selection_ranges.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let ranges = match columnar_state {
|
||||
ColumnarSelectionState::FromMouse { .. } => {
|
||||
@@ -14239,29 +14236,23 @@ impl Editor {
|
||||
|
||||
pub fn add_selection_above(
|
||||
&mut self,
|
||||
action: &AddSelectionAbove,
|
||||
_: &AddSelectionAbove,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.add_selection(true, action.skip_soft_wrap, window, cx);
|
||||
self.add_selection(true, window, cx);
|
||||
}
|
||||
|
||||
pub fn add_selection_below(
|
||||
&mut self,
|
||||
action: &AddSelectionBelow,
|
||||
_: &AddSelectionBelow,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.add_selection(false, action.skip_soft_wrap, window, cx);
|
||||
self.add_selection(false, window, cx);
|
||||
}
|
||||
|
||||
fn add_selection(
|
||||
&mut self,
|
||||
above: bool,
|
||||
skip_soft_wrap: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
@@ -14348,19 +14339,12 @@ impl Editor {
|
||||
};
|
||||
|
||||
let mut maybe_new_selection = None;
|
||||
let direction = if above { -1 } else { 1 };
|
||||
|
||||
while row != end_row {
|
||||
if skip_soft_wrap {
|
||||
row = display_map
|
||||
.start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
|
||||
.row();
|
||||
} else if above {
|
||||
if above {
|
||||
row.0 -= 1;
|
||||
} else {
|
||||
row.0 += 1;
|
||||
}
|
||||
|
||||
if let Some(new_selection) = self.selections.build_columnar_selection(
|
||||
&display_map,
|
||||
row,
|
||||
|
||||
@@ -12509,7 +12509,6 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
// Set up a buffer white some trailing whitespace and no trailing newline.
|
||||
cx.set_state(
|
||||
&[
|
||||
@@ -25682,83 +25681,6 @@ async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppC
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
cx.set_state(indoc!(
|
||||
r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
|
||||
Second line here"#
|
||||
));
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
// Enable soft wrapping with a narrow width to force soft wrapping and
|
||||
// confirm that more than 2 rows are being displayed.
|
||||
editor.set_wrap_width(Some(100.0.into()), cx);
|
||||
assert!(editor.display_text(cx).lines().count() > 2);
|
||||
|
||||
editor.add_selection_below(
|
||||
&AddSelectionBelow {
|
||||
skip_soft_wrap: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
editor.selections.display_ranges(cx),
|
||||
&[
|
||||
DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
|
||||
DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
|
||||
]
|
||||
);
|
||||
|
||||
editor.add_selection_above(
|
||||
&AddSelectionAbove {
|
||||
skip_soft_wrap: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
editor.selections.display_ranges(cx),
|
||||
&[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
|
||||
);
|
||||
|
||||
editor.add_selection_below(
|
||||
&AddSelectionBelow {
|
||||
skip_soft_wrap: false,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
editor.selections.display_ranges(cx),
|
||||
&[
|
||||
DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
|
||||
DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
|
||||
]
|
||||
);
|
||||
|
||||
editor.add_selection_above(
|
||||
&AddSelectionAbove {
|
||||
skip_soft_wrap: false,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
editor.selections.display_ranges(cx),
|
||||
&[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_document_colors(cx: &mut TestAppContext) {
|
||||
let expected_color = Rgba {
|
||||
|
||||
@@ -7212,16 +7212,9 @@ impl EditorElement {
|
||||
* ScrollPixelOffset::from(max_glyph_advance)
|
||||
- ScrollPixelOffset::from(delta.x * scroll_sensitivity))
|
||||
/ ScrollPixelOffset::from(max_glyph_advance);
|
||||
|
||||
let scale_factor = window.scale_factor();
|
||||
let y = (current_scroll_position.y
|
||||
* ScrollPixelOffset::from(line_height)
|
||||
* ScrollPixelOffset::from(scale_factor)
|
||||
let y = (current_scroll_position.y * ScrollPixelOffset::from(line_height)
|
||||
- ScrollPixelOffset::from(delta.y * scroll_sensitivity))
|
||||
.round()
|
||||
/ ScrollPixelOffset::from(line_height)
|
||||
/ ScrollPixelOffset::from(scale_factor);
|
||||
|
||||
/ ScrollPixelOffset::from(line_height);
|
||||
let mut scroll_position =
|
||||
point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
|
||||
let forbid_vertical_scroll = editor.scroll_manager.forbid_vertical_scroll();
|
||||
|
||||
@@ -493,15 +493,22 @@ pub fn show_link_definition(
|
||||
}
|
||||
|
||||
let trigger_anchor = trigger_point.anchor();
|
||||
let anchor = snapshot.buffer_snapshot().anchor_before(*trigger_anchor);
|
||||
let Some(buffer) = editor.buffer().read(cx).buffer_for_anchor(anchor, cx) else {
|
||||
let Some((buffer, buffer_position)) = editor
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_anchor_for_position(*trigger_anchor, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Anchor {
|
||||
excerpt_id,
|
||||
text_anchor,
|
||||
..
|
||||
} = anchor;
|
||||
|
||||
let Some((excerpt_id, _, _)) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpt_containing(*trigger_anchor, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let same_kind = hovered_link_state.preferred_kind == preferred_kind
|
||||
|| hovered_link_state
|
||||
.links
|
||||
@@ -531,7 +538,7 @@ pub fn show_link_definition(
|
||||
async move {
|
||||
let result = match &trigger_point {
|
||||
TriggerPoint::Text(_) => {
|
||||
if let Some((url_range, url)) = find_url(&buffer, text_anchor, cx.clone()) {
|
||||
if let Some((url_range, url)) = find_url(&buffer, buffer_position, cx.clone()) {
|
||||
this.read_with(cx, |_, _| {
|
||||
let range = maybe!({
|
||||
let start =
|
||||
@@ -543,7 +550,7 @@ pub fn show_link_definition(
|
||||
})
|
||||
.ok()
|
||||
} else if let Some((filename_range, filename)) =
|
||||
find_file(&buffer, project.clone(), text_anchor, cx).await
|
||||
find_file(&buffer, project.clone(), buffer_position, cx).await
|
||||
{
|
||||
let range = maybe!({
|
||||
let start =
|
||||
@@ -555,7 +562,7 @@ pub fn show_link_definition(
|
||||
Some((range, vec![HoverLink::File(filename)]))
|
||||
} else if let Some(provider) = provider {
|
||||
let task = cx.update(|_, cx| {
|
||||
provider.definitions(&buffer, text_anchor, preferred_kind, cx)
|
||||
provider.definitions(&buffer, buffer_position, preferred_kind, cx)
|
||||
})?;
|
||||
if let Some(task) = task {
|
||||
task.await.ok().flatten().map(|definition_result| {
|
||||
|
||||
@@ -392,11 +392,6 @@ impl SelectionsCollection {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Attempts to build a selection in the provided `DisplayRow` within the
|
||||
/// same range as the provided range of `Pixels`.
|
||||
/// Returns `None` if the range is not empty but it starts past the line's
|
||||
/// length, meaning that the line isn't long enough to be contained within
|
||||
/// part of the provided range.
|
||||
pub fn build_columnar_selection(
|
||||
&mut self,
|
||||
display_map: &DisplaySnapshot,
|
||||
|
||||
@@ -7,7 +7,6 @@ pub mod fs_watcher;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use ashpd::desktop::trash;
|
||||
use futures::stream::iter;
|
||||
use gpui::App;
|
||||
use gpui::BackgroundExecutor;
|
||||
use gpui::Global;
|
||||
@@ -563,17 +562,12 @@ impl Fs for RealFs {
|
||||
|
||||
async fn load(&self, path: &Path) -> Result<String> {
|
||||
let path = path.to_path_buf();
|
||||
self.executor
|
||||
.spawn(async move { Ok(std::fs::read_to_string(path)?) })
|
||||
.await
|
||||
let text = smol::unblock(|| std::fs::read_to_string(path)).await?;
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>> {
|
||||
let path = path.to_path_buf();
|
||||
let bytes = self
|
||||
.executor
|
||||
.spawn(async move { std::fs::read(path) })
|
||||
.await?;
|
||||
let bytes = smol::unblock(|| std::fs::read(path)).await?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
@@ -641,46 +635,30 @@ impl Fs for RealFs {
|
||||
if let Some(path) = path.parent() {
|
||||
self.create_dir(path).await?;
|
||||
}
|
||||
let path = path.to_owned();
|
||||
let contents = content.to_owned();
|
||||
self.executor
|
||||
.spawn(async move {
|
||||
std::fs::write(path, contents)?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
smol::fs::write(path, content).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
|
||||
let path = path.to_owned();
|
||||
self.executor
|
||||
.spawn(async move {
|
||||
std::fs::canonicalize(&path).with_context(|| format!("canonicalizing {path:?}"))
|
||||
})
|
||||
Ok(smol::fs::canonicalize(path)
|
||||
.await
|
||||
.with_context(|| format!("canonicalizing {path:?}"))?)
|
||||
}
|
||||
|
||||
async fn is_file(&self, path: &Path) -> bool {
|
||||
let path = path.to_owned();
|
||||
self.executor
|
||||
.spawn(async move { std::fs::metadata(path).is_ok_and(|metadata| metadata.is_file()) })
|
||||
smol::fs::metadata(path)
|
||||
.await
|
||||
.is_ok_and(|metadata| metadata.is_file())
|
||||
}
|
||||
|
||||
async fn is_dir(&self, path: &Path) -> bool {
|
||||
let path = path.to_owned();
|
||||
self.executor
|
||||
.spawn(async move { std::fs::metadata(path).is_ok_and(|metadata| metadata.is_dir()) })
|
||||
smol::fs::metadata(path)
|
||||
.await
|
||||
.is_ok_and(|metadata| metadata.is_dir())
|
||||
}
|
||||
|
||||
async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
|
||||
let path_buf = path.to_owned();
|
||||
let symlink_metadata = match self
|
||||
.executor
|
||||
.spawn(async move { std::fs::symlink_metadata(&path_buf) })
|
||||
.await
|
||||
{
|
||||
let symlink_metadata = match smol::fs::symlink_metadata(path).await {
|
||||
Ok(metadata) => metadata,
|
||||
Err(err) => {
|
||||
return match (err.kind(), err.raw_os_error()) {
|
||||
@@ -691,28 +669,19 @@ impl Fs for RealFs {
|
||||
}
|
||||
};
|
||||
|
||||
let path_buf = path.to_path_buf();
|
||||
let path_exists = smol::unblock(move || {
|
||||
path_buf
|
||||
.try_exists()
|
||||
.with_context(|| format!("checking existence for path {path_buf:?}"))
|
||||
})
|
||||
.await?;
|
||||
let is_symlink = symlink_metadata.file_type().is_symlink();
|
||||
let metadata = if is_symlink {
|
||||
let path_buf = path.to_path_buf();
|
||||
let path_exists = self
|
||||
.executor
|
||||
.spawn(async move {
|
||||
path_buf
|
||||
.try_exists()
|
||||
.with_context(|| format!("checking existence for path {path_buf:?}"))
|
||||
})
|
||||
.await?;
|
||||
if path_exists {
|
||||
let path_buf = path.to_path_buf();
|
||||
self.executor
|
||||
.spawn(async move { std::fs::metadata(path_buf) })
|
||||
.await
|
||||
.with_context(|| "accessing symlink for path {path}")?
|
||||
} else {
|
||||
symlink_metadata
|
||||
}
|
||||
} else {
|
||||
symlink_metadata
|
||||
let metadata = match (is_symlink, path_exists) {
|
||||
(true, true) => smol::fs::metadata(path)
|
||||
.await
|
||||
.with_context(|| "accessing symlink for path {path}")?,
|
||||
_ => symlink_metadata,
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -738,11 +707,7 @@ impl Fs for RealFs {
|
||||
}
|
||||
|
||||
async fn read_link(&self, path: &Path) -> Result<PathBuf> {
|
||||
let path = path.to_owned();
|
||||
let path = self
|
||||
.executor
|
||||
.spawn(async move { std::fs::read_link(&path) })
|
||||
.await?;
|
||||
let path = smol::fs::read_link(path).await?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
@@ -750,13 +715,7 @@ impl Fs for RealFs {
|
||||
&self,
|
||||
path: &Path,
|
||||
) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
|
||||
let path = path.to_owned();
|
||||
let result = iter(
|
||||
self.executor
|
||||
.spawn(async move { std::fs::read_dir(path) })
|
||||
.await?,
|
||||
)
|
||||
.map(|entry| match entry {
|
||||
let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
|
||||
Ok(entry) => Ok(entry.path()),
|
||||
Err(error) => Err(anyhow!("failed to read dir entry {error:?}")),
|
||||
});
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "fs_benchmarks"
|
||||
version = "0.1.0"
|
||||
publish.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fs.workspace = true
|
||||
gpui = {workspace = true, features = ["windows-manifest"]}
|
||||
workspace-hack.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,32 +0,0 @@
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, Application};
|
||||
fn main() {
|
||||
let Some(path_to_read) = std::env::args().nth(1) else {
|
||||
println!("Expected path to read as 1st argument.");
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = Application::headless().run(|cx| {
|
||||
let fs = fs::RealFs::new(None, cx.background_executor().clone());
|
||||
cx.background_spawn(async move {
|
||||
let timer = std::time::Instant::now();
|
||||
let result = fs.load_bytes(path_to_read.as_ref()).await;
|
||||
let elapsed = timer.elapsed();
|
||||
if let Err(e) = result {
|
||||
println!("Failed `load_bytes` after {elapsed:?} with error `{e}`");
|
||||
} else {
|
||||
println!("Took {elapsed:?} to read {} bytes", result.unwrap().len());
|
||||
};
|
||||
let timer = std::time::Instant::now();
|
||||
let result = fs.metadata(path_to_read.as_ref()).await;
|
||||
let elapsed = timer.elapsed();
|
||||
if let Err(e) = result {
|
||||
println!("Failed `metadata` after {elapsed:?} with error `{e}`");
|
||||
} else {
|
||||
println!("Took {elapsed:?} to query metadata");
|
||||
};
|
||||
std::process::exit(0);
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
}
|
||||
@@ -374,6 +374,7 @@ impl DataTable {
|
||||
impl Render for DataTable {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.font_family(".SystemUIFont")
|
||||
.bg(gpui::white())
|
||||
.text_sm()
|
||||
.size_full()
|
||||
|
||||
@@ -20,6 +20,7 @@ impl Render for GradientViewer {
|
||||
let color_space = self.color_space;
|
||||
|
||||
div()
|
||||
.font_family(".SystemUIFont")
|
||||
.bg(gpui::white())
|
||||
.size_full()
|
||||
.p_4()
|
||||
|
||||
@@ -47,6 +47,7 @@ impl Render for ImageGallery {
|
||||
div()
|
||||
.image_cache(self.image_cache.clone())
|
||||
.id("main")
|
||||
.font_family(".SystemUIFont")
|
||||
.text_color(gpui::black())
|
||||
.bg(rgb(0xE9E9E9))
|
||||
.overflow_y_scroll()
|
||||
@@ -101,6 +102,7 @@ impl Render for ImageGallery {
|
||||
.child(image_cache(simple_lru_cache("lru-cache", IMAGES_IN_GALLERY)).child(
|
||||
div()
|
||||
.id("main")
|
||||
.font_family(".SystemUIFont")
|
||||
.bg(rgb(0xE9E9E9))
|
||||
.text_color(gpui::black())
|
||||
.overflow_y_scroll()
|
||||
|
||||
@@ -328,6 +328,7 @@ impl Render for PaintingViewer {
|
||||
let dashed = self.dashed;
|
||||
|
||||
div()
|
||||
.font_family(".SystemUIFont")
|
||||
.bg(gpui::white())
|
||||
.size_full()
|
||||
.p_4()
|
||||
|
||||
@@ -70,7 +70,6 @@ struct StateInner {
|
||||
#[allow(clippy::type_complexity)]
|
||||
scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut Window, &mut App)>>,
|
||||
scrollbar_drag_start_height: Option<Pixels>,
|
||||
measuring_behavior: ListMeasuringBehavior,
|
||||
}
|
||||
|
||||
/// Whether the list is scrolling from top to bottom or bottom to top.
|
||||
@@ -104,26 +103,6 @@ pub enum ListSizingBehavior {
|
||||
Auto,
|
||||
}
|
||||
|
||||
/// The measuring behavior to apply during layout.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum ListMeasuringBehavior {
|
||||
/// Measure all items in the list.
|
||||
/// Note: This can be expensive for the first frame in a large list.
|
||||
Measure(bool),
|
||||
/// Only measure visible items
|
||||
#[default]
|
||||
Visible,
|
||||
}
|
||||
|
||||
impl ListMeasuringBehavior {
|
||||
fn reset(&mut self) {
|
||||
match self {
|
||||
ListMeasuringBehavior::Measure(has_measured) => *has_measured = false,
|
||||
ListMeasuringBehavior::Visible => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The horizontal sizing behavior to apply during layout.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum ListHorizontalSizingBehavior {
|
||||
@@ -224,20 +203,11 @@ impl ListState {
|
||||
scroll_handler: None,
|
||||
reset: false,
|
||||
scrollbar_drag_start_height: None,
|
||||
measuring_behavior: ListMeasuringBehavior::default(),
|
||||
})));
|
||||
this.splice(0..0, item_count);
|
||||
this
|
||||
}
|
||||
|
||||
/// Set the list to measure all items in the list in the first layout phase.
|
||||
///
|
||||
/// This is useful for ensuring that the scrollbar size is correct instead of based on only rendered elements.
|
||||
pub fn measure_all(self) -> Self {
|
||||
self.0.borrow_mut().measuring_behavior = ListMeasuringBehavior::Measure(false);
|
||||
self
|
||||
}
|
||||
|
||||
/// Reset this instantiation of the list state.
|
||||
///
|
||||
/// Note that this will cause scroll events to be dropped until the next paint.
|
||||
@@ -245,7 +215,6 @@ impl ListState {
|
||||
let old_count = {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
state.reset = true;
|
||||
state.measuring_behavior.reset();
|
||||
state.logical_scroll_top = None;
|
||||
state.scrollbar_drag_start_height = None;
|
||||
state.items.summary().count
|
||||
@@ -555,48 +524,6 @@ impl StateInner {
|
||||
cursor.start().height + logical_scroll_top.offset_in_item
|
||||
}
|
||||
|
||||
fn layout_all_items(
|
||||
&mut self,
|
||||
available_width: Pixels,
|
||||
render_item: &mut RenderItemFn,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
match &mut self.measuring_behavior {
|
||||
ListMeasuringBehavior::Visible => {
|
||||
return;
|
||||
}
|
||||
ListMeasuringBehavior::Measure(has_measured) => {
|
||||
if *has_measured {
|
||||
return;
|
||||
}
|
||||
*has_measured = true;
|
||||
}
|
||||
}
|
||||
|
||||
let mut cursor = self.items.cursor::<Count>(());
|
||||
let available_item_space = size(
|
||||
AvailableSpace::Definite(available_width),
|
||||
AvailableSpace::MinContent,
|
||||
);
|
||||
|
||||
let mut measured_items = Vec::default();
|
||||
|
||||
for (ix, item) in cursor.enumerate() {
|
||||
let size = item.size().unwrap_or_else(|| {
|
||||
let mut element = render_item(ix, window, cx);
|
||||
element.layout_as_root(available_item_space, window, cx)
|
||||
});
|
||||
|
||||
measured_items.push(ListItem::Measured {
|
||||
size,
|
||||
focus_handle: item.focus_handle(),
|
||||
});
|
||||
}
|
||||
|
||||
self.items = SumTree::from_iter(measured_items, ());
|
||||
}
|
||||
|
||||
fn layout_items(
|
||||
&mut self,
|
||||
available_width: Option<Pixels>,
|
||||
@@ -784,13 +711,6 @@ impl StateInner {
|
||||
cx: &mut App,
|
||||
) -> Result<LayoutItemsResponse, ListOffset> {
|
||||
window.transact(|window| {
|
||||
match self.measuring_behavior {
|
||||
ListMeasuringBehavior::Measure(has_measured) if !has_measured => {
|
||||
self.layout_all_items(bounds.size.width, render_item, window, cx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut layout_response = self.layout_items(
|
||||
Some(bounds.size.width),
|
||||
bounds.size.height,
|
||||
|
||||
@@ -2679,15 +2679,6 @@ impl Pixels {
|
||||
Self(self.0.floor())
|
||||
}
|
||||
|
||||
/// Rounds the `Pixels` value to the nearest pixel accounting for scaling.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a new `Pixels` instance with the rounded value.
|
||||
pub fn round_pixel(&self, scale_factor: f32) -> Self {
|
||||
Self((self.0 * scale_factor).round() / scale_factor)
|
||||
}
|
||||
|
||||
/// Rounds the `Pixels` value to the nearest whole number.
|
||||
///
|
||||
/// # Returns
|
||||
|
||||
@@ -530,18 +530,8 @@ impl WindowsWindowInner {
|
||||
};
|
||||
let scale_factor = lock.scale_factor;
|
||||
let wheel_scroll_amount = match modifiers.shift {
|
||||
true => {
|
||||
self.system_settings
|
||||
.borrow()
|
||||
.mouse_wheel_settings
|
||||
.wheel_scroll_chars
|
||||
}
|
||||
false => {
|
||||
self.system_settings
|
||||
.borrow()
|
||||
.mouse_wheel_settings
|
||||
.wheel_scroll_lines
|
||||
}
|
||||
true => lock.system_settings.mouse_wheel_settings.wheel_scroll_chars,
|
||||
false => lock.system_settings.mouse_wheel_settings.wheel_scroll_lines,
|
||||
};
|
||||
drop(lock);
|
||||
|
||||
@@ -584,11 +574,7 @@ impl WindowsWindowInner {
|
||||
return Some(1);
|
||||
};
|
||||
let scale_factor = lock.scale_factor;
|
||||
let wheel_scroll_chars = self
|
||||
.system_settings
|
||||
.borrow()
|
||||
.mouse_wheel_settings
|
||||
.wheel_scroll_chars;
|
||||
let wheel_scroll_chars = lock.system_settings.mouse_wheel_settings.wheel_scroll_chars;
|
||||
drop(lock);
|
||||
|
||||
let wheel_distance =
|
||||
@@ -721,8 +707,11 @@ impl WindowsWindowInner {
|
||||
// used by Chrome. However, it may result in one row of pixels being obscured
|
||||
// in our client area. But as Chrome says, "there seems to be no better solution."
|
||||
if is_maximized
|
||||
&& let Some(ref taskbar_position) =
|
||||
self.system_settings.borrow().auto_hide_taskbar_position
|
||||
&& let Some(ref taskbar_position) = self
|
||||
.state
|
||||
.borrow()
|
||||
.system_settings
|
||||
.auto_hide_taskbar_position
|
||||
{
|
||||
// For the auto-hide taskbar, adjust in by 1 pixel on taskbar edge,
|
||||
// so the window isn't treated as a "fullscreen app", which would cause
|
||||
@@ -1112,11 +1101,9 @@ impl WindowsWindowInner {
|
||||
if wparam.0 != 0 {
|
||||
let mut lock = self.state.borrow_mut();
|
||||
let display = lock.display;
|
||||
lock.system_settings.update(display, wparam.0);
|
||||
lock.click_state.system_update(wparam.0);
|
||||
lock.border_offset.update(handle).log_err();
|
||||
// system settings may emit a window message which wants to take the refcell lock, so drop it
|
||||
drop(lock);
|
||||
self.system_settings.borrow_mut().update(display, wparam.0);
|
||||
} else {
|
||||
self.handle_system_theme_changed(handle, lparam)?;
|
||||
};
|
||||
|
||||
@@ -51,6 +51,7 @@ pub struct WindowsWindowState {
|
||||
pub renderer: DirectXRenderer,
|
||||
|
||||
pub click_state: ClickState,
|
||||
pub system_settings: WindowsSystemSettings,
|
||||
pub current_cursor: Option<HCURSOR>,
|
||||
pub nc_button_pressed: Option<u32>,
|
||||
|
||||
@@ -65,7 +66,6 @@ pub(crate) struct WindowsWindowInner {
|
||||
pub(super) this: Weak<Self>,
|
||||
drop_target_helper: IDropTargetHelper,
|
||||
pub(crate) state: RefCell<WindowsWindowState>,
|
||||
pub(crate) system_settings: RefCell<WindowsSystemSettings>,
|
||||
pub(crate) handle: AnyWindowHandle,
|
||||
pub(crate) hide_title_bar: bool,
|
||||
pub(crate) is_movable: bool,
|
||||
@@ -115,6 +115,7 @@ impl WindowsWindowState {
|
||||
let system_key_handled = false;
|
||||
let hovered = false;
|
||||
let click_state = ClickState::new();
|
||||
let system_settings = WindowsSystemSettings::new(display);
|
||||
let nc_button_pressed = None;
|
||||
let fullscreen = None;
|
||||
let initial_placement = None;
|
||||
@@ -137,6 +138,7 @@ impl WindowsWindowState {
|
||||
hovered,
|
||||
renderer,
|
||||
click_state,
|
||||
system_settings,
|
||||
current_cursor,
|
||||
nc_button_pressed,
|
||||
display,
|
||||
@@ -229,7 +231,6 @@ impl WindowsWindowInner {
|
||||
validation_number: context.validation_number,
|
||||
main_receiver: context.main_receiver.clone(),
|
||||
platform_window_handle: context.platform_window_handle,
|
||||
system_settings: RefCell::new(WindowsSystemSettings::new(context.display)),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -643,12 +644,10 @@ impl PlatformWindow for WindowsWindow {
|
||||
let mut btn_encoded = Vec::new();
|
||||
for (index, btn) in answers.iter().enumerate() {
|
||||
let encoded = HSTRING::from(btn.label().as_ref());
|
||||
let button_id = match btn {
|
||||
PromptButton::Ok(_) => IDOK.0,
|
||||
PromptButton::Cancel(_) => IDCANCEL.0,
|
||||
// the first few low integer values are reserved for known buttons
|
||||
// so for simplicity we just go backwards from -1
|
||||
PromptButton::Other(_) => -(index as i32) - 1,
|
||||
let button_id = if btn.is_cancel() {
|
||||
IDCANCEL.0
|
||||
} else {
|
||||
index as i32 - 100
|
||||
};
|
||||
button_id_map.push(button_id);
|
||||
buttons.push(TASKDIALOG_BUTTON {
|
||||
@@ -666,11 +665,11 @@ impl PlatformWindow for WindowsWindow {
|
||||
.context("unable to create task dialog")
|
||||
.log_err();
|
||||
|
||||
if let Some(clicked) =
|
||||
button_id_map.iter().position(|&button_id| button_id == res)
|
||||
{
|
||||
let _ = done_tx.send(clicked);
|
||||
}
|
||||
let clicked = button_id_map
|
||||
.iter()
|
||||
.position(|&button_id| button_id == res)
|
||||
.unwrap();
|
||||
let _ = done_tx.send(clicked);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -403,7 +403,13 @@ impl Default for TextStyle {
|
||||
TextStyle {
|
||||
color: black(),
|
||||
// todo(linux) make this configurable or choose better default
|
||||
font_family: ".SystemUIFont".into(),
|
||||
font_family: if cfg!(any(target_os = "linux", target_os = "freebsd")) {
|
||||
"FreeMono".into()
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"Segoe UI".into()
|
||||
} else {
|
||||
"Helvetica".into()
|
||||
},
|
||||
font_features: FontFeatures::default(),
|
||||
font_fallbacks: None,
|
||||
font_size: rems(1.).into(),
|
||||
|
||||
@@ -75,13 +75,11 @@ impl TextSystem {
|
||||
font(".ZedMono"),
|
||||
font(".ZedSans"),
|
||||
font("Helvetica"),
|
||||
font("Segoe UI"), // Windows
|
||||
font("Ubuntu"), // Gnome (Ubuntu)
|
||||
font("Adwaita Sans"), // Gnome 47
|
||||
font("Cantarell"), // Gnome
|
||||
font("Noto Sans"), // KDE
|
||||
font("DejaVu Sans"),
|
||||
font("Arial"), // macOS, Windows
|
||||
font("Segoe UI"), // Windows
|
||||
font("Cantarell"), // Gnome
|
||||
font("Ubuntu"), // Gnome (Ubuntu)
|
||||
font("Noto Sans"), // KDE
|
||||
font("DejaVu Sans")
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,9 +196,8 @@ fn paint_line(
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Result<()> {
|
||||
let scale_factor = window.scale_factor();
|
||||
let line_bounds = Bounds::new(
|
||||
point(origin.x, origin.y.round_pixel(scale_factor)),
|
||||
origin,
|
||||
size(
|
||||
layout.width,
|
||||
line_height * (wrap_boundaries.len() as f32 + 1.),
|
||||
@@ -206,6 +205,7 @@ fn paint_line(
|
||||
);
|
||||
window.paint_layer(line_bounds, |window| {
|
||||
let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
|
||||
let baseline_offset = point(px(0.), padding_top + layout.ascent);
|
||||
let mut decoration_runs = decoration_runs.iter();
|
||||
let mut wraps = wrap_boundaries.iter().peekable();
|
||||
let mut run_end = 0;
|
||||
@@ -213,11 +213,6 @@ fn paint_line(
|
||||
let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||
let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
|
||||
let text_system = cx.text_system().clone();
|
||||
let scale_factor = window.scale_factor();
|
||||
let baseline_offset = point(
|
||||
px(0.),
|
||||
(padding_top + layout.ascent).round_pixel(scale_factor),
|
||||
);
|
||||
let mut glyph_origin = point(
|
||||
aligned_origin_x(
|
||||
origin,
|
||||
@@ -227,7 +222,7 @@ fn paint_line(
|
||||
layout,
|
||||
wraps.peek(),
|
||||
),
|
||||
origin.y.round_pixel(scale_factor),
|
||||
origin.y,
|
||||
);
|
||||
let mut prev_glyph_position = Point::default();
|
||||
let mut max_glyph_size = size(px(0.), px(0.));
|
||||
@@ -278,7 +273,7 @@ fn paint_line(
|
||||
layout,
|
||||
wraps.peek(),
|
||||
);
|
||||
glyph_origin.y = (glyph_origin.y + line_height).round_pixel(scale_factor);
|
||||
glyph_origin.y += line_height;
|
||||
}
|
||||
prev_glyph_position = glyph.position;
|
||||
|
||||
|
||||
@@ -2982,7 +2982,7 @@ impl Window {
|
||||
})?
|
||||
.expect("Callback above only errors or returns Some");
|
||||
let bounds = Bounds {
|
||||
origin: glyph_origin + raster_bounds.origin.map(Into::into),
|
||||
origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
|
||||
size: tile.bounds.size.map(Into::into),
|
||||
};
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
|
||||
@@ -23,9 +23,7 @@ use gpui::{
|
||||
use language::{Language, LanguageConfig, ToOffset as _};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use project::{CompletionDisplayOptions, Project};
|
||||
use settings::{
|
||||
BaseKeymap, KeybindSource, KeymapFile, Settings as _, SettingsAssets, infer_json_indent_size,
|
||||
};
|
||||
use settings::{BaseKeymap, KeybindSource, KeymapFile, Settings as _, SettingsAssets};
|
||||
use ui::{
|
||||
ActiveTheme as _, App, Banner, BorrowAppContext, ContextMenu, IconButtonShape, Indicator,
|
||||
Modal, ModalFooter, ModalHeader, ParentElement as _, PopoverMenu, Render, Section,
|
||||
@@ -1200,12 +1198,13 @@ impl KeymapEditor {
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
|
||||
self.previous_edit = Some(PreviousEdit::ScrollBarOffset(
|
||||
self.table_interaction_state.read(cx).scroll_offset(),
|
||||
));
|
||||
let keyboard_mapper = cx.keyboard_mapper().clone();
|
||||
cx.spawn(async move |_, _| {
|
||||
remove_keybinding(to_remove, &fs, keyboard_mapper.as_ref()).await
|
||||
remove_keybinding(to_remove, &fs, tab_size, keyboard_mapper.as_ref()).await
|
||||
})
|
||||
.detach_and_notify_err(window, cx);
|
||||
}
|
||||
@@ -2289,6 +2288,7 @@ impl KeybindingEditorModal {
|
||||
fn save(&mut self, cx: &mut Context<Self>) -> Result<(), InputError> {
|
||||
let existing_keybind = self.editing_keybind.clone();
|
||||
let fs = self.fs.clone();
|
||||
let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
|
||||
|
||||
let mut new_keystrokes = self.validate_keystrokes(cx).map_err(InputError::error)?;
|
||||
new_keystrokes
|
||||
@@ -2367,6 +2367,7 @@ impl KeybindingEditorModal {
|
||||
&action_mapping,
|
||||
new_action_args.as_deref(),
|
||||
&fs,
|
||||
tab_size,
|
||||
keyboard_mapper.as_ref(),
|
||||
)
|
||||
.await
|
||||
@@ -3018,14 +3019,13 @@ async fn save_keybinding_update(
|
||||
action_mapping: &ActionMapping,
|
||||
new_args: Option<&str>,
|
||||
fs: &Arc<dyn Fs>,
|
||||
tab_size: usize,
|
||||
keyboard_mapper: &dyn PlatformKeyboardMapper,
|
||||
) -> anyhow::Result<()> {
|
||||
let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
|
||||
.await
|
||||
.context("Failed to load keymap file")?;
|
||||
|
||||
let tab_size = infer_json_indent_size(&keymap_contents);
|
||||
|
||||
let existing_keystrokes = existing.keystrokes().unwrap_or_default();
|
||||
let existing_context = existing.context().and_then(KeybindContextString::local_str);
|
||||
let existing_args = existing
|
||||
@@ -3089,6 +3089,7 @@ async fn save_keybinding_update(
|
||||
async fn remove_keybinding(
|
||||
existing: ProcessedBinding,
|
||||
fs: &Arc<dyn Fs>,
|
||||
tab_size: usize,
|
||||
keyboard_mapper: &dyn PlatformKeyboardMapper,
|
||||
) -> anyhow::Result<()> {
|
||||
let Some(keystrokes) = existing.keystrokes() else {
|
||||
@@ -3097,7 +3098,6 @@ async fn remove_keybinding(
|
||||
let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
|
||||
.await
|
||||
.context("Failed to load keymap file")?;
|
||||
let tab_size = infer_json_indent_size(&keymap_contents);
|
||||
|
||||
let operation = settings::KeybindUpdateOperation::Remove {
|
||||
target: settings::KeybindUpdateTarget {
|
||||
|
||||
@@ -810,11 +810,15 @@ impl LanguageModel for CloudLanguageModel {
|
||||
}
|
||||
cloud_llm_client::LanguageModelProvider::OpenAi => {
|
||||
let client = self.client.clone();
|
||||
let model = match open_ai::Model::from_id(&self.model.id.0) {
|
||||
Ok(model) => model,
|
||||
Err(err) => return async move { Err(anyhow!(err).into()) }.boxed(),
|
||||
};
|
||||
let request = into_open_ai(
|
||||
request,
|
||||
&self.model.id.0,
|
||||
self.model.supports_parallel_tool_calls,
|
||||
true,
|
||||
model.id(),
|
||||
model.supports_parallel_tool_calls(),
|
||||
model.supports_prompt_cache_key(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
@@ -856,11 +860,15 @@ impl LanguageModel for CloudLanguageModel {
|
||||
}
|
||||
cloud_llm_client::LanguageModelProvider::XAi => {
|
||||
let client = self.client.clone();
|
||||
let model = match x_ai::Model::from_id(&self.model.id.0) {
|
||||
Ok(model) => model,
|
||||
Err(err) => return async move { Err(anyhow!(err).into()) }.boxed(),
|
||||
};
|
||||
let request = into_open_ai(
|
||||
request,
|
||||
&self.model.id.0,
|
||||
self.model.supports_parallel_tool_calls,
|
||||
false,
|
||||
model.id(),
|
||||
model.supports_parallel_tool_calls(),
|
||||
model.supports_prompt_cache_key(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
@@ -222,7 +222,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
Some((lsp::CompletionItemKind::MODULE, detail)) => {
|
||||
let text = format!("{label} {detail}");
|
||||
let source = Rope::from(format!("import {text}").as_str());
|
||||
let runs = language.highlight_text(&source, 7..7 + text[name_offset..].len());
|
||||
let runs = language.highlight_text(&source, 7..7 + text.len());
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
.as_deref()
|
||||
@@ -242,7 +242,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
Rope::from(format!("var {} {}", &text[name_offset..], detail).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 4..4 + text[name_offset..].len()),
|
||||
language.highlight_text(&source, 4..4 + text.len()),
|
||||
);
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
@@ -259,7 +259,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 5..5 + text[name_offset..].len()),
|
||||
language.highlight_text(&source, 5..5 + text.len()),
|
||||
);
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
@@ -276,7 +276,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 5..5 + text[name_offset..].len()),
|
||||
language.highlight_text(&source, 5..5 + text.len()),
|
||||
);
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
@@ -294,7 +294,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
Rope::from(format!("type T struct {{ {} }}", &text[name_offset..]).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 16..16 + text[name_offset..].len()),
|
||||
language.highlight_text(&source, 16..16 + text.len()),
|
||||
);
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
@@ -312,7 +312,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
let source = Rope::from(format!("func {} {{}}", &text[name_offset..]).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 5..5 + text[name_offset..].len()),
|
||||
language.highlight_text(&source, 5..5 + text.len()),
|
||||
);
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
|
||||
@@ -403,9 +403,6 @@ impl<'a> MarkdownParser<'a> {
|
||||
if let Some(mut image) = image.take() {
|
||||
if !text.is_empty() {
|
||||
image.set_alt_text(std::mem::take(&mut text).into());
|
||||
mem::take(&mut highlights);
|
||||
mem::take(&mut region_ranges);
|
||||
mem::take(&mut regions);
|
||||
}
|
||||
markdown_text_like.push(MarkdownParagraphChunk::Image(image));
|
||||
}
|
||||
@@ -1274,40 +1271,17 @@ mod tests {
|
||||
panic!("Expected a paragraph");
|
||||
};
|
||||
assert_eq!(
|
||||
paragraph[0],
|
||||
MarkdownParagraphChunk::Image(Image {
|
||||
source_range: 0..111,
|
||||
link: Link::Web {
|
||||
url: "https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png".to_string(),
|
||||
},
|
||||
alt_text: Some("test".into()),
|
||||
height: None,
|
||||
width: None,
|
||||
},)
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_image_alt_text() {
|
||||
let parsed = parse("[](https://zed.dev)\n ").await;
|
||||
|
||||
let paragraph = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] {
|
||||
text
|
||||
} else {
|
||||
panic!("Expected a paragraph");
|
||||
};
|
||||
assert_eq!(
|
||||
paragraph[0],
|
||||
MarkdownParagraphChunk::Image(Image {
|
||||
source_range: 0..142,
|
||||
link: Link::Web {
|
||||
url: "https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/zed-industries/zed/main/assets/badge/v0.json".to_string(),
|
||||
},
|
||||
alt_text: Some("Zed".into()),
|
||||
height: None,
|
||||
width: None,
|
||||
},)
|
||||
);
|
||||
paragraph[0],
|
||||
MarkdownParagraphChunk::Image(Image {
|
||||
source_range: 0..111,
|
||||
link: Link::Web {
|
||||
url: "https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png".to_string(),
|
||||
},
|
||||
alt_text: Some("test".into()),
|
||||
height: None,
|
||||
width: None,
|
||||
},)
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -61,7 +61,6 @@ pub struct RenderContext {
|
||||
syntax_theme: Arc<SyntaxTheme>,
|
||||
indent: usize,
|
||||
checkbox_clicked_callback: Option<CheckboxClickedCallback>,
|
||||
is_last_child: bool,
|
||||
}
|
||||
|
||||
impl RenderContext {
|
||||
@@ -95,7 +94,6 @@ impl RenderContext {
|
||||
code_block_background_color: theme.colors().surface_background,
|
||||
code_span_background_color: theme.colors().editor_document_highlight_read_background,
|
||||
checkbox_clicked_callback: None,
|
||||
is_last_child: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,25 +135,12 @@ impl RenderContext {
|
||||
/// We give padding between "This is a block quote."
|
||||
/// and "And this is the next paragraph."
|
||||
fn with_common_p(&self, element: Div) -> Div {
|
||||
if self.indent > 0 && !self.is_last_child {
|
||||
if self.indent > 0 {
|
||||
element.pb(self.scaled_rems(0.75))
|
||||
} else {
|
||||
element
|
||||
}
|
||||
}
|
||||
|
||||
/// The is used to indicate that the current element is the last child or not of its parent.
|
||||
///
|
||||
/// Then we can avoid adding padding to the bottom of the last child.
|
||||
fn with_last_child<R>(&mut self, is_last: bool, render: R) -> AnyElement
|
||||
where
|
||||
R: FnOnce(&mut Self) -> AnyElement,
|
||||
{
|
||||
self.is_last_child = is_last;
|
||||
let element = render(self);
|
||||
self.is_last_child = false;
|
||||
element
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_parsed_markdown(
|
||||
@@ -600,12 +585,7 @@ fn render_markdown_block_quote(
|
||||
let children: Vec<AnyElement> = parsed
|
||||
.children
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(ix, child)| {
|
||||
cx.with_last_child(ix + 1 == parsed.children.len(), |cx| {
|
||||
render_markdown_block(child, cx)
|
||||
})
|
||||
})
|
||||
.map(|child| render_markdown_block(child, cx))
|
||||
.collect();
|
||||
|
||||
cx.indent -= 1;
|
||||
|
||||
@@ -103,7 +103,7 @@ pub(crate) mod m_2025_07_08 {
|
||||
pub(crate) mod m_2025_10_01 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::flatten_code_actions_formatters;
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_10_02 {
|
||||
|
||||
@@ -1,74 +1,109 @@
|
||||
use crate::patterns::migrate_language_setting;
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
pub fn flatten_code_actions_formatters(value: &mut Value) -> Result<()> {
|
||||
migrate_language_setting(value, |value, _path| {
|
||||
let Some(obj) = value.as_object_mut() else {
|
||||
return Ok(());
|
||||
use crate::MigrationPatterns;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns =
|
||||
&[(FORMATTER_PATTERN, migrate_code_action_formatters)];
|
||||
|
||||
const FORMATTER_PATTERN: &str = r#"
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @formatter) (#any-of? @formatter "formatter" "format_on_save")
|
||||
value: [
|
||||
(array
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @code-actions-key) (#eq? @code-actions-key "code_actions")
|
||||
value: (object
|
||||
((pair) @code-action ","?)*
|
||||
)
|
||||
)
|
||||
) @code-actions-obj
|
||||
) @formatter-array
|
||||
(object
|
||||
(pair
|
||||
key: (string (string_content) @code-actions-key) (#eq? @code-actions-key "code_actions")
|
||||
value: (object
|
||||
((pair) @code-action ","?)*
|
||||
)
|
||||
)
|
||||
) @code-actions-obj
|
||||
]
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
pub fn migrate_code_action_formatters(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let code_actions_obj_ix = query.capture_index_for_name("code-actions-obj")?;
|
||||
let code_actions_obj_node = mat.nodes_for_capture_index(code_actions_obj_ix).next()?;
|
||||
|
||||
let mut code_actions = vec![];
|
||||
|
||||
let code_actions_ix = query.capture_index_for_name("code-action")?;
|
||||
for code_action_node in mat.nodes_for_capture_index(code_actions_ix) {
|
||||
let Some(enabled) = code_action_node
|
||||
.child_by_field_name("value")
|
||||
.map(|n| n.kind() != "false")
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
for key in ["formatter", "format_on_save"] {
|
||||
let Some(formatter) = obj.get_mut(key) else {
|
||||
continue;
|
||||
};
|
||||
let new_formatter = match formatter {
|
||||
Value::Array(arr) => {
|
||||
let mut new_arr = Vec::new();
|
||||
let mut found_code_actions = false;
|
||||
for item in arr {
|
||||
let Some(obj) = item.as_object() else {
|
||||
new_arr.push(item.clone());
|
||||
continue;
|
||||
};
|
||||
let code_actions_obj = obj
|
||||
.get("code_actions")
|
||||
.and_then(|code_actions| code_actions.as_object());
|
||||
let Some(code_actions) = code_actions_obj else {
|
||||
new_arr.push(item.clone());
|
||||
continue;
|
||||
};
|
||||
found_code_actions = true;
|
||||
for (name, enabled) in code_actions {
|
||||
if !enabled.as_bool().unwrap_or(true) {
|
||||
continue;
|
||||
}
|
||||
new_arr.push(serde_json::json!({
|
||||
"code_action": name
|
||||
}));
|
||||
}
|
||||
}
|
||||
if !found_code_actions {
|
||||
continue;
|
||||
}
|
||||
Value::Array(new_arr)
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
let mut new_arr = Vec::new();
|
||||
let code_actions_obj = obj
|
||||
.get("code_actions")
|
||||
.and_then(|code_actions| code_actions.as_object());
|
||||
let Some(code_actions) = code_actions_obj else {
|
||||
continue;
|
||||
};
|
||||
for (name, enabled) in code_actions {
|
||||
if !enabled.as_bool().unwrap_or(true) {
|
||||
continue;
|
||||
}
|
||||
new_arr.push(serde_json::json!({
|
||||
"code_action": name
|
||||
}));
|
||||
}
|
||||
if new_arr.len() == 1 {
|
||||
new_arr.pop().unwrap()
|
||||
} else {
|
||||
Value::Array(new_arr)
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
obj.insert(key.to_string(), new_formatter);
|
||||
if !enabled {
|
||||
continue;
|
||||
}
|
||||
return Ok(());
|
||||
})
|
||||
let Some(name) = code_action_node
|
||||
.child_by_field_name("key")
|
||||
.and_then(|n| n.child(1))
|
||||
.map(|n| &contents[n.byte_range()])
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
code_actions.push(name);
|
||||
}
|
||||
|
||||
let indent = query
|
||||
.capture_index_for_name("formatter")
|
||||
.and_then(|ix| mat.nodes_for_capture_index(ix).next())
|
||||
.map(|node| node.start_position().column + 1)
|
||||
.unwrap_or(2);
|
||||
|
||||
let mut code_actions_str = code_actions
|
||||
.into_iter()
|
||||
.map(|code_action| format!(r#"{{ "code_action": "{}" }}"#, code_action))
|
||||
.collect::<Vec<_>>()
|
||||
.join(&format!(",\n{}", " ".repeat(indent)));
|
||||
let is_array = query
|
||||
.capture_index_for_name("formatter-array")
|
||||
.map(|ix| mat.nodes_for_capture_index(ix).count() > 0)
|
||||
.unwrap_or(false);
|
||||
if !is_array {
|
||||
code_actions_str.insert_str(0, &" ".repeat(indent));
|
||||
code_actions_str.insert_str(0, "[\n");
|
||||
code_actions_str.push('\n');
|
||||
code_actions_str.push_str(&" ".repeat(indent.saturating_sub(2)));
|
||||
code_actions_str.push_str("]");
|
||||
}
|
||||
let mut replace_range = code_actions_obj_node.byte_range();
|
||||
if is_array && code_actions_str.is_empty() {
|
||||
let mut cursor = code_actions_obj_node.parent().unwrap().walk();
|
||||
cursor.goto_first_child();
|
||||
while cursor.node().id() != code_actions_obj_node.id() && cursor.goto_next_sibling() {}
|
||||
while cursor.goto_next_sibling()
|
||||
&& (cursor.node().is_extra()
|
||||
|| cursor.node().is_missing()
|
||||
|| cursor.node().kind() == "comment")
|
||||
{}
|
||||
if cursor.node().kind() == "," {
|
||||
// found comma, delete up to next node
|
||||
while cursor.goto_next_sibling()
|
||||
&& (cursor.node().is_extra() || cursor.node().is_missing())
|
||||
{}
|
||||
replace_range.end = cursor.node().range().start_byte;
|
||||
}
|
||||
}
|
||||
Some((replace_range, code_actions_str))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::patterns::migrate_language_setting;
|
||||
|
||||
pub fn remove_formatters_on_save(value: &mut Value) -> Result<()> {
|
||||
migrate_language_setting(value, remove_formatters_on_save_inner)
|
||||
remove_formatters_on_save_inner(value, &[])?;
|
||||
let languages = value
|
||||
.as_object_mut()
|
||||
.and_then(|obj| obj.get_mut("languages"))
|
||||
.and_then(|languages| languages.as_object_mut());
|
||||
if let Some(languages) = languages {
|
||||
for (language_name, language) in languages.iter_mut() {
|
||||
let path = vec!["languages", language_name];
|
||||
remove_formatters_on_save_inner(language, &path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_formatters_on_save_inner(value: &mut Value, path: &[&str]) -> Result<()> {
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::patterns::migrate_language_setting;
|
||||
|
||||
pub fn remove_code_actions_on_format(value: &mut Value) -> Result<()> {
|
||||
migrate_language_setting(value, remove_code_actions_on_format_inner)
|
||||
remove_code_actions_on_format_inner(value, &[])?;
|
||||
let languages = value
|
||||
.as_object_mut()
|
||||
.and_then(|obj| obj.get_mut("languages"))
|
||||
.and_then(|languages| languages.as_object_mut());
|
||||
if let Some(languages) = languages {
|
||||
for (language_name, language) in languages.iter_mut() {
|
||||
let path = vec!["languages", language_name];
|
||||
remove_code_actions_on_format_inner(language, &path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_code_actions_on_format_inner(value: &mut Value, path: &[&str]) -> Result<()> {
|
||||
|
||||
@@ -74,7 +74,6 @@ fn run_migrations(text: &str, migrations: &[MigrationType]) -> Result<Option<Str
|
||||
|
||||
let mut current_text = text.to_string();
|
||||
let mut result: Option<String> = None;
|
||||
let json_indent_size = settings::infer_json_indent_size(¤t_text);
|
||||
for migration in migrations.iter() {
|
||||
let migrated_text = match migration {
|
||||
MigrationType::TreeSitter(patterns, query) => migrate(¤t_text, patterns, query)?,
|
||||
@@ -93,7 +92,7 @@ fn run_migrations(text: &str, migrations: &[MigrationType]) -> Result<Option<Str
|
||||
settings::update_value_in_json_text(
|
||||
&mut current,
|
||||
&mut vec![],
|
||||
json_indent_size,
|
||||
2,
|
||||
&old_value,
|
||||
&new_value,
|
||||
&mut edits,
|
||||
@@ -205,7 +204,10 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
|
||||
migrations::m_2025_07_08::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_07_08,
|
||||
),
|
||||
MigrationType::Json(migrations::m_2025_10_01::flatten_code_actions_formatters),
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_10_01::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_10_01,
|
||||
),
|
||||
MigrationType::Json(migrations::m_2025_10_02::remove_formatters_on_save),
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_10_03::SETTINGS_PATTERNS,
|
||||
@@ -326,6 +328,10 @@ define_query!(
|
||||
SETTINGS_QUERY_2025_07_08,
|
||||
migrations::m_2025_07_08::SETTINGS_PATTERNS
|
||||
);
|
||||
define_query!(
|
||||
SETTINGS_QUERY_2025_10_01,
|
||||
migrations::m_2025_10_01::SETTINGS_PATTERNS
|
||||
);
|
||||
define_query!(
|
||||
SETTINGS_QUERY_2025_10_03,
|
||||
migrations::m_2025_10_03::SETTINGS_PATTERNS
|
||||
@@ -345,11 +351,10 @@ mod tests {
|
||||
use super::*;
|
||||
use unindent::Unindent as _;
|
||||
|
||||
#[track_caller]
|
||||
fn assert_migrated_correctly(migrated: Option<String>, expected: Option<&str>) {
|
||||
match (&migrated, &expected) {
|
||||
(Some(migrated), Some(expected)) => {
|
||||
pretty_assertions::assert_str_eq!(expected, migrated);
|
||||
pretty_assertions::assert_str_eq!(migrated, expected);
|
||||
}
|
||||
_ => {
|
||||
pretty_assertions::assert_eq!(migrated.as_deref(), expected);
|
||||
@@ -367,7 +372,6 @@ mod tests {
|
||||
assert_migrated_correctly(migrated, output);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_migrate_settings_with_migrations(
|
||||
migrations: &[MigrationType],
|
||||
input: &str,
|
||||
@@ -1339,28 +1343,24 @@ mod tests {
|
||||
fn test_flatten_code_action_formatters_basic_array() {
|
||||
assert_migrate_settings(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "included-1"
|
||||
},
|
||||
{
|
||||
"code_action": "included-2"
|
||||
}
|
||||
]
|
||||
}"#
|
||||
"formatter": [
|
||||
{ "code_action": "included-1" },
|
||||
{ "code_action": "included-2" }
|
||||
]
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
@@ -1370,25 +1370,21 @@ mod tests {
|
||||
fn test_flatten_code_action_formatters_basic_object() {
|
||||
assert_migrate_settings(
|
||||
&r#"{
|
||||
"formatter": {
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"excluded": false,
|
||||
"included-2": true
|
||||
}
|
||||
}
|
||||
}"#
|
||||
"formatter": {
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"excluded": false,
|
||||
"included-2": true
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "included-1"
|
||||
},
|
||||
{
|
||||
"code_action": "included-2"
|
||||
}
|
||||
]
|
||||
"formatter": [
|
||||
{ "code_action": "included-1" },
|
||||
{ "code_action": "included-2" }
|
||||
]
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
@@ -1398,57 +1394,47 @@ mod tests {
|
||||
#[test]
|
||||
fn test_flatten_code_action_formatters_array_with_multiple_action_blocks() {
|
||||
assert_migrate_settings(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
}
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
]
|
||||
}"#
|
||||
.unindent(),
|
||||
r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
}
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
]
|
||||
}"#,
|
||||
Some(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "included-1"
|
||||
},
|
||||
{
|
||||
"code_action": "included-2"
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_action": "included-3"
|
||||
},
|
||||
{
|
||||
"code_action": "included-4"
|
||||
}
|
||||
]
|
||||
}"#
|
||||
.unindent(),
|
||||
r#"{
|
||||
"formatter": [
|
||||
{ "code_action": "included-1" },
|
||||
{ "code_action": "included-2" },
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{ "code_action": "included-3" },
|
||||
{ "code_action": "included-4" },
|
||||
]
|
||||
}"#,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1457,63 +1443,55 @@ mod tests {
|
||||
fn test_flatten_code_action_formatters_array_with_multiple_action_blocks_in_languages() {
|
||||
assert_migrate_settings(
|
||||
&r#"{
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
}
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "included-1"
|
||||
},
|
||||
{
|
||||
"code_action": "included-2"
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_action": "included-3"
|
||||
},
|
||||
{
|
||||
"code_action": "included-4"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"formatter": [
|
||||
{ "code_action": "included-1" },
|
||||
{ "code_action": "included-2" },
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{ "code_action": "included-3" },
|
||||
{ "code_action": "included-4" },
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
@@ -1524,120 +1502,100 @@ mod tests {
|
||||
{
|
||||
assert_migrate_settings(
|
||||
&r#"{
|
||||
"formatter": {
|
||||
"code_actions": {
|
||||
"default-1": true,
|
||||
"default-2": true,
|
||||
"default-3": true,
|
||||
"default-4": true,
|
||||
}
|
||||
},
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
"formatter": {
|
||||
"code_actions": {
|
||||
"default-1": true,
|
||||
"default-2": true,
|
||||
"default-3": true,
|
||||
"default-4": true,
|
||||
}
|
||||
},
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
}
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
"Python": {
|
||||
"formatter": [
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
"Python": {
|
||||
"formatter": [
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "default-1"
|
||||
},
|
||||
{
|
||||
"code_action": "default-2"
|
||||
},
|
||||
{
|
||||
"code_action": "default-3"
|
||||
},
|
||||
{
|
||||
"code_action": "default-4"
|
||||
}
|
||||
],
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "included-1"
|
||||
},
|
||||
{
|
||||
"code_action": "included-2"
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_action": "included-3"
|
||||
},
|
||||
{
|
||||
"code_action": "included-4"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Python": {
|
||||
"formatter": [
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_action": "included-3"
|
||||
},
|
||||
{
|
||||
"code_action": "included-4"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
"formatter": [
|
||||
{ "code_action": "default-1" },
|
||||
{ "code_action": "default-2" },
|
||||
{ "code_action": "default-3" },
|
||||
{ "code_action": "default-4" }
|
||||
],
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"formatter": [
|
||||
{ "code_action": "included-1" },
|
||||
{ "code_action": "included-2" },
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{ "code_action": "included-3" },
|
||||
{ "code_action": "included-4" },
|
||||
]
|
||||
},
|
||||
"Python": {
|
||||
"formatter": [
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{ "code_action": "included-3" },
|
||||
{ "code_action": "included-4" },
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
@@ -1646,185 +1604,153 @@ mod tests {
|
||||
#[test]
|
||||
fn test_flatten_code_action_formatters_array_with_format_on_save_and_multiple_languages() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_01::flatten_code_actions_formatters,
|
||||
&[MigrationType::TreeSitter(
|
||||
migrations::m_2025_10_01::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_10_01,
|
||||
)],
|
||||
&r#"{
|
||||
"formatter": {
|
||||
"code_actions": {
|
||||
"default-1": true,
|
||||
"default-2": true,
|
||||
"default-3": true,
|
||||
"default-4": true,
|
||||
}
|
||||
},
|
||||
"format_on_save": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
}
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
],
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"format_on_save": "prettier",
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
"formatter": {
|
||||
"code_actions": {
|
||||
"default-1": true,
|
||||
"default-2": true,
|
||||
"default-3": true,
|
||||
"default-4": true,
|
||||
}
|
||||
},
|
||||
"format_on_save": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
}
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
],
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"format_on_save": "prettier",
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
}
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
"Python": {
|
||||
"format_on_save": {
|
||||
"code_actions": {
|
||||
"on-save-1": true,
|
||||
"on-save-2": true,
|
||||
}
|
||||
},
|
||||
"formatter": [
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
"Python": {
|
||||
"format_on_save": {
|
||||
"code_actions": {
|
||||
"on-save-1": true,
|
||||
"on-save-2": true,
|
||||
}
|
||||
},
|
||||
"formatter": [
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"excluded-2": false,
|
||||
}
|
||||
}
|
||||
// some comment
|
||||
,
|
||||
{
|
||||
"code_actions": {
|
||||
"excluded": false,
|
||||
"included-3": true,
|
||||
"included-4": true,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"
|
||||
{
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "default-1"
|
||||
},
|
||||
{
|
||||
"code_action": "default-2"
|
||||
},
|
||||
{
|
||||
"code_action": "default-3"
|
||||
},
|
||||
{
|
||||
"code_action": "default-4"
|
||||
}
|
||||
],
|
||||
"format_on_save": [
|
||||
{
|
||||
"code_action": "included-1"
|
||||
},
|
||||
{
|
||||
"code_action": "included-2"
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_action": "included-3"
|
||||
},
|
||||
{
|
||||
"code_action": "included-4"
|
||||
}
|
||||
],
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"format_on_save": "prettier",
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "included-1"
|
||||
},
|
||||
{
|
||||
"code_action": "included-2"
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_action": "included-3"
|
||||
},
|
||||
{
|
||||
"code_action": "included-4"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Python": {
|
||||
"format_on_save": [
|
||||
{
|
||||
"code_action": "on-save-1"
|
||||
},
|
||||
{
|
||||
"code_action": "on-save-2"
|
||||
}
|
||||
],
|
||||
"formatter": [
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_action": "included-3"
|
||||
},
|
||||
{
|
||||
"code_action": "included-4"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{ "code_action": "default-1" },
|
||||
{ "code_action": "default-2" },
|
||||
{ "code_action": "default-3" },
|
||||
{ "code_action": "default-4" }
|
||||
],
|
||||
"format_on_save": [
|
||||
{ "code_action": "included-1" },
|
||||
{ "code_action": "included-2" },
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{ "code_action": "included-3" },
|
||||
{ "code_action": "included-4" },
|
||||
],
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"format_on_save": "prettier",
|
||||
"formatter": [
|
||||
{ "code_action": "included-1" },
|
||||
{ "code_action": "included-2" },
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{ "code_action": "included-3" },
|
||||
{ "code_action": "included-4" },
|
||||
]
|
||||
},
|
||||
"Python": {
|
||||
"format_on_save": [
|
||||
{ "code_action": "on-save-1" },
|
||||
{ "code_action": "on-save-2" }
|
||||
],
|
||||
"formatter": [
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{ "code_action": "included-3" },
|
||||
{ "code_action": "included-4" },
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
@@ -2026,25 +1952,25 @@ mod tests {
|
||||
migrations::m_2025_10_10::remove_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"code_actions_on_format": {
|
||||
"a": true,
|
||||
"b": false,
|
||||
"c": true
|
||||
}
|
||||
}"#
|
||||
"code_actions_on_format": {
|
||||
"a": true,
|
||||
"b": false,
|
||||
"c": true
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "a"
|
||||
},
|
||||
{
|
||||
"code_action": "c"
|
||||
}
|
||||
]
|
||||
}
|
||||
"#
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "a"
|
||||
},
|
||||
{
|
||||
"code_action": "c"
|
||||
}
|
||||
]
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
@@ -2237,12 +2163,12 @@ mod tests {
|
||||
]
|
||||
},
|
||||
"Python": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.organizeImports"
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.organizeImports"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
@@ -2286,53 +2212,4 @@ mod tests {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_action_formatters_issue() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_01::flatten_code_actions_formatters,
|
||||
)],
|
||||
&r#"
|
||||
{
|
||||
"languages": {
|
||||
"Python": {
|
||||
"language_servers": ["ruff"],
|
||||
"format_on_save": "on",
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
// Fix all auto-fixable lint violations
|
||||
"source.fixAll.ruff": true,
|
||||
// Organize imports
|
||||
"source.organizeImports.ruff": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"
|
||||
{
|
||||
"languages": {
|
||||
"Python": {
|
||||
"language_servers": ["ruff"],
|
||||
"format_on_save": "on",
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.fixAll.ruff"
|
||||
},
|
||||
{
|
||||
"code_action": "source.organizeImports.ruff"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,4 @@ pub(crate) use settings::{
|
||||
SETTINGS_ASSISTANT_PATTERN, SETTINGS_ASSISTANT_TOOLS_PATTERN,
|
||||
SETTINGS_DUPLICATED_AGENT_PATTERN, SETTINGS_EDIT_PREDICTIONS_ASSISTANT_PATTERN,
|
||||
SETTINGS_LANGUAGES_PATTERN, SETTINGS_NESTED_KEY_VALUE_PATTERN, SETTINGS_ROOT_KEY_VALUE_PATTERN,
|
||||
migrate_language_setting,
|
||||
};
|
||||
|
||||
@@ -108,24 +108,3 @@ pub const SETTINGS_DUPLICATED_AGENT_PATTERN: &str = r#"(document
|
||||
(#eq? @agent1 "agent")
|
||||
(#eq? @agent2 "agent")
|
||||
)"#;
|
||||
|
||||
/// Migrate language settings,
|
||||
/// calls `migrate_fn` with the top level object as well as all language settings under the "languages" key
|
||||
/// Fails early if `migrate_fn` returns an error at any point
|
||||
pub fn migrate_language_setting(
|
||||
value: &mut serde_json::Value,
|
||||
migrate_fn: fn(&mut serde_json::Value, path: &[&str]) -> anyhow::Result<()>,
|
||||
) -> anyhow::Result<()> {
|
||||
migrate_fn(value, &[])?;
|
||||
let languages = value
|
||||
.as_object_mut()
|
||||
.and_then(|obj| obj.get_mut("languages"))
|
||||
.and_then(|languages| languages.as_object_mut());
|
||||
if let Some(languages) = languages {
|
||||
for (language_name, language) in languages.iter_mut() {
|
||||
let path = vec!["languages", language_name];
|
||||
migrate_fn(language, &path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use gpui::{
|
||||
AnyView, Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Pixels, Point,
|
||||
Subscription,
|
||||
AnyView, Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
|
||||
};
|
||||
use ui::{
|
||||
FluentBuilder as _, IntoElement, PopoverMenu, PopoverMenuHandle, PopoverTrigger, prelude::*,
|
||||
App, ButtonCommon, FluentBuilder as _, IntoElement, PopoverMenu, PopoverMenuHandle,
|
||||
PopoverTrigger, RenderOnce, Window, px,
|
||||
};
|
||||
|
||||
use crate::{Picker, PickerDelegate};
|
||||
@@ -19,7 +19,6 @@ where
|
||||
tooltip: TT,
|
||||
handle: Option<PopoverMenuHandle<Picker<P>>>,
|
||||
anchor: Corner,
|
||||
offset: Option<Point<Pixels>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -44,10 +43,6 @@ where
|
||||
trigger,
|
||||
tooltip,
|
||||
handle: None,
|
||||
offset: Some(Point {
|
||||
x: px(0.0),
|
||||
y: px(-2.0),
|
||||
}),
|
||||
anchor,
|
||||
}
|
||||
}
|
||||
@@ -56,11 +51,6 @@ where
|
||||
self.handle = Some(handle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn offset(mut self, offset: Point<Pixels>) -> Self {
|
||||
self.offset = Some(offset);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, TT, P> EventEmitter<DismissEvent> for PickerPopoverMenu<T, TT, P>
|
||||
@@ -96,6 +86,9 @@ where
|
||||
.trigger_with_tooltip(self.trigger, self.tooltip)
|
||||
.anchor(self.anchor)
|
||||
.when_some(self.handle, |menu, handle| menu.with_handle(handle))
|
||||
.when_some(self.offset, |menu, offset| menu.offset(offset))
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(-2.0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,6 +577,7 @@ fn get_or_npm_install_builtin_agent(
|
||||
package_name: SharedString,
|
||||
entrypoint_path: PathBuf,
|
||||
minimum_version: Option<semver::Version>,
|
||||
channel: &'static str,
|
||||
status_tx: Option<watch::Sender<SharedString>>,
|
||||
new_version_available: Option<watch::Sender<Option<String>>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
@@ -648,10 +649,12 @@ fn get_or_npm_install_builtin_agent(
|
||||
let dir = dir.clone();
|
||||
let fs = fs.clone();
|
||||
async move {
|
||||
// TODO remove the filter
|
||||
let latest_version = node_runtime
|
||||
.npm_package_latest_version(&package_name)
|
||||
.await
|
||||
.ok();
|
||||
.ok()
|
||||
.filter(|_| channel == "latest");
|
||||
if let Some(latest_version) = latest_version
|
||||
&& &latest_version != &file_name.to_string_lossy()
|
||||
{
|
||||
@@ -660,6 +663,7 @@ fn get_or_npm_install_builtin_agent(
|
||||
dir.clone(),
|
||||
node_runtime,
|
||||
package_name.clone(),
|
||||
channel,
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
@@ -683,6 +687,7 @@ fn get_or_npm_install_builtin_agent(
|
||||
dir.clone(),
|
||||
node_runtime,
|
||||
package_name.clone(),
|
||||
channel,
|
||||
))
|
||||
.await?
|
||||
.into()
|
||||
@@ -731,13 +736,14 @@ async fn download_latest_version(
|
||||
dir: PathBuf,
|
||||
node_runtime: NodeRuntime,
|
||||
package_name: SharedString,
|
||||
channel: &'static str,
|
||||
) -> Result<String> {
|
||||
log::debug!("downloading latest version of {package_name}");
|
||||
|
||||
let tmp_dir = tempfile::tempdir_in(&dir)?;
|
||||
|
||||
node_runtime
|
||||
.npm_install_packages(tmp_dir.path(), &[(&package_name, "latest")])
|
||||
.npm_install_packages(tmp_dir.path(), &[(&package_name, channel)])
|
||||
.await?;
|
||||
|
||||
let version = node_runtime
|
||||
@@ -880,12 +886,17 @@ impl ExternalAgentServer for LocalGemini {
|
||||
GEMINI_NAME.into(),
|
||||
"@google/gemini-cli".into(),
|
||||
"node_modules/@google/gemini-cli/dist/index.js".into(),
|
||||
// TODO remove these windows-specific workarounds once v0.9.0 stable is released
|
||||
if cfg!(windows) {
|
||||
// v0.8.x on Windows has a bug that causes the initialize request to hang forever
|
||||
Some("0.9.0".parse().unwrap())
|
||||
Some("0.9.0-preview.4".parse().unwrap())
|
||||
} else {
|
||||
Some("0.2.1".parse().unwrap())
|
||||
},
|
||||
if cfg!(windows) {
|
||||
"0.9.0-preview.4"
|
||||
} else {
|
||||
"latest"
|
||||
},
|
||||
status_tx,
|
||||
new_version_available_tx,
|
||||
fs,
|
||||
@@ -969,6 +980,7 @@ impl ExternalAgentServer for LocalClaudeCode {
|
||||
"@zed-industries/claude-code-acp".into(),
|
||||
"node_modules/@zed-industries/claude-code-acp/dist/index.js".into(),
|
||||
Some("0.5.2".parse().unwrap()),
|
||||
"latest",
|
||||
status_tx,
|
||||
new_version_available_tx,
|
||||
fs,
|
||||
|
||||
@@ -263,7 +263,7 @@ async fn load_shell_environment(
|
||||
.into_iter()
|
||||
.collect();
|
||||
(Some(fake_env), None)
|
||||
} else if cfg!(target_os = "windows") {
|
||||
} else if cfg!(target_os = "windows",) {
|
||||
let (shell, args) = shell.program_and_args();
|
||||
let envs = match shell_env::capture(shell, args, dir).await {
|
||||
Ok(envs) => envs,
|
||||
|
||||
@@ -2698,10 +2698,8 @@ impl ProjectPanel {
|
||||
});
|
||||
|
||||
if item_count == 1 {
|
||||
// open entry if not dir, setting is enabled, and only focus if rename is not pending
|
||||
if !entry.is_dir()
|
||||
&& ProjectPanelSettings::get_global(cx).open_file_on_paste
|
||||
{
|
||||
// open entry if not dir, and only focus if rename is not pending
|
||||
if !entry.is_dir() {
|
||||
project_panel.open_entry(
|
||||
entry.id,
|
||||
disambiguation_range.is_none(),
|
||||
|
||||
@@ -32,7 +32,6 @@ pub struct ProjectPanelSettings {
|
||||
pub hide_root: bool,
|
||||
pub hide_hidden: bool,
|
||||
pub drag_and_drop: bool,
|
||||
pub open_file_on_paste: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
@@ -83,7 +82,6 @@ impl Settings for ProjectPanelSettings {
|
||||
hide_root: project_panel.hide_root.unwrap(),
|
||||
hide_hidden: project_panel.hide_hidden.unwrap(),
|
||||
drag_and_drop: project_panel.drag_and_drop.unwrap(),
|
||||
open_file_on_paste: project_panel.open_file_on_paste.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ impl WslRemoteConnection {
|
||||
this.can_exec = this.detect_can_exec(shell).await?;
|
||||
this.platform = this.detect_platform(shell).await?;
|
||||
this.remote_binary_path = Some(
|
||||
this.ensure_server_binary(&delegate, release_channel, version, commit, shell, cx)
|
||||
this.ensure_server_binary(&delegate, release_channel, version, commit, cx)
|
||||
.await?,
|
||||
);
|
||||
log::debug!("Detected WSL environment: {this:#?}");
|
||||
@@ -163,7 +163,6 @@ impl WslRemoteConnection {
|
||||
release_channel: ReleaseChannel,
|
||||
version: SemanticVersion,
|
||||
commit: Option<AppCommitSha>,
|
||||
shell: ShellKind,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Arc<RelPath>> {
|
||||
let version_str = match release_channel {
|
||||
@@ -185,13 +184,9 @@ impl WslRemoteConnection {
|
||||
paths::remote_wsl_server_dir_relative().join(RelPath::unix(&binary_name).unwrap());
|
||||
|
||||
if let Some(parent) = dst_path.parent() {
|
||||
let parent = parent.display(PathStyle::Posix);
|
||||
if shell == ShellKind::Nushell {
|
||||
self.run_wsl_command("mkdir", &[&parent]).await
|
||||
} else {
|
||||
self.run_wsl_command("mkdir", &["-p", &parent]).await
|
||||
}
|
||||
.map_err(|e| anyhow!("Failed to create directory: {}", e))?;
|
||||
self.run_wsl_command("mkdir", &["-p", &parent.display(PathStyle::Posix)])
|
||||
.await
|
||||
.map_err(|e| anyhow!("Failed to create directory: {}", e))?;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
@@ -566,10 +566,6 @@ pub struct ProjectPanelSettingsContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub drag_and_drop: Option<bool>,
|
||||
/// Whether to automatically open files when pasting them in the project panel.
|
||||
///
|
||||
/// Default: true
|
||||
pub open_file_on_paste: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
|
||||
@@ -262,8 +262,8 @@ pub fn replace_value_in_json_text<T: AsRef<str>>(
|
||||
} else {
|
||||
// We don't have the key, construct the nested objects
|
||||
let new_value = construct_json_value(&key_path[depth..], new_value);
|
||||
let indent_prefix_len = tab_size * depth;
|
||||
let mut new_val = to_pretty_json(&new_value, tab_size, indent_prefix_len);
|
||||
let indent_prefix_len = 4 * depth;
|
||||
let mut new_val = to_pretty_json(&new_value, 4, indent_prefix_len);
|
||||
if depth == 0 {
|
||||
new_val.push('\n');
|
||||
}
|
||||
@@ -628,100 +628,6 @@ pub fn append_top_level_array_value_in_json_text(
|
||||
}
|
||||
}
|
||||
|
||||
/// Infers the indentation size used in JSON text by analyzing the tree structure.
|
||||
/// Returns the detected indent size, or a default of 2 if no indentation is found.
|
||||
pub fn infer_json_indent_size(text: &str) -> usize {
|
||||
const MAX_INDENT_SIZE: usize = 64;
|
||||
|
||||
let mut parser = tree_sitter::Parser::new();
|
||||
parser
|
||||
.set_language(&tree_sitter_json::LANGUAGE.into())
|
||||
.unwrap();
|
||||
|
||||
let Some(syntax_tree) = parser.parse(text, None) else {
|
||||
return 4;
|
||||
};
|
||||
|
||||
let mut cursor = syntax_tree.walk();
|
||||
let mut indent_counts = [0u32; MAX_INDENT_SIZE];
|
||||
|
||||
// Traverse the tree to find indentation patterns
|
||||
fn visit_node(
|
||||
cursor: &mut tree_sitter::TreeCursor,
|
||||
indent_counts: &mut [u32; MAX_INDENT_SIZE],
|
||||
depth: usize,
|
||||
) {
|
||||
if depth >= 3 {
|
||||
return;
|
||||
}
|
||||
let node = cursor.node();
|
||||
let node_kind = node.kind();
|
||||
|
||||
// For objects and arrays, check the indentation of their first content child
|
||||
if matches!(node_kind, "object" | "array") {
|
||||
let container_column = node.start_position().column;
|
||||
let container_row = node.start_position().row;
|
||||
|
||||
if cursor.goto_first_child() {
|
||||
// Skip the opening bracket
|
||||
loop {
|
||||
let child = cursor.node();
|
||||
let child_kind = child.kind();
|
||||
|
||||
// Look for the first actual content (pair for objects, value for arrays)
|
||||
if (node_kind == "object" && child_kind == "pair")
|
||||
|| (node_kind == "array"
|
||||
&& !matches!(child_kind, "[" | "]" | "," | "comment"))
|
||||
{
|
||||
let child_column = child.start_position().column;
|
||||
let child_row = child.start_position().row;
|
||||
|
||||
// Only count if the child is on a different line
|
||||
if child_row > container_row && child_column > container_column {
|
||||
let indent = child_column - container_column;
|
||||
if indent > 0 && indent < MAX_INDENT_SIZE {
|
||||
indent_counts[indent] += 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if !cursor.goto_next_sibling() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
cursor.goto_parent();
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse to children
|
||||
if cursor.goto_first_child() {
|
||||
loop {
|
||||
visit_node(cursor, indent_counts, depth + 1);
|
||||
if !cursor.goto_next_sibling() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
cursor.goto_parent();
|
||||
}
|
||||
}
|
||||
|
||||
visit_node(&mut cursor, &mut indent_counts, 0);
|
||||
|
||||
// Find the indent size with the highest count
|
||||
let mut max_count = 0;
|
||||
let mut max_indent = 4;
|
||||
|
||||
for (indent, &count) in indent_counts.iter().enumerate() {
|
||||
if count > max_count {
|
||||
max_count = count;
|
||||
max_indent = indent;
|
||||
}
|
||||
}
|
||||
|
||||
if max_count == 0 { 2 } else { max_indent }
|
||||
}
|
||||
|
||||
pub fn to_pretty_json(
|
||||
value: &impl Serialize,
|
||||
indent_size: usize,
|
||||
@@ -2580,69 +2486,4 @@ mod tests {
|
||||
.unindent(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_infer_json_indent_size() {
|
||||
let json_2_spaces = r#"{
|
||||
"key1": "value1",
|
||||
"nested": {
|
||||
"key2": "value2",
|
||||
"array": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
assert_eq!(infer_json_indent_size(json_2_spaces), 2);
|
||||
|
||||
let json_4_spaces = r#"{
|
||||
"key1": "value1",
|
||||
"nested": {
|
||||
"key2": "value2",
|
||||
"array": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
assert_eq!(infer_json_indent_size(json_4_spaces), 4);
|
||||
|
||||
let json_8_spaces = r#"{
|
||||
"key1": "value1",
|
||||
"nested": {
|
||||
"key2": "value2"
|
||||
}
|
||||
}"#;
|
||||
assert_eq!(infer_json_indent_size(json_8_spaces), 8);
|
||||
|
||||
let json_single_line = r#"{"key": "value", "nested": {"inner": "data"}}"#;
|
||||
assert_eq!(infer_json_indent_size(json_single_line), 2);
|
||||
|
||||
let json_empty = r#"{}"#;
|
||||
assert_eq!(infer_json_indent_size(json_empty), 2);
|
||||
|
||||
let json_array = r#"[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "first"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "second"
|
||||
}
|
||||
]"#;
|
||||
assert_eq!(infer_json_indent_size(json_array), 2);
|
||||
|
||||
let json_mixed = r#"{
|
||||
"a": {
|
||||
"b": {
|
||||
"c": "value"
|
||||
}
|
||||
},
|
||||
"d": "value2"
|
||||
}"#;
|
||||
assert_eq!(infer_json_indent_size(json_mixed), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ pub type EditorconfigProperties = ec4rs::Properties;
|
||||
use crate::{
|
||||
ActiveSettingsProfileName, FontFamilyName, IconThemeName, LanguageSettingsContent,
|
||||
LanguageToSettingsMap, SettingsJsonSchemaParams, ThemeName, VsCodeSettings, WorktreeId,
|
||||
infer_json_indent_size,
|
||||
merge_from::MergeFrom,
|
||||
parse_json_with_comments,
|
||||
settings_content::{
|
||||
@@ -638,7 +637,7 @@ impl SettingsStore {
|
||||
|
||||
let mut key_path = Vec::new();
|
||||
let mut edits = Vec::new();
|
||||
let tab_size = infer_json_indent_size(&text);
|
||||
let tab_size = self.json_tab_size();
|
||||
let mut text = text.to_string();
|
||||
update_value_in_json_text(
|
||||
&mut text,
|
||||
@@ -651,6 +650,10 @@ impl SettingsStore {
|
||||
edits
|
||||
}
|
||||
|
||||
pub fn json_tab_size(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
/// Sets the default settings via a JSON string.
|
||||
///
|
||||
/// The string should contain a JSON object with a default value for every setting.
|
||||
@@ -1537,9 +1540,9 @@ mod tests {
|
||||
})
|
||||
},
|
||||
r#"{
|
||||
"tabs": {
|
||||
"git_status": true
|
||||
}
|
||||
"tabs": {
|
||||
"git_status": true
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
@@ -1554,9 +1557,9 @@ mod tests {
|
||||
.unindent(),
|
||||
|settings| settings.title_bar.get_or_insert_default().show_branch_name = Some(true),
|
||||
r#"{
|
||||
"title_bar": {
|
||||
"show_branch_name": true
|
||||
}
|
||||
"title_bar": {
|
||||
"show_branch_name": true
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
@@ -1581,7 +1584,7 @@ mod tests {
|
||||
.unindent(),
|
||||
r#" { "editor.tabSize": 37 } "#.to_owned(),
|
||||
r#"{
|
||||
"tab_size": 37
|
||||
"tab_size": 37
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
@@ -1634,9 +1637,9 @@ mod tests {
|
||||
.unindent(),
|
||||
r#"{ "workbench.editor.decorations.colors": true }"#.to_owned(),
|
||||
r#"{
|
||||
"tabs": {
|
||||
"git_status": true
|
||||
}
|
||||
"tabs": {
|
||||
"git_status": true
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
@@ -1652,11 +1655,11 @@ mod tests {
|
||||
.unindent(),
|
||||
r#"{ "editor.fontFamily": "Cascadia Code, 'Consolas', Courier New" }"#.to_owned(),
|
||||
r#"{
|
||||
"buffer_font_fallbacks": [
|
||||
"Consolas",
|
||||
"Courier New"
|
||||
],
|
||||
"buffer_font_family": "Cascadia Code"
|
||||
"buffer_font_fallbacks": [
|
||||
"Consolas",
|
||||
"Courier New"
|
||||
],
|
||||
"buffer_font_family": "Cascadia Code"
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
@@ -1692,16 +1695,16 @@ mod tests {
|
||||
.get_or_insert_default()
|
||||
.enabled = Some(true);
|
||||
});
|
||||
pretty_assertions::assert_str_eq!(
|
||||
assert_eq!(
|
||||
actual,
|
||||
r#"{
|
||||
"git": {
|
||||
"git": {
|
||||
"inline_blame": {
|
||||
"enabled": true
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
"#
|
||||
}
|
||||
"#
|
||||
.unindent()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1723,7 +1723,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
title: language_name,
|
||||
files: USER | LOCAL,
|
||||
render: Arc::new(|this, window, cx| {
|
||||
this.render_sub_page_items(
|
||||
this.render_page_items(
|
||||
language_settings_data()
|
||||
.iter()
|
||||
.chain(non_editor_language_settings_data().iter())
|
||||
@@ -3120,27 +3120,6 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Open File on Paste",
|
||||
description: "Whether to automatically open files when pasting them in the project panel",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(project_panel) = &settings_content.project_panel {
|
||||
&project_panel.open_file_on_paste
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.project_panel
|
||||
.get_or_insert_default()
|
||||
.open_file_on_paste
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Terminal Panel"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Terminal Dock",
|
||||
|
||||
@@ -6,10 +6,10 @@ use editor::{Editor, EditorEvent};
|
||||
use feature_flags::FeatureFlag;
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{
|
||||
Action, App, Div, Entity, FocusHandle, Focusable, FontWeight, Global, ListState,
|
||||
ReadGlobal as _, ScrollHandle, Stateful, Subscription, Task, TitlebarOptions,
|
||||
UniformListScrollHandle, Window, WindowBounds, WindowHandle, WindowOptions, actions, div, list,
|
||||
point, prelude::*, px, size, uniform_list,
|
||||
Action, App, Div, Entity, FocusHandle, Focusable, FontWeight, Global, ReadGlobal as _,
|
||||
ScrollHandle, Stateful, Subscription, Task, TitlebarOptions, UniformListScrollHandle, Window,
|
||||
WindowBounds, WindowHandle, WindowOptions, actions, div, point, prelude::*, px, size,
|
||||
uniform_list,
|
||||
};
|
||||
use heck::ToTitleCase as _;
|
||||
use project::WorktreeId;
|
||||
@@ -455,9 +455,8 @@ pub fn open_settings_editor(
|
||||
|
||||
if let Some(existing_window) = existing_window {
|
||||
existing_window
|
||||
.update(cx, |settings_window, window, cx| {
|
||||
.update(cx, |settings_window, window, _| {
|
||||
settings_window.original_window = Some(workspace_handle);
|
||||
settings_window.observe_last_window_close(cx);
|
||||
window.activate_window();
|
||||
})
|
||||
.ok();
|
||||
@@ -537,14 +536,12 @@ pub struct SettingsWindow {
|
||||
filter_table: Vec<Vec<bool>>,
|
||||
has_query: bool,
|
||||
content_handles: Vec<Vec<Entity<NonFocusableHandle>>>,
|
||||
sub_page_scroll_handle: ScrollHandle,
|
||||
page_scroll_handle: ScrollHandle,
|
||||
focus_handle: FocusHandle,
|
||||
navbar_focus_handle: Entity<NonFocusableHandle>,
|
||||
content_focus_handle: Entity<NonFocusableHandle>,
|
||||
files_focus_handle: FocusHandle,
|
||||
search_index: Option<Arc<SearchIndex>>,
|
||||
visible_items: Vec<usize>,
|
||||
list_state: ListState,
|
||||
}
|
||||
|
||||
struct SearchIndex {
|
||||
@@ -604,7 +601,7 @@ impl SettingsPageItem {
|
||||
fn render(
|
||||
&self,
|
||||
settings_window: &SettingsWindow,
|
||||
item_index: usize,
|
||||
section_header: &'static str,
|
||||
is_last: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<SettingsWindow>,
|
||||
@@ -715,23 +712,7 @@ impl SettingsPageItem {
|
||||
.on_click({
|
||||
let sub_page_link = sub_page_link.clone();
|
||||
cx.listener(move |this, _, _, cx| {
|
||||
let mut section_index = item_index;
|
||||
let current_page = this.current_page();
|
||||
|
||||
while !matches!(
|
||||
current_page.items[section_index],
|
||||
SettingsPageItem::SectionHeader(_)
|
||||
) {
|
||||
section_index -= 1;
|
||||
}
|
||||
|
||||
let SettingsPageItem::SectionHeader(header) =
|
||||
current_page.items[section_index]
|
||||
else {
|
||||
unreachable!("All items always have a section header above them")
|
||||
};
|
||||
|
||||
this.push_sub_page(sub_page_link.clone(), header, cx)
|
||||
this.push_sub_page(sub_page_link.clone(), section_header, cx)
|
||||
})
|
||||
}),
|
||||
)
|
||||
@@ -999,14 +980,9 @@ impl SettingsWindow {
|
||||
None
|
||||
};
|
||||
|
||||
// high overdraw value so the list scrollbar len doesn't change too much
|
||||
let list_state = gpui::ListState::new(0, gpui::ListAlignment::Top, px(100.0)).measure_all();
|
||||
list_state.set_scroll_handler(|_, _, _| {});
|
||||
|
||||
let mut this = Self {
|
||||
title_bar,
|
||||
original_window,
|
||||
|
||||
worktree_root_dirs: HashMap::default(),
|
||||
files: vec![],
|
||||
drop_down_file: None,
|
||||
@@ -1020,7 +996,7 @@ impl SettingsWindow {
|
||||
filter_table: vec![],
|
||||
has_query: false,
|
||||
content_handles: vec![],
|
||||
sub_page_scroll_handle: ScrollHandle::new(),
|
||||
page_scroll_handle: ScrollHandle::new(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
navbar_focus_handle: NonFocusableHandle::new(
|
||||
NAVBAR_CONTAINER_TAB_INDEX,
|
||||
@@ -1040,12 +1016,8 @@ impl SettingsWindow {
|
||||
.tab_index(HEADER_CONTAINER_TAB_INDEX)
|
||||
.tab_stop(false),
|
||||
search_index: None,
|
||||
visible_items: Vec::default(),
|
||||
list_state,
|
||||
};
|
||||
|
||||
this.observe_last_window_close(cx);
|
||||
|
||||
this.fetch_files(window, cx);
|
||||
this.build_ui(window, cx);
|
||||
this.build_search_index();
|
||||
@@ -1057,23 +1029,6 @@ impl SettingsWindow {
|
||||
this
|
||||
}
|
||||
|
||||
fn observe_last_window_close(&mut self, cx: &mut App) {
|
||||
cx.on_window_closed(|cx| {
|
||||
if let Some(existing_window) = cx
|
||||
.windows()
|
||||
.into_iter()
|
||||
.find_map(|window| window.downcast::<SettingsWindow>())
|
||||
&& cx.windows().len() == 1
|
||||
{
|
||||
cx.update_window(*existing_window, |_, window, _| {
|
||||
window.remove_window();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn toggle_navbar_entry(&mut self, nav_entry_index: usize) {
|
||||
// We can only toggle root entries
|
||||
if !self.navbar_entries[nav_entry_index].is_root {
|
||||
@@ -1230,7 +1185,6 @@ impl SettingsWindow {
|
||||
}
|
||||
self.has_query = false;
|
||||
self.filter_matches_to_file();
|
||||
self.reset_list_state();
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
@@ -1260,7 +1214,6 @@ impl SettingsWindow {
|
||||
this.has_query = true;
|
||||
this.filter_matches_to_file();
|
||||
this.open_first_nav_page();
|
||||
this.reset_list_state();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -1437,18 +1390,6 @@ impl SettingsWindow {
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
fn reset_list_state(&mut self) {
|
||||
// plus one for the title
|
||||
self.visible_items = self.visible_page_items().map(|(index, _)| index).collect();
|
||||
|
||||
if self.visible_items.is_empty() {
|
||||
self.list_state.reset(0);
|
||||
} else {
|
||||
// show page title if page is non empty
|
||||
self.list_state.reset(self.visible_items.len() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_ui(&mut self, window: &mut Window, cx: &mut Context<SettingsWindow>) {
|
||||
if self.pages.is_empty() {
|
||||
self.pages = page_data::settings_data(cx);
|
||||
@@ -1459,7 +1400,6 @@ impl SettingsWindow {
|
||||
sub_page_stack_mut().clear();
|
||||
// PERF: doesn't have to be rebuilt, can just be filled with true. pages is constant once it is built
|
||||
self.build_filter_table();
|
||||
self.reset_list_state();
|
||||
self.update_matches(cx);
|
||||
|
||||
cx.notify();
|
||||
@@ -1523,17 +1463,7 @@ impl SettingsWindow {
|
||||
if !self.is_nav_entry_visible(navbar_entry) {
|
||||
self.open_first_nav_page();
|
||||
}
|
||||
|
||||
let is_new_page = self.navbar_entries[self.navbar_entry].page_index
|
||||
!= self.navbar_entries[navbar_entry].page_index;
|
||||
self.navbar_entry = navbar_entry;
|
||||
|
||||
// We only need to reset visible items when updating matches
|
||||
// and selecting a new page
|
||||
if is_new_page {
|
||||
self.reset_list_state();
|
||||
}
|
||||
|
||||
sub_page_stack_mut().clear();
|
||||
}
|
||||
|
||||
@@ -1980,8 +1910,7 @@ impl SettingsWindow {
|
||||
if self.navbar_entries[navbar_entry_index].is_root
|
||||
|| !self.is_nav_entry_visible(navbar_entry_index)
|
||||
{
|
||||
self.sub_page_scroll_handle
|
||||
.set_offset(point(px(0.), px(0.)));
|
||||
self.page_scroll_handle.set_offset(point(px(0.), px(0.)));
|
||||
if focus_content {
|
||||
let Some(first_item_index) =
|
||||
self.visible_page_items().next().map(|(index, _)| index)
|
||||
@@ -2002,11 +1931,9 @@ impl SettingsWindow {
|
||||
else {
|
||||
return;
|
||||
};
|
||||
self.page_scroll_handle
|
||||
.scroll_to_top_of_item(selected_item_index + 1);
|
||||
|
||||
self.list_state.scroll_to(gpui::ListOffset {
|
||||
item_ix: selected_item_index + 1,
|
||||
offset_in_item: px(0.),
|
||||
});
|
||||
if focus_content {
|
||||
self.focus_content_element(entry_item_index, window, cx);
|
||||
} else {
|
||||
@@ -2084,107 +2011,7 @@ impl SettingsWindow {
|
||||
.child(Label::new(last))
|
||||
}
|
||||
|
||||
fn render_page_items(
|
||||
&mut self,
|
||||
page_index: Option<usize>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<SettingsWindow>,
|
||||
) -> impl IntoElement {
|
||||
let mut page_content = v_flex().id("settings-ui-page").size_full();
|
||||
|
||||
let has_active_search = !self.search_bar.read(cx).is_empty(cx);
|
||||
let has_no_results = self.visible_items.len() == 0 && has_active_search;
|
||||
|
||||
if has_no_results {
|
||||
let search_query = self.search_bar.read(cx).text(cx);
|
||||
page_content = page_content.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.gap_1()
|
||||
.child(div().child("No Results"))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.child(format!("No settings match \"{}\"", search_query)),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
let items = &self.current_page().items;
|
||||
|
||||
let last_non_header_index = self
|
||||
.visible_items
|
||||
.iter()
|
||||
.map(|index| &items[*index])
|
||||
.enumerate()
|
||||
.rev()
|
||||
.find(|(_, item)| !matches!(item, SettingsPageItem::SectionHeader(_)))
|
||||
.map(|(index, _)| index);
|
||||
|
||||
let root_nav_label = self
|
||||
.navbar_entries
|
||||
.iter()
|
||||
.find(|entry| entry.is_root && entry.page_index == self.current_page_index())
|
||||
.map(|entry| entry.title);
|
||||
|
||||
let list_content = list(
|
||||
self.list_state.clone(),
|
||||
cx.processor(move |this, index, window, cx| {
|
||||
if index == 0 {
|
||||
return div()
|
||||
.when(sub_page_stack().is_empty(), |this| {
|
||||
this.when_some(root_nav_label, |this, title| {
|
||||
this.child(
|
||||
Label::new(title).size(LabelSize::Large).mt_2().mb_3(),
|
||||
)
|
||||
})
|
||||
})
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
let index = index - 1;
|
||||
let actual_item_index = this.visible_items[index];
|
||||
let item: &SettingsPageItem = &this.current_page().items[actual_item_index];
|
||||
|
||||
let no_bottom_border = this
|
||||
.visible_items
|
||||
.get(index + 1)
|
||||
.map(|item_index| {
|
||||
let item = &this.current_page().items[*item_index];
|
||||
matches!(item, SettingsPageItem::SectionHeader(_))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
let is_last = Some(index) == last_non_header_index;
|
||||
|
||||
v_flex()
|
||||
.id(("settings-page-item", actual_item_index))
|
||||
.w_full()
|
||||
.min_w_0()
|
||||
.when_some(page_index, |element, page_index| {
|
||||
element.track_focus(
|
||||
&this.content_handles[page_index][actual_item_index]
|
||||
.focus_handle(cx),
|
||||
)
|
||||
})
|
||||
.child(item.render(
|
||||
this,
|
||||
actual_item_index,
|
||||
no_bottom_border || is_last,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.into_any_element()
|
||||
}),
|
||||
);
|
||||
|
||||
page_content = page_content.child(list_content.size_full())
|
||||
}
|
||||
page_content
|
||||
}
|
||||
|
||||
fn render_sub_page_items<'a, Items: Iterator<Item = (usize, &'a SettingsPageItem)>>(
|
||||
fn render_page_items<'a, Items: Iterator<Item = (usize, &'a SettingsPageItem)>>(
|
||||
&self,
|
||||
items: Items,
|
||||
page_index: Option<usize>,
|
||||
@@ -2195,7 +2022,7 @@ impl SettingsWindow {
|
||||
.id("settings-ui-page")
|
||||
.size_full()
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(&self.sub_page_scroll_handle);
|
||||
.track_scroll(&self.page_scroll_handle);
|
||||
|
||||
let items: Vec<_> = items.collect();
|
||||
let items_len = items.len();
|
||||
@@ -2265,7 +2092,7 @@ impl SettingsWindow {
|
||||
})
|
||||
.child(item.render(
|
||||
self,
|
||||
actual_item_index,
|
||||
section_header.expect("All items rendered after a section header"),
|
||||
no_bottom_border || is_last,
|
||||
window,
|
||||
cx,
|
||||
@@ -2288,7 +2115,12 @@ impl SettingsWindow {
|
||||
page_header = self.render_files_header(window, cx).into_any_element();
|
||||
|
||||
page_content = self
|
||||
.render_page_items(Some(self.current_page_index()), window, cx)
|
||||
.render_page_items(
|
||||
self.visible_page_items(),
|
||||
Some(self.current_page_index()),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.into_any_element();
|
||||
} else {
|
||||
page_header = h_flex()
|
||||
@@ -2311,19 +2143,13 @@ impl SettingsWindow {
|
||||
}
|
||||
|
||||
return v_flex()
|
||||
.id("Settings-ui-page")
|
||||
.flex_1()
|
||||
.pt_6()
|
||||
.pb_8()
|
||||
.px_8()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(page_header)
|
||||
.when(sub_page_stack().is_empty(), |this| {
|
||||
this.vertical_scrollbar_for(self.list_state.clone(), window, cx)
|
||||
})
|
||||
.when(!sub_page_stack().is_empty(), |this| {
|
||||
this.vertical_scrollbar_for(self.sub_page_scroll_handle.clone(), window, cx)
|
||||
})
|
||||
.vertical_scrollbar_for(self.page_scroll_handle.clone(), window, cx)
|
||||
.track_focus(&self.content_focus_handle.focus_handle(cx))
|
||||
.child(
|
||||
div()
|
||||
@@ -2965,7 +2791,7 @@ mod test {
|
||||
has_query: false,
|
||||
content_handles: vec![],
|
||||
search_task: None,
|
||||
sub_page_scroll_handle: ScrollHandle::new(),
|
||||
page_scroll_handle: ScrollHandle::new(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
navbar_focus_handle: NonFocusableHandle::new(
|
||||
NAVBAR_CONTAINER_TAB_INDEX,
|
||||
@@ -2981,8 +2807,6 @@ mod test {
|
||||
),
|
||||
files_focus_handle: cx.focus_handle(),
|
||||
search_index: None,
|
||||
visible_items: Vec::default(),
|
||||
list_state: ListState::new(0, gpui::ListAlignment::Top, px(0.0)),
|
||||
};
|
||||
|
||||
settings_window.build_filter_table();
|
||||
|
||||
@@ -189,18 +189,10 @@ impl Render for TitleBar {
|
||||
let status = &*status.borrow();
|
||||
let user = self.user_store.read(cx).current_user();
|
||||
|
||||
let signed_in = user.is_some();
|
||||
|
||||
children.push(
|
||||
h_flex()
|
||||
.map(|this| {
|
||||
if signed_in {
|
||||
this.pr_1p5()
|
||||
} else {
|
||||
this.pr_1()
|
||||
}
|
||||
})
|
||||
.gap_1()
|
||||
.pr_1()
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.children(self.render_call_controls(window, cx))
|
||||
.children(self.render_connection_status(status, cx))
|
||||
@@ -763,13 +755,17 @@ impl TitleBar {
|
||||
.map(|this| {
|
||||
if is_signed_in {
|
||||
this.trigger_with_tooltip(
|
||||
ButtonLike::new("user-menu").children(
|
||||
TitleBarSettings::get_global(cx)
|
||||
.show_user_picture
|
||||
.then(|| user_avatar.clone())
|
||||
.flatten()
|
||||
.map(|avatar| Avatar::new(avatar)),
|
||||
),
|
||||
ButtonLike::new("user-menu")
|
||||
.child(
|
||||
h_flex().gap_0p5().mr_1().children(
|
||||
TitleBarSettings::get_global(cx)
|
||||
.show_user_picture
|
||||
.then(|| user_avatar.clone())
|
||||
.flatten()
|
||||
.map(|avatar| Avatar::new(avatar)),
|
||||
),
|
||||
)
|
||||
.style(ButtonStyle::Subtle),
|
||||
Tooltip::text("Toggle User Menu"),
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -45,7 +45,6 @@ pub fn get_windows_git_bash() -> Option<String> {
|
||||
let git = which::which("git").ok()?;
|
||||
let git_bash = git.parent()?.parent()?.join("bin").join("bash.exe");
|
||||
if git_bash.is_file() {
|
||||
log::info!("Found git-bash at {}", git_bash.display());
|
||||
Some(git_bash.to_string_lossy().to_string())
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -177,12 +177,8 @@ async fn capture_windows(
|
||||
.args([
|
||||
"-c",
|
||||
&format!(
|
||||
"cd '{}'; {}{} --printenv",
|
||||
"cd '{}'; {} --printenv",
|
||||
directory.display(),
|
||||
shell_kind
|
||||
.command_prefix()
|
||||
.map(|prefix| prefix.to_string())
|
||||
.unwrap_or_default(),
|
||||
zed_path.display()
|
||||
),
|
||||
])
|
||||
|
||||
@@ -1525,6 +1525,29 @@ fn wrapping_right_single(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayP
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a point, returns the start of the buffer row that is a given number of
|
||||
/// buffer rows away from the current position.
|
||||
///
|
||||
/// This moves by buffer rows instead of display rows, a distinction that is
|
||||
/// important when soft wrapping is enabled.
|
||||
pub(crate) fn start_of_relative_buffer_row(
|
||||
map: &DisplaySnapshot,
|
||||
point: DisplayPoint,
|
||||
times: isize,
|
||||
) -> DisplayPoint {
|
||||
let start = map.display_point_to_fold_point(point, Bias::Left);
|
||||
let target = start.row() as isize + times;
|
||||
let new_row = (target.max(0) as u32).min(map.fold_snapshot().max_point().row());
|
||||
|
||||
map.clip_point(
|
||||
map.fold_point_to_display_point(
|
||||
map.fold_snapshot()
|
||||
.clip_point(FoldPoint::new(new_row, 0), Bias::Right),
|
||||
),
|
||||
Bias::Right,
|
||||
)
|
||||
}
|
||||
|
||||
fn up_down_buffer_rows(
|
||||
map: &DisplaySnapshot,
|
||||
mut point: DisplayPoint,
|
||||
@@ -2104,7 +2127,7 @@ pub(crate) fn end_of_line(
|
||||
times: usize,
|
||||
) -> DisplayPoint {
|
||||
if times > 1 {
|
||||
point = map.start_of_relative_buffer_row(point, times as isize - 1);
|
||||
point = start_of_relative_buffer_row(map, point, times as isize - 1);
|
||||
}
|
||||
if display_lines {
|
||||
map.clip_point(
|
||||
@@ -2709,17 +2732,17 @@ fn sneak_backward(
|
||||
}
|
||||
|
||||
fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
|
||||
let correct_line = map.start_of_relative_buffer_row(point, times as isize);
|
||||
let correct_line = start_of_relative_buffer_row(map, point, times as isize);
|
||||
first_non_whitespace(map, false, correct_line)
|
||||
}
|
||||
|
||||
fn previous_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
|
||||
let correct_line = map.start_of_relative_buffer_row(point, -(times as isize));
|
||||
let correct_line = start_of_relative_buffer_row(map, point, -(times as isize));
|
||||
first_non_whitespace(map, false, correct_line)
|
||||
}
|
||||
|
||||
fn go_to_column(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
|
||||
let correct_line = map.start_of_relative_buffer_row(point, 0);
|
||||
let correct_line = start_of_relative_buffer_row(map, point, 0);
|
||||
right(map, correct_line, times.saturating_sub(1))
|
||||
}
|
||||
|
||||
@@ -2729,7 +2752,7 @@ pub(crate) fn next_line_end(
|
||||
times: usize,
|
||||
) -> DisplayPoint {
|
||||
if times > 1 {
|
||||
point = map.start_of_relative_buffer_row(point, times as isize - 1);
|
||||
point = start_of_relative_buffer_row(map, point, times as isize - 1);
|
||||
}
|
||||
end_of_line(map, false, point, 1)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ use editor::Editor;
|
||||
use editor::{Anchor, SelectionEffects};
|
||||
use editor::{Bias, ToPoint};
|
||||
use editor::{display_map::ToDisplayPoint, movement};
|
||||
use gpui::{Context, Window, actions};
|
||||
use gpui::{Action, Context, Window, actions};
|
||||
use language::{Point, SelectionGoal};
|
||||
use log::error;
|
||||
use multi_buffer::MultiBufferRow;
|
||||
@@ -123,6 +123,8 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||
Vim::action(editor, cx, Vim::toggle_comments);
|
||||
Vim::action(editor, cx, Vim::paste);
|
||||
Vim::action(editor, cx, Vim::show_location);
|
||||
Vim::action(editor, cx, Vim::go_to_tab);
|
||||
Vim::action(editor, cx, Vim::go_to_previous_tab);
|
||||
|
||||
Vim::action(editor, cx, |vim, _: &DeleteLeft, window, cx| {
|
||||
vim.record_current_action(cx);
|
||||
@@ -679,7 +681,7 @@ impl Vim {
|
||||
editor.edit_with_autoindent(edits, cx);
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_cursors_with(|map, cursor, _| {
|
||||
let previous_line = map.start_of_relative_buffer_row(cursor, -1);
|
||||
let previous_line = motion::start_of_relative_buffer_row(map, cursor, -1);
|
||||
let insert_point = motion::end_of_line(map, false, previous_line, 1);
|
||||
(insert_point, SelectionGoal::None)
|
||||
});
|
||||
@@ -1012,8 +1014,55 @@ impl Vim {
|
||||
self.switch_mode(Mode::Insert, true, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn go_to_tab(&mut self, _: &GoToTab, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let count = Vim::take_count(cx);
|
||||
Vim::take_forced_motion(cx);
|
||||
|
||||
if let Some(tab_index) = count {
|
||||
// <count>gt goes to tab <count> (1-based).
|
||||
let zero_based_index = tab_index.saturating_sub(1);
|
||||
window.dispatch_action(
|
||||
workspace::pane::ActivateItem(zero_based_index).boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
// If no count is provided, go to the next tab.
|
||||
window.dispatch_action(workspace::pane::ActivateNextItem.boxed_clone(), cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn go_to_previous_tab(
|
||||
&mut self,
|
||||
_: &GoToPreviousTab,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let count = Vim::take_count(cx);
|
||||
Vim::take_forced_motion(cx);
|
||||
|
||||
if let Some(count) = count {
|
||||
// gT with count goes back that many tabs with wraparound (not the same as gt!).
|
||||
if let Some(workspace) = self.workspace(window) {
|
||||
let pane = workspace.read(cx).active_pane().read(cx);
|
||||
let item_count = pane.items().count();
|
||||
if item_count > 0 {
|
||||
let current_index = pane.active_item_index();
|
||||
let target_index = (current_index as isize - count as isize)
|
||||
.rem_euclid(item_count as isize)
|
||||
as usize;
|
||||
window.dispatch_action(
|
||||
workspace::pane::ActivateItem(target_index).boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No count provided, go to the previous tab.
|
||||
window.dispatch_action(workspace::pane::ActivatePreviousItem.boxed_clone(), cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use gpui::{KeyBinding, TestAppContext, UpdateGlobal};
|
||||
|
||||
@@ -51,10 +51,7 @@ use vim_mode_setting::HelixModeSetting;
|
||||
use vim_mode_setting::VimModeSetting;
|
||||
use workspace::{self, Pane, Workspace};
|
||||
|
||||
use crate::{
|
||||
normal::{GoToPreviousTab, GoToTab},
|
||||
state::ReplayableAction,
|
||||
};
|
||||
use crate::state::ReplayableAction;
|
||||
|
||||
/// Number is used to manage vim's count. Pushing a digit
|
||||
/// multiplies the current value by 10 and adds the digit.
|
||||
@@ -412,46 +409,6 @@ pub fn init(cx: &mut App) {
|
||||
cx.defer_in(window, |vim, window, cx| vim.search_submit(window, cx))
|
||||
})
|
||||
});
|
||||
workspace.register_action(|_, _: &GoToTab, window, cx| {
|
||||
let count = Vim::take_count(cx);
|
||||
Vim::take_forced_motion(cx);
|
||||
|
||||
if let Some(tab_index) = count {
|
||||
// <count>gt goes to tab <count> (1-based).
|
||||
let zero_based_index = tab_index.saturating_sub(1);
|
||||
window.dispatch_action(
|
||||
workspace::pane::ActivateItem(zero_based_index).boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
// If no count is provided, go to the next tab.
|
||||
window.dispatch_action(workspace::pane::ActivateNextItem.boxed_clone(), cx);
|
||||
}
|
||||
});
|
||||
|
||||
workspace.register_action(|workspace, _: &GoToPreviousTab, window, cx| {
|
||||
let count = Vim::take_count(cx);
|
||||
Vim::take_forced_motion(cx);
|
||||
|
||||
if let Some(count) = count {
|
||||
// gT with count goes back that many tabs with wraparound (not the same as gt!).
|
||||
let pane = workspace.active_pane().read(cx);
|
||||
let item_count = pane.items().count();
|
||||
if item_count > 0 {
|
||||
let current_index = pane.active_item_index();
|
||||
let target_index = (current_index as isize - count as isize)
|
||||
.rem_euclid(item_count as isize)
|
||||
as usize;
|
||||
window.dispatch_action(
|
||||
workspace::pane::ActivateItem(target_index).boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// No count provided, go to the previous tab.
|
||||
window.dispatch_action(workspace::pane::ActivatePreviousItem.boxed_clone(), cx);
|
||||
}
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@ use workspace::searchable::Direction;
|
||||
|
||||
use crate::{
|
||||
Vim,
|
||||
motion::{Motion, MotionKind, first_non_whitespace, next_line_end, start_of_line},
|
||||
motion::{
|
||||
Motion, MotionKind, first_non_whitespace, next_line_end, start_of_line,
|
||||
start_of_relative_buffer_row,
|
||||
},
|
||||
object::Object,
|
||||
state::{Mark, Mode, Operator},
|
||||
};
|
||||
@@ -403,9 +406,7 @@ impl Vim {
|
||||
// Move to the next or previous buffer row, ensuring that
|
||||
// wrapped lines are handled correctly.
|
||||
let direction = if tail.row() > head.row() { -1 } else { 1 };
|
||||
row = map
|
||||
.start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
|
||||
.row();
|
||||
row = start_of_relative_buffer_row(map, DisplayPoint::new(row, 0), direction).row();
|
||||
}
|
||||
|
||||
s.select(selections);
|
||||
|
||||
@@ -24,7 +24,6 @@ test-support = [
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-lock.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
fs.workspace = true
|
||||
|
||||
@@ -64,7 +64,7 @@ use std::{
|
||||
use sum_tree::{Bias, Dimensions, Edit, KeyedItem, SeekTarget, SumTree, Summary, TreeMap, TreeSet};
|
||||
use text::{LineEnding, Rope};
|
||||
use util::{
|
||||
ResultExt, debug_panic, maybe,
|
||||
ResultExt, debug_panic,
|
||||
paths::{PathMatcher, PathStyle, SanitizedPath, home_dir},
|
||||
rel_path::RelPath,
|
||||
};
|
||||
@@ -226,7 +226,7 @@ impl Default for WorkDirectory {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalSnapshot {
|
||||
snapshot: Snapshot,
|
||||
global_gitignore: Option<Arc<Gitignore>>,
|
||||
@@ -239,7 +239,6 @@ pub struct LocalSnapshot {
|
||||
/// The file handle of the worktree root. `None` if the worktree is a directory.
|
||||
/// (so we can find it after it's been moved)
|
||||
root_file_handle: Option<Arc<dyn fs::FileHandle>>,
|
||||
executor: BackgroundExecutor,
|
||||
}
|
||||
|
||||
struct BackgroundScannerState {
|
||||
@@ -322,6 +321,7 @@ impl DerefMut for LocalSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ScanState {
|
||||
Started,
|
||||
Updated {
|
||||
@@ -402,7 +402,6 @@ impl Worktree {
|
||||
PathStyle::local(),
|
||||
),
|
||||
root_file_handle,
|
||||
executor: cx.background_executor().clone(),
|
||||
};
|
||||
|
||||
let worktree_id = snapshot.id();
|
||||
@@ -1070,7 +1069,7 @@ impl LocalWorktree {
|
||||
scan_requests_rx,
|
||||
path_prefixes_to_scan_rx,
|
||||
next_entry_id,
|
||||
state: async_lock::Mutex::new(BackgroundScannerState {
|
||||
state: Mutex::new(BackgroundScannerState {
|
||||
prev_snapshot: snapshot.snapshot.clone(),
|
||||
snapshot,
|
||||
scanned_dirs: Default::default(),
|
||||
@@ -2443,7 +2442,7 @@ impl LocalSnapshot {
|
||||
log::trace!("insert entry {:?}", entry.path);
|
||||
if entry.is_file() && entry.path.file_name() == Some(&GITIGNORE) {
|
||||
let abs_path = self.absolutize(&entry.path);
|
||||
match self.executor.block(build_gitignore(&abs_path, fs)) {
|
||||
match smol::block_on(build_gitignore(&abs_path, fs)) {
|
||||
Ok(ignore) => {
|
||||
self.ignores_by_parent_abs_path
|
||||
.insert(abs_path.parent().unwrap().into(), (Arc::new(ignore), true));
|
||||
@@ -2494,12 +2493,7 @@ impl LocalSnapshot {
|
||||
inodes
|
||||
}
|
||||
|
||||
async fn ignore_stack_for_abs_path(
|
||||
&self,
|
||||
abs_path: &Path,
|
||||
is_dir: bool,
|
||||
fs: &dyn Fs,
|
||||
) -> IgnoreStack {
|
||||
fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool, fs: &dyn Fs) -> IgnoreStack {
|
||||
let mut new_ignores = Vec::new();
|
||||
let mut repo_root = None;
|
||||
for (index, ancestor) in abs_path.ancestors().enumerate() {
|
||||
@@ -2510,8 +2504,9 @@ impl LocalSnapshot {
|
||||
new_ignores.push((ancestor, None));
|
||||
}
|
||||
}
|
||||
|
||||
let metadata = fs.metadata(&ancestor.join(DOT_GIT)).await.ok().flatten();
|
||||
let metadata = smol::block_on(fs.metadata(&ancestor.join(DOT_GIT)))
|
||||
.ok()
|
||||
.flatten();
|
||||
if metadata.is_some() {
|
||||
repo_root = Some(Arc::from(ancestor));
|
||||
break;
|
||||
@@ -2657,7 +2652,7 @@ impl BackgroundScannerState {
|
||||
.any(|p| entry.path.starts_with(p))
|
||||
}
|
||||
|
||||
async fn enqueue_scan_dir(
|
||||
fn enqueue_scan_dir(
|
||||
&self,
|
||||
abs_path: Arc<Path>,
|
||||
entry: &Entry,
|
||||
@@ -2665,10 +2660,7 @@ impl BackgroundScannerState {
|
||||
fs: &dyn Fs,
|
||||
) {
|
||||
let path = entry.path.clone();
|
||||
let ignore_stack = self
|
||||
.snapshot
|
||||
.ignore_stack_for_abs_path(&abs_path, true, fs)
|
||||
.await;
|
||||
let ignore_stack = self.snapshot.ignore_stack_for_abs_path(&abs_path, true, fs);
|
||||
let mut ancestor_inodes = self.snapshot.ancestor_inodes_for_path(&path);
|
||||
|
||||
if !ancestor_inodes.contains(&entry.inode) {
|
||||
@@ -2706,17 +2698,11 @@ impl BackgroundScannerState {
|
||||
}
|
||||
}
|
||||
|
||||
async fn insert_entry(
|
||||
&mut self,
|
||||
mut entry: Entry,
|
||||
fs: &dyn Fs,
|
||||
watcher: &dyn Watcher,
|
||||
) -> Entry {
|
||||
fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs, watcher: &dyn Watcher) -> Entry {
|
||||
self.reuse_entry_id(&mut entry);
|
||||
let entry = self.snapshot.insert_entry(entry, fs);
|
||||
if entry.path.file_name() == Some(&DOT_GIT) {
|
||||
self.insert_git_repository(entry.path.clone(), fs, watcher)
|
||||
.await;
|
||||
self.insert_git_repository(entry.path.clone(), fs, watcher);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -2847,7 +2833,7 @@ impl BackgroundScannerState {
|
||||
self.snapshot.check_invariants(false);
|
||||
}
|
||||
|
||||
async fn insert_git_repository(
|
||||
fn insert_git_repository(
|
||||
&mut self,
|
||||
dot_git_path: Arc<RelPath>,
|
||||
fs: &dyn Fs,
|
||||
@@ -2888,11 +2874,10 @@ impl BackgroundScannerState {
|
||||
fs,
|
||||
watcher,
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
|
||||
async fn insert_git_repository_for_path(
|
||||
fn insert_git_repository_for_path(
|
||||
&mut self,
|
||||
work_directory: WorkDirectory,
|
||||
dot_git_abs_path: Arc<Path>,
|
||||
@@ -2914,7 +2899,7 @@ impl BackgroundScannerState {
|
||||
let work_directory_abs_path = self.snapshot.work_directory_abs_path(&work_directory);
|
||||
|
||||
let (repository_dir_abs_path, common_dir_abs_path) =
|
||||
discover_git_paths(&dot_git_abs_path, fs).await;
|
||||
discover_git_paths(&dot_git_abs_path, fs);
|
||||
watcher
|
||||
.add(&common_dir_abs_path)
|
||||
.context("failed to add common directory to watcher")
|
||||
@@ -3558,7 +3543,7 @@ impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey {
|
||||
}
|
||||
|
||||
struct BackgroundScanner {
|
||||
state: async_lock::Mutex<BackgroundScannerState>,
|
||||
state: Mutex<BackgroundScannerState>,
|
||||
fs: Arc<dyn Fs>,
|
||||
fs_case_sensitive: bool,
|
||||
status_updates_tx: UnboundedSender<ScanState>,
|
||||
@@ -3584,39 +3569,31 @@ impl BackgroundScanner {
|
||||
// If the worktree root does not contain a git repository, then find
|
||||
// the git repository in an ancestor directory. Find any gitignore files
|
||||
// in ancestor directories.
|
||||
let root_abs_path = self.state.lock().await.snapshot.abs_path.clone();
|
||||
let root_abs_path = self.state.lock().snapshot.abs_path.clone();
|
||||
let (ignores, repo) = discover_ancestor_git_repo(self.fs.clone(), &root_abs_path).await;
|
||||
self.state
|
||||
.lock()
|
||||
.await
|
||||
.snapshot
|
||||
.ignores_by_parent_abs_path
|
||||
.extend(ignores);
|
||||
let containing_git_repository = if let Some((ancestor_dot_git, work_directory)) = repo {
|
||||
maybe!(async {
|
||||
self.state
|
||||
.lock()
|
||||
.await
|
||||
.insert_git_repository_for_path(
|
||||
work_directory,
|
||||
ancestor_dot_git.clone().into(),
|
||||
self.fs.as_ref(),
|
||||
self.watcher.as_ref(),
|
||||
)
|
||||
.await
|
||||
.log_err()?;
|
||||
Some(ancestor_dot_git)
|
||||
})
|
||||
.await
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let containing_git_repository = repo.and_then(|(ancestor_dot_git, work_directory)| {
|
||||
self.state
|
||||
.lock()
|
||||
.insert_git_repository_for_path(
|
||||
work_directory,
|
||||
ancestor_dot_git.clone().into(),
|
||||
self.fs.as_ref(),
|
||||
self.watcher.as_ref(),
|
||||
)
|
||||
.log_err()?;
|
||||
Some(ancestor_dot_git)
|
||||
});
|
||||
|
||||
log::trace!("containing git repository: {containing_git_repository:?}");
|
||||
|
||||
let mut global_gitignore_events =
|
||||
if let Some(global_gitignore_path) = &paths::global_gitignore_path() {
|
||||
self.state.lock().await.snapshot.global_gitignore =
|
||||
self.state.lock().snapshot.global_gitignore =
|
||||
if self.fs.is_file(&global_gitignore_path).await {
|
||||
build_gitignore(global_gitignore_path, self.fs.as_ref())
|
||||
.await
|
||||
@@ -3630,34 +3607,31 @@ impl BackgroundScanner {
|
||||
.await
|
||||
.0
|
||||
} else {
|
||||
self.state.lock().await.snapshot.global_gitignore = None;
|
||||
self.state.lock().snapshot.global_gitignore = None;
|
||||
Box::pin(futures::stream::empty())
|
||||
};
|
||||
|
||||
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
||||
{
|
||||
let mut state = self.state.lock().await;
|
||||
let mut state = self.state.lock();
|
||||
state.snapshot.scan_id += 1;
|
||||
if let Some(mut root_entry) = state.snapshot.root_entry().cloned() {
|
||||
let ignore_stack = state
|
||||
.snapshot
|
||||
.ignore_stack_for_abs_path(root_abs_path.as_path(), true, self.fs.as_ref())
|
||||
.await;
|
||||
let ignore_stack = state.snapshot.ignore_stack_for_abs_path(
|
||||
root_abs_path.as_path(),
|
||||
true,
|
||||
self.fs.as_ref(),
|
||||
);
|
||||
if ignore_stack.is_abs_path_ignored(root_abs_path.as_path(), true) {
|
||||
root_entry.is_ignored = true;
|
||||
state
|
||||
.insert_entry(root_entry.clone(), self.fs.as_ref(), self.watcher.as_ref())
|
||||
.await;
|
||||
state.insert_entry(root_entry.clone(), self.fs.as_ref(), self.watcher.as_ref());
|
||||
}
|
||||
if root_entry.is_dir() {
|
||||
state
|
||||
.enqueue_scan_dir(
|
||||
root_abs_path.as_path().into(),
|
||||
&root_entry,
|
||||
&scan_job_tx,
|
||||
self.fs.as_ref(),
|
||||
)
|
||||
.await;
|
||||
state.enqueue_scan_dir(
|
||||
root_abs_path.as_path().into(),
|
||||
&root_entry,
|
||||
&scan_job_tx,
|
||||
self.fs.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -3666,11 +3640,11 @@ impl BackgroundScanner {
|
||||
drop(scan_job_tx);
|
||||
self.scan_dirs(true, scan_job_rx).await;
|
||||
{
|
||||
let mut state = self.state.lock().await;
|
||||
let mut state = self.state.lock();
|
||||
state.snapshot.completed_scan_id = state.snapshot.scan_id;
|
||||
}
|
||||
|
||||
self.send_status_update(false, SmallVec::new()).await;
|
||||
self.send_status_update(false, SmallVec::new());
|
||||
|
||||
// Process any any FS events that occurred while performing the initial scan.
|
||||
// For these events, update events cannot be as precise, because we didn't
|
||||
@@ -3715,7 +3689,7 @@ impl BackgroundScanner {
|
||||
if did_scan {
|
||||
let abs_path =
|
||||
{
|
||||
let mut state = self.state.lock().await;
|
||||
let mut state = self.state.lock();
|
||||
state.path_prefixes_to_scan.insert(request.path.clone());
|
||||
state.snapshot.absolutize(&request.path)
|
||||
};
|
||||
@@ -3724,7 +3698,7 @@ impl BackgroundScanner {
|
||||
self.process_events(vec![abs_path]).await;
|
||||
}
|
||||
}
|
||||
self.send_status_update(false, request.done).await;
|
||||
self.send_status_update(false, request.done);
|
||||
}
|
||||
|
||||
paths = fs_events_rx.next().fuse() => {
|
||||
@@ -3753,7 +3727,7 @@ impl BackgroundScanner {
|
||||
request.relative_paths.sort_unstable();
|
||||
self.forcibly_load_paths(&request.relative_paths).await;
|
||||
|
||||
let root_path = self.state.lock().await.snapshot.abs_path.clone();
|
||||
let root_path = self.state.lock().snapshot.abs_path.clone();
|
||||
let root_canonical_path = self.fs.canonicalize(root_path.as_path()).await;
|
||||
let root_canonical_path = match &root_canonical_path {
|
||||
Ok(path) => SanitizedPath::new(path),
|
||||
@@ -3775,7 +3749,7 @@ impl BackgroundScanner {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
{
|
||||
let mut state = self.state.lock().await;
|
||||
let mut state = self.state.lock();
|
||||
let is_idle = state.snapshot.completed_scan_id == state.snapshot.scan_id;
|
||||
state.snapshot.scan_id += 1;
|
||||
if is_idle {
|
||||
@@ -3792,12 +3766,12 @@ impl BackgroundScanner {
|
||||
)
|
||||
.await;
|
||||
|
||||
self.send_status_update(scanning, request.done).await
|
||||
self.send_status_update(scanning, request.done)
|
||||
}
|
||||
|
||||
async fn process_events(&self, mut abs_paths: Vec<PathBuf>) {
|
||||
log::trace!("process events: {abs_paths:?}");
|
||||
let root_path = self.state.lock().await.snapshot.abs_path.clone();
|
||||
let root_path = self.state.lock().snapshot.abs_path.clone();
|
||||
let root_canonical_path = self.fs.canonicalize(root_path.as_path()).await;
|
||||
let root_canonical_path = match &root_canonical_path {
|
||||
Ok(path) => SanitizedPath::new(path),
|
||||
@@ -3805,7 +3779,6 @@ impl BackgroundScanner {
|
||||
let new_path = self
|
||||
.state
|
||||
.lock()
|
||||
.await
|
||||
.snapshot
|
||||
.root_file_handle
|
||||
.clone()
|
||||
@@ -3838,31 +3811,24 @@ impl BackgroundScanner {
|
||||
let mut dot_git_abs_paths = Vec::new();
|
||||
abs_paths.sort_unstable();
|
||||
abs_paths.dedup_by(|a, b| a.starts_with(b));
|
||||
{
|
||||
let snapshot = &self.state.lock().await.snapshot;
|
||||
abs_paths.retain(|abs_path| {
|
||||
abs_paths.retain(|abs_path| {
|
||||
let abs_path = &SanitizedPath::new(abs_path);
|
||||
|
||||
|
||||
let snapshot = &self.state.lock().snapshot;
|
||||
{
|
||||
let mut is_git_related = false;
|
||||
|
||||
let dot_git_paths = self.executor.block(maybe!(async {
|
||||
let mut path = None;
|
||||
for ancestor in abs_path.as_path().ancestors() {
|
||||
|
||||
if is_git_dir(ancestor, self.fs.as_ref()).await {
|
||||
let dot_git_paths = abs_path.as_path().ancestors().find_map(|ancestor| {
|
||||
if smol::block_on(is_git_dir(ancestor, self.fs.as_ref())) {
|
||||
let path_in_git_dir = abs_path
|
||||
.as_path()
|
||||
.strip_prefix(ancestor)
|
||||
.expect("stripping off the ancestor");
|
||||
path = Some((ancestor.to_owned(), path_in_git_dir.to_owned()));
|
||||
break;
|
||||
Some((ancestor.to_owned(), path_in_git_dir.to_owned()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
path
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
if let Some((dot_git_abs_path, path_in_git_dir)) = dot_git_paths {
|
||||
if skipped_files_in_dot_git
|
||||
@@ -3935,12 +3901,12 @@ impl BackgroundScanner {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if relative_paths.is_empty() && dot_git_abs_paths.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.state.lock().await.snapshot.scan_id += 1;
|
||||
self.state.lock().snapshot.scan_id += 1;
|
||||
|
||||
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
||||
log::debug!("received fs events {:?}", relative_paths);
|
||||
@@ -3954,29 +3920,29 @@ impl BackgroundScanner {
|
||||
.await;
|
||||
|
||||
let affected_repo_roots = if !dot_git_abs_paths.is_empty() {
|
||||
self.update_git_repositories(dot_git_abs_paths).await
|
||||
self.update_git_repositories(dot_git_abs_paths)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
{
|
||||
let mut ignores_to_update = self.ignores_needing_update().await;
|
||||
let mut ignores_to_update = self.ignores_needing_update();
|
||||
ignores_to_update.extend(affected_repo_roots);
|
||||
let ignores_to_update = self.order_ignores(ignores_to_update).await;
|
||||
let snapshot = self.state.lock().await.snapshot.clone();
|
||||
let ignores_to_update = self.order_ignores(ignores_to_update);
|
||||
let snapshot = self.state.lock().snapshot.clone();
|
||||
self.update_ignore_statuses_for_paths(scan_job_tx, snapshot, ignores_to_update)
|
||||
.await;
|
||||
self.scan_dirs(false, scan_job_rx).await;
|
||||
}
|
||||
|
||||
{
|
||||
let mut state = self.state.lock().await;
|
||||
let mut state = self.state.lock();
|
||||
state.snapshot.completed_scan_id = state.snapshot.scan_id;
|
||||
for (_, entry) in mem::take(&mut state.removed_entries) {
|
||||
state.scanned_dirs.remove(&entry.id);
|
||||
}
|
||||
}
|
||||
self.send_status_update(false, SmallVec::new()).await;
|
||||
self.send_status_update(false, SmallVec::new());
|
||||
}
|
||||
|
||||
async fn update_global_gitignore(&self, abs_path: &Path) {
|
||||
@@ -3985,30 +3951,30 @@ impl BackgroundScanner {
|
||||
.log_err()
|
||||
.map(Arc::new);
|
||||
let (prev_snapshot, ignore_stack, abs_path) = {
|
||||
let mut state = self.state.lock().await;
|
||||
let mut state = self.state.lock();
|
||||
state.snapshot.global_gitignore = ignore;
|
||||
let abs_path = state.snapshot.abs_path().clone();
|
||||
let ignore_stack = state
|
||||
.snapshot
|
||||
.ignore_stack_for_abs_path(&abs_path, true, self.fs.as_ref())
|
||||
.await;
|
||||
let ignore_stack =
|
||||
state
|
||||
.snapshot
|
||||
.ignore_stack_for_abs_path(&abs_path, true, self.fs.as_ref());
|
||||
(state.snapshot.clone(), ignore_stack, abs_path)
|
||||
};
|
||||
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
||||
self.update_ignore_statuses_for_paths(
|
||||
scan_job_tx,
|
||||
prev_snapshot,
|
||||
vec![(abs_path, ignore_stack)],
|
||||
vec![(abs_path, ignore_stack)].into_iter(),
|
||||
)
|
||||
.await;
|
||||
self.scan_dirs(false, scan_job_rx).await;
|
||||
self.send_status_update(false, SmallVec::new()).await;
|
||||
self.send_status_update(false, SmallVec::new());
|
||||
}
|
||||
|
||||
async fn forcibly_load_paths(&self, paths: &[Arc<RelPath>]) -> bool {
|
||||
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
||||
{
|
||||
let mut state = self.state.lock().await;
|
||||
let mut state = self.state.lock();
|
||||
let root_path = state.snapshot.abs_path.clone();
|
||||
for path in paths {
|
||||
for ancestor in path.ancestors() {
|
||||
@@ -4016,14 +3982,12 @@ impl BackgroundScanner {
|
||||
&& entry.kind == EntryKind::UnloadedDir
|
||||
{
|
||||
let abs_path = root_path.join(ancestor.as_std_path());
|
||||
state
|
||||
.enqueue_scan_dir(
|
||||
abs_path.into(),
|
||||
entry,
|
||||
&scan_job_tx,
|
||||
self.fs.as_ref(),
|
||||
)
|
||||
.await;
|
||||
state.enqueue_scan_dir(
|
||||
abs_path.into(),
|
||||
entry,
|
||||
&scan_job_tx,
|
||||
self.fs.as_ref(),
|
||||
);
|
||||
state.paths_to_scan.insert(path.clone());
|
||||
break;
|
||||
}
|
||||
@@ -4035,7 +3999,7 @@ impl BackgroundScanner {
|
||||
self.scan_dir(&job).await.log_err();
|
||||
}
|
||||
|
||||
!mem::take(&mut self.state.lock().await.paths_to_scan).is_empty()
|
||||
!mem::take(&mut self.state.lock().paths_to_scan).is_empty()
|
||||
}
|
||||
|
||||
async fn scan_dirs(
|
||||
@@ -4083,7 +4047,7 @@ impl BackgroundScanner {
|
||||
) {
|
||||
Ok(_) => {
|
||||
last_progress_update_count += 1;
|
||||
self.send_status_update(true, SmallVec::new()).await;
|
||||
self.send_status_update(true, SmallVec::new());
|
||||
}
|
||||
Err(count) => {
|
||||
last_progress_update_count = count;
|
||||
@@ -4108,12 +4072,8 @@ impl BackgroundScanner {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn send_status_update(
|
||||
&self,
|
||||
scanning: bool,
|
||||
barrier: SmallVec<[barrier::Sender; 1]>,
|
||||
) -> bool {
|
||||
let mut state = self.state.lock().await;
|
||||
fn send_status_update(&self, scanning: bool, barrier: SmallVec<[barrier::Sender; 1]>) -> bool {
|
||||
let mut state = self.state.lock();
|
||||
if state.changed_paths.is_empty() && scanning {
|
||||
return true;
|
||||
}
|
||||
@@ -4142,7 +4102,7 @@ impl BackgroundScanner {
|
||||
let root_abs_path;
|
||||
let root_char_bag;
|
||||
{
|
||||
let snapshot = &self.state.lock().await.snapshot;
|
||||
let snapshot = &self.state.lock().snapshot;
|
||||
if self.settings.is_path_excluded(&job.path) {
|
||||
log::error!("skipping excluded directory {:?}", job.path);
|
||||
return Ok(());
|
||||
@@ -4195,14 +4155,12 @@ impl BackgroundScanner {
|
||||
};
|
||||
|
||||
if child_name == DOT_GIT {
|
||||
let mut state = self.state.lock().await;
|
||||
state
|
||||
.insert_git_repository(
|
||||
child_path.clone(),
|
||||
self.fs.as_ref(),
|
||||
self.watcher.as_ref(),
|
||||
)
|
||||
.await;
|
||||
let mut state = self.state.lock();
|
||||
state.insert_git_repository(
|
||||
child_path.clone(),
|
||||
self.fs.as_ref(),
|
||||
self.watcher.as_ref(),
|
||||
);
|
||||
} else if child_name == GITIGNORE {
|
||||
match build_gitignore(&child_abs_path, self.fs.as_ref()).await {
|
||||
Ok(ignore) => {
|
||||
@@ -4222,7 +4180,7 @@ impl BackgroundScanner {
|
||||
|
||||
if self.settings.is_path_excluded(&child_path) {
|
||||
log::debug!("skipping excluded child entry {child_path:?}");
|
||||
self.state.lock().await.remove_path(&child_path);
|
||||
self.state.lock().remove_path(&child_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -4322,7 +4280,7 @@ impl BackgroundScanner {
|
||||
new_entries.push(child_entry);
|
||||
}
|
||||
|
||||
let mut state = self.state.lock().await;
|
||||
let mut state = self.state.lock();
|
||||
|
||||
// Identify any subdirectories that should not be scanned.
|
||||
let mut job_ix = 0;
|
||||
@@ -4404,7 +4362,7 @@ impl BackgroundScanner {
|
||||
None
|
||||
};
|
||||
|
||||
let mut state = self.state.lock().await;
|
||||
let mut state = self.state.lock();
|
||||
let doing_recursive_update = scan_queue_tx.is_some();
|
||||
|
||||
// Remove any entries for paths that no longer exist or are being recursively
|
||||
@@ -4420,10 +4378,11 @@ impl BackgroundScanner {
|
||||
let abs_path: Arc<Path> = root_abs_path.join(path.as_std_path()).into();
|
||||
match metadata {
|
||||
Ok(Some((metadata, canonical_path))) => {
|
||||
let ignore_stack = state
|
||||
.snapshot
|
||||
.ignore_stack_for_abs_path(&abs_path, metadata.is_dir, self.fs.as_ref())
|
||||
.await;
|
||||
let ignore_stack = state.snapshot.ignore_stack_for_abs_path(
|
||||
&abs_path,
|
||||
metadata.is_dir,
|
||||
self.fs.as_ref(),
|
||||
);
|
||||
let is_external = !canonical_path.starts_with(&root_canonical_path);
|
||||
let mut fs_entry = Entry::new(
|
||||
path.clone(),
|
||||
@@ -4455,22 +4414,18 @@ impl BackgroundScanner {
|
||||
|| (fs_entry.path.is_empty()
|
||||
&& abs_path.file_name() == Some(OsStr::new(DOT_GIT)))
|
||||
{
|
||||
state
|
||||
.enqueue_scan_dir(
|
||||
abs_path,
|
||||
&fs_entry,
|
||||
scan_queue_tx,
|
||||
self.fs.as_ref(),
|
||||
)
|
||||
.await;
|
||||
state.enqueue_scan_dir(
|
||||
abs_path,
|
||||
&fs_entry,
|
||||
scan_queue_tx,
|
||||
self.fs.as_ref(),
|
||||
);
|
||||
} else {
|
||||
fs_entry.kind = EntryKind::UnloadedDir;
|
||||
}
|
||||
}
|
||||
|
||||
state
|
||||
.insert_entry(fs_entry.clone(), self.fs.as_ref(), self.watcher.as_ref())
|
||||
.await;
|
||||
state.insert_entry(fs_entry.clone(), self.fs.as_ref(), self.watcher.as_ref());
|
||||
|
||||
if path.is_empty()
|
||||
&& let Some((ignores, repo)) = new_ancestor_repo.take()
|
||||
@@ -4485,7 +4440,6 @@ impl BackgroundScanner {
|
||||
self.fs.as_ref(),
|
||||
self.watcher.as_ref(),
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
@@ -4524,11 +4478,11 @@ impl BackgroundScanner {
|
||||
&self,
|
||||
scan_job_tx: Sender<ScanJob>,
|
||||
prev_snapshot: LocalSnapshot,
|
||||
ignores_to_update: Vec<(Arc<Path>, IgnoreStack)>,
|
||||
mut ignores_to_update: impl Iterator<Item = (Arc<Path>, IgnoreStack)>,
|
||||
) {
|
||||
let (ignore_queue_tx, ignore_queue_rx) = channel::unbounded();
|
||||
{
|
||||
for (parent_abs_path, ignore_stack) in ignores_to_update {
|
||||
while let Some((parent_abs_path, ignore_stack)) = ignores_to_update.next() {
|
||||
ignore_queue_tx
|
||||
.send_blocking(UpdateIgnoreStatusJob {
|
||||
abs_path: parent_abs_path,
|
||||
@@ -4569,11 +4523,11 @@ impl BackgroundScanner {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn ignores_needing_update(&self) -> Vec<Arc<Path>> {
|
||||
fn ignores_needing_update(&self) -> Vec<Arc<Path>> {
|
||||
let mut ignores_to_update = Vec::new();
|
||||
|
||||
{
|
||||
let snapshot = &mut self.state.lock().await.snapshot;
|
||||
let snapshot = &mut self.state.lock().snapshot;
|
||||
let abs_path = snapshot.abs_path.clone();
|
||||
snapshot
|
||||
.ignores_by_parent_abs_path
|
||||
@@ -4601,27 +4555,26 @@ impl BackgroundScanner {
|
||||
ignores_to_update
|
||||
}
|
||||
|
||||
async fn order_ignores(&self, mut ignores: Vec<Arc<Path>>) -> Vec<(Arc<Path>, IgnoreStack)> {
|
||||
fn order_ignores(
|
||||
&self,
|
||||
mut ignores: Vec<Arc<Path>>,
|
||||
) -> impl use<> + Iterator<Item = (Arc<Path>, IgnoreStack)> {
|
||||
let fs = self.fs.clone();
|
||||
let snapshot = self.state.lock().await.snapshot.clone();
|
||||
let snapshot = self.state.lock().snapshot.clone();
|
||||
ignores.sort_unstable();
|
||||
let mut ignores_to_update = ignores.into_iter().peekable();
|
||||
|
||||
let mut result = vec![];
|
||||
while let Some(parent_abs_path) = ignores_to_update.next() {
|
||||
std::iter::from_fn(move || {
|
||||
let parent_abs_path = ignores_to_update.next()?;
|
||||
while ignores_to_update
|
||||
.peek()
|
||||
.map_or(false, |p| p.starts_with(&parent_abs_path))
|
||||
{
|
||||
ignores_to_update.next().unwrap();
|
||||
}
|
||||
let ignore_stack = snapshot
|
||||
.ignore_stack_for_abs_path(&parent_abs_path, true, fs.as_ref())
|
||||
.await;
|
||||
result.push((parent_abs_path, ignore_stack));
|
||||
}
|
||||
|
||||
result
|
||||
let ignore_stack =
|
||||
snapshot.ignore_stack_for_abs_path(&parent_abs_path, true, fs.as_ref());
|
||||
Some((parent_abs_path, ignore_stack))
|
||||
})
|
||||
}
|
||||
|
||||
async fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &LocalSnapshot) {
|
||||
@@ -4653,7 +4606,7 @@ impl BackgroundScanner {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Ok(Some(metadata)) = self.fs.metadata(&job.abs_path.join(DOT_GIT)).await
|
||||
if let Ok(Some(metadata)) = smol::block_on(self.fs.metadata(&job.abs_path.join(DOT_GIT)))
|
||||
&& metadata.is_dir
|
||||
{
|
||||
ignore_stack.repo_root = Some(job.abs_path.clone());
|
||||
@@ -4673,16 +4626,14 @@ impl BackgroundScanner {
|
||||
|
||||
// Scan any directories that were previously ignored and weren't previously scanned.
|
||||
if was_ignored && !entry.is_ignored && entry.kind.is_unloaded() {
|
||||
let state = self.state.lock().await;
|
||||
let state = self.state.lock();
|
||||
if state.should_scan_directory(&entry) {
|
||||
state
|
||||
.enqueue_scan_dir(
|
||||
abs_path.clone(),
|
||||
&entry,
|
||||
&job.scan_queue,
|
||||
self.fs.as_ref(),
|
||||
)
|
||||
.await;
|
||||
state.enqueue_scan_dir(
|
||||
abs_path.clone(),
|
||||
&entry,
|
||||
&job.scan_queue,
|
||||
self.fs.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4706,7 +4657,7 @@ impl BackgroundScanner {
|
||||
}
|
||||
}
|
||||
|
||||
let state = &mut self.state.lock().await;
|
||||
let state = &mut self.state.lock();
|
||||
for edit in &entries_by_path_edits {
|
||||
if let Edit::Insert(entry) = edit
|
||||
&& let Err(ix) = state.changed_paths.binary_search(&entry.path)
|
||||
@@ -4722,9 +4673,9 @@ impl BackgroundScanner {
|
||||
state.snapshot.entries_by_id.edit(entries_by_id_edits, ());
|
||||
}
|
||||
|
||||
async fn update_git_repositories(&self, dot_git_paths: Vec<PathBuf>) -> Vec<Arc<Path>> {
|
||||
fn update_git_repositories(&self, dot_git_paths: Vec<PathBuf>) -> Vec<Arc<Path>> {
|
||||
log::trace!("reloading repositories: {dot_git_paths:?}");
|
||||
let mut state = self.state.lock().await;
|
||||
let mut state = self.state.lock();
|
||||
let scan_id = state.snapshot.scan_id;
|
||||
let mut affected_repo_roots = Vec::new();
|
||||
for dot_git_dir in dot_git_paths {
|
||||
@@ -4754,15 +4705,13 @@ impl BackgroundScanner {
|
||||
return Vec::new();
|
||||
};
|
||||
affected_repo_roots.push(dot_git_dir.parent().unwrap().into());
|
||||
state
|
||||
.insert_git_repository(
|
||||
RelPath::new(relative, PathStyle::local())
|
||||
.unwrap()
|
||||
.into_arc(),
|
||||
self.fs.as_ref(),
|
||||
self.watcher.as_ref(),
|
||||
)
|
||||
.await;
|
||||
state.insert_git_repository(
|
||||
RelPath::new(relative, PathStyle::local())
|
||||
.unwrap()
|
||||
.into_arc(),
|
||||
self.fs.as_ref(),
|
||||
self.watcher.as_ref(),
|
||||
);
|
||||
}
|
||||
Some(local_repository) => {
|
||||
state.snapshot.git_repositories.update(
|
||||
@@ -4790,7 +4739,7 @@ impl BackgroundScanner {
|
||||
|
||||
if exists_in_snapshot
|
||||
|| matches!(
|
||||
self.fs.metadata(&entry.common_dir_abs_path).await,
|
||||
smol::block_on(self.fs.metadata(&entry.common_dir_abs_path)),
|
||||
Ok(Some(_))
|
||||
)
|
||||
{
|
||||
@@ -5549,13 +5498,11 @@ fn parse_gitfile(content: &str) -> anyhow::Result<&Path> {
|
||||
Ok(Path::new(path.trim()))
|
||||
}
|
||||
|
||||
async fn discover_git_paths(dot_git_abs_path: &Arc<Path>, fs: &dyn Fs) -> (Arc<Path>, Arc<Path>) {
|
||||
fn discover_git_paths(dot_git_abs_path: &Arc<Path>, fs: &dyn Fs) -> (Arc<Path>, Arc<Path>) {
|
||||
let mut repository_dir_abs_path = dot_git_abs_path.clone();
|
||||
let mut common_dir_abs_path = dot_git_abs_path.clone();
|
||||
|
||||
if let Some(path) = fs
|
||||
.load(dot_git_abs_path)
|
||||
.await
|
||||
if let Some(path) = smol::block_on(fs.load(dot_git_abs_path))
|
||||
.ok()
|
||||
.as_ref()
|
||||
.and_then(|contents| parse_gitfile(contents).log_err())
|
||||
@@ -5564,19 +5511,17 @@ async fn discover_git_paths(dot_git_abs_path: &Arc<Path>, fs: &dyn Fs) -> (Arc<P
|
||||
.parent()
|
||||
.unwrap_or(Path::new(""))
|
||||
.join(path);
|
||||
if let Some(path) = fs.canonicalize(&path).await.log_err() {
|
||||
if let Some(path) = smol::block_on(fs.canonicalize(&path)).log_err() {
|
||||
repository_dir_abs_path = Path::new(&path).into();
|
||||
common_dir_abs_path = repository_dir_abs_path.clone();
|
||||
|
||||
if let Some(commondir_contents) = fs.load(&path.join("commondir")).await.ok()
|
||||
&& let Some(commondir_path) = fs
|
||||
.canonicalize(&path.join(commondir_contents.trim()))
|
||||
.await
|
||||
.log_err()
|
||||
if let Some(commondir_contents) = smol::block_on(fs.load(&path.join("commondir"))).ok()
|
||||
&& let Some(commondir_path) =
|
||||
smol::block_on(fs.canonicalize(&path.join(commondir_contents.trim()))).log_err()
|
||||
{
|
||||
common_dir_abs_path = commondir_path.as_path().into();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(repository_dir_abs_path, common_dir_abs_path)
|
||||
}
|
||||
|
||||
@@ -734,6 +734,7 @@ async fn test_write_file(cx: &mut TestAppContext) {
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
worktree.read_with(cx, |tree, _| {
|
||||
let tracked = tree
|
||||
.entry_for_path(rel_path("tracked-dir/file.txt"))
|
||||
@@ -1536,7 +1537,7 @@ async fn test_random_worktree_operations_during_initial_scan(
|
||||
assert_eq!(
|
||||
updated_snapshot.entries(true, 0).collect::<Vec<_>>(),
|
||||
final_snapshot.entries(true, 0).collect::<Vec<_>>(),
|
||||
"wrong updates after snapshot {i}: {updates:#?}",
|
||||
"wrong updates after snapshot {i}: {snapshot:#?} {updates:#?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "worktree_benchmarks"
|
||||
version = "0.1.0"
|
||||
publish.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fs.workspace = true
|
||||
gpui = { workspace = true, features = ["windows-manifest"] }
|
||||
settings.workspace = true
|
||||
worktree.workspace = true
|
||||
workspace-hack = { version = "0.1", path = "../../tooling/workspace-hack" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,54 +0,0 @@
|
||||
use std::{
|
||||
path::Path,
|
||||
sync::{Arc, atomic::AtomicUsize},
|
||||
};
|
||||
|
||||
use fs::RealFs;
|
||||
use gpui::Application;
|
||||
use settings::Settings;
|
||||
use worktree::{Worktree, WorktreeSettings};
|
||||
|
||||
fn main() {
|
||||
let Some(worktree_root_path) = std::env::args().nth(1) else {
|
||||
println!(
|
||||
"Missing path to worktree root\nUsage: bench_background_scan PATH_TO_WORKTREE_ROOT"
|
||||
);
|
||||
return;
|
||||
};
|
||||
let app = Application::headless();
|
||||
|
||||
app.run(|cx| {
|
||||
settings::init(cx);
|
||||
WorktreeSettings::register(cx);
|
||||
let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let worktree = Worktree::local(
|
||||
Path::new(&worktree_root_path),
|
||||
true,
|
||||
fs,
|
||||
Arc::new(AtomicUsize::new(0)),
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
.expect("Worktree initialization to succeed");
|
||||
let did_finish_scan = worktree
|
||||
.update(cx, |this, _| this.as_local().unwrap().scan_complete())
|
||||
.unwrap();
|
||||
let start = std::time::Instant::now();
|
||||
did_finish_scan.await;
|
||||
let elapsed = start.elapsed();
|
||||
let (files, directories) = worktree
|
||||
.read_with(cx, |this, _| (this.file_count(), this.dir_count()))
|
||||
.unwrap();
|
||||
println!(
|
||||
"{:?} for {directories} directories and {files} files",
|
||||
elapsed
|
||||
);
|
||||
cx.update(|cx| {
|
||||
cx.quit();
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
}
|
||||
@@ -185,18 +185,8 @@ pub fn app_menus(cx: &mut App) -> Vec<Menu> {
|
||||
editor::actions::SelectPreviousSyntaxNode,
|
||||
),
|
||||
MenuItem::separator(),
|
||||
MenuItem::action(
|
||||
"Add Cursor Above",
|
||||
editor::actions::AddSelectionAbove {
|
||||
skip_soft_wrap: true,
|
||||
},
|
||||
),
|
||||
MenuItem::action(
|
||||
"Add Cursor Below",
|
||||
editor::actions::AddSelectionBelow {
|
||||
skip_soft_wrap: true,
|
||||
},
|
||||
),
|
||||
MenuItem::action("Add Cursor Above", editor::actions::AddSelectionAbove),
|
||||
MenuItem::action("Add Cursor Below", editor::actions::AddSelectionBelow),
|
||||
MenuItem::action(
|
||||
"Select Next Occurrence",
|
||||
editor::actions::SelectNext {
|
||||
|
||||
@@ -266,18 +266,8 @@ impl Render for QuickActionBar {
|
||||
)
|
||||
.action("Expand Selection", Box::new(SelectLargerSyntaxNode))
|
||||
.action("Shrink Selection", Box::new(SelectSmallerSyntaxNode))
|
||||
.action(
|
||||
"Add Cursor Above",
|
||||
Box::new(AddSelectionAbove {
|
||||
skip_soft_wrap: true,
|
||||
}),
|
||||
)
|
||||
.action(
|
||||
"Add Cursor Below",
|
||||
Box::new(AddSelectionBelow {
|
||||
skip_soft_wrap: true,
|
||||
}),
|
||||
)
|
||||
.action("Add Cursor Above", Box::new(AddSelectionAbove))
|
||||
.action("Add Cursor Below", Box::new(AddSelectionBelow))
|
||||
.separator()
|
||||
.action("Go to Symbol", Box::new(ToggleOutline))
|
||||
.action("Go to Line/Column", Box::new(ToggleGoToLine))
|
||||
|
||||
@@ -45,7 +45,6 @@ const MAX_EVENT_COUNT: usize = 16;
|
||||
|
||||
pub const DEFAULT_CONTEXT_OPTIONS: EditPredictionContextOptions = EditPredictionContextOptions {
|
||||
use_imports: true,
|
||||
use_references: false,
|
||||
excerpt: EditPredictionExcerptOptions {
|
||||
max_bytes: 512,
|
||||
min_bytes: 128,
|
||||
|
||||
@@ -20,11 +20,9 @@ use ui::{ContextMenu, ContextMenuEntry, DropdownMenu, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use util::{ResultExt, paths::PathStyle, rel_path::RelPath};
|
||||
use workspace::{Item, SplitDirection, Workspace};
|
||||
use zeta2::{PredictionDebugInfo, Zeta, ZetaOptions};
|
||||
use zeta2::{DEFAULT_CONTEXT_OPTIONS, PredictionDebugInfo, Zeta, ZetaOptions};
|
||||
|
||||
use edit_prediction_context::{
|
||||
DeclarationStyle, EditPredictionContextOptions, EditPredictionExcerptOptions,
|
||||
};
|
||||
use edit_prediction_context::{DeclarationStyle, EditPredictionExcerptOptions};
|
||||
|
||||
actions!(
|
||||
dev,
|
||||
@@ -234,20 +232,17 @@ impl Zeta2Inspector {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
let zeta_options = this.zeta.read(cx).options().clone();
|
||||
|
||||
let context_options = EditPredictionContextOptions {
|
||||
excerpt: EditPredictionExcerptOptions {
|
||||
max_bytes: number_input_value(&this.max_excerpt_bytes_input, cx),
|
||||
min_bytes: number_input_value(&this.min_excerpt_bytes_input, cx),
|
||||
target_before_cursor_over_total_bytes: number_input_value(
|
||||
&this.cursor_context_ratio_input,
|
||||
cx,
|
||||
),
|
||||
},
|
||||
..zeta_options.context
|
||||
let mut context_options = DEFAULT_CONTEXT_OPTIONS.clone();
|
||||
context_options.excerpt = EditPredictionExcerptOptions {
|
||||
max_bytes: number_input_value(&this.max_excerpt_bytes_input, cx),
|
||||
min_bytes: number_input_value(&this.min_excerpt_bytes_input, cx),
|
||||
target_before_cursor_over_total_bytes: number_input_value(
|
||||
&this.cursor_context_ratio_input,
|
||||
cx,
|
||||
),
|
||||
};
|
||||
|
||||
let zeta_options = this.zeta.read(cx).options();
|
||||
this.set_options(
|
||||
ZetaOptions {
|
||||
context: context_options,
|
||||
|
||||
@@ -94,8 +94,6 @@ struct Zeta2Args {
|
||||
file_indexing_parallelism: usize,
|
||||
#[arg(long, default_value_t = false)]
|
||||
disable_imports_gathering: bool,
|
||||
#[arg(long, default_value_t = false)]
|
||||
disable_reference_retrieval: bool,
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Default, Debug, Clone)]
|
||||
@@ -113,7 +111,7 @@ impl Into<predict_edits_v3::PromptFormat> for PromptFormat {
|
||||
Self::MarkedExcerpt => predict_edits_v3::PromptFormat::MarkedExcerpt,
|
||||
Self::LabeledSections => predict_edits_v3::PromptFormat::LabeledSections,
|
||||
Self::OnlySnippets => predict_edits_v3::PromptFormat::OnlySnippets,
|
||||
Self::NumberedLines => predict_edits_v3::PromptFormat::NumLinesUniDiff,
|
||||
Self::NumberedLines => predict_edits_v3::PromptFormat::NumberedLines,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,7 +300,6 @@ impl Zeta2Args {
|
||||
fn to_options(&self, omit_excerpt_overlaps: bool) -> zeta2::ZetaOptions {
|
||||
zeta2::ZetaOptions {
|
||||
context: EditPredictionContextOptions {
|
||||
use_references: !self.disable_reference_retrieval,
|
||||
use_imports: !self.disable_imports_gathering,
|
||||
excerpt: EditPredictionExcerptOptions {
|
||||
max_bytes: self.max_excerpt_bytes,
|
||||
|
||||
@@ -63,7 +63,7 @@ For more information, see the [Gemini CLI docs](https://github.com/google-gemini
|
||||
Similar to Zed's first-party agent, you can use Gemini CLI to do anything that you need.
|
||||
And to give it context, you can @-mention files, recent threads, symbols, or fetch the web.
|
||||
|
||||
> Note that some first-party agent features don't yet work with Gemini CLI: editing past messages, resuming threads from history, and checkpointing.
|
||||
> Note that some first-party agent features don't yet work with Gemini CLI: editing past messages, resuming threads from history, checkpointing, and using the agent in SSH projects.
|
||||
> We hope to add these features in the near future.
|
||||
|
||||
## Claude Code
|
||||
@@ -113,27 +113,6 @@ If you want to override the executable used by the adapter, you can set the `CLA
|
||||
}
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
Similar to Zed's first-party agent, you can use Claude Code to do anything that you need.
|
||||
And to give it context, you can @-mention files, recent threads, symbols, or fetch the web.
|
||||
|
||||
In complement to talking to it [over ACP](https://agentclientprotocol.com), Zed relies on the [Claude Code SDK](https://docs.anthropic.com/en/docs/claude-code/sdk/sdk-overview) to support some of its specific features.
|
||||
However, the SDK doesn't yet expose everything needed to fully support all of them:
|
||||
|
||||
- Slash Commands: A subset of [built-in commands](https://docs.anthropic.com/en/docs/claude-code/slash-commands#built-in-slash-commands) are supported, while [custom slash commands](https://docs.anthropic.com/en/docs/claude-code/slash-commands#custom-slash-commands) are fully supported.
|
||||
- [Subagents](https://docs.anthropic.com/en/docs/claude-code/sub-agents) are supported.
|
||||
- [Hooks](https://docs.anthropic.com/en/docs/claude-code/hooks-guide) are currently _not_ supported.
|
||||
|
||||
> Also note that some [first-party agent](./agent-panel.md) features don't yet work with Claude Code: editing past messages, resuming threads from history, and checkpointing.
|
||||
> We hope to add these features in the near future.
|
||||
|
||||
#### CLAUDE.md
|
||||
|
||||
Claude Code in Zed will automatically use any `CLAUDE.md` file found in your project root, project subdirectories, or root `.claude` directory.
|
||||
|
||||
If you don't have a `CLAUDE.md` file, you can ask Claude Code to create one for you through the `init` slash command.
|
||||
|
||||
## Codex CLI
|
||||
|
||||
You can also run [Codex CLI](https://github.com/openai/codex) directly via Zed's [agent panel](./agent-panel.md).
|
||||
@@ -175,12 +154,25 @@ Zed will always use this managed version of Codex even if you have it installed
|
||||
|
||||
### Usage
|
||||
|
||||
Similar to Zed's first-party agent, you can use Codex to do anything that you need.
|
||||
And to give it context, you can @-mention files, symbols, or fetch the web.
|
||||
Similar to Zed's first-party agent, you can use Claude Code to do anything that you need.
|
||||
And to give it context, you can @-mention files, recent threads, symbols, or fetch the web.
|
||||
|
||||
> Note that some first-party agent features don't yet work with Codex: editing past messages, resuming threads from history, and checkpointing.
|
||||
In complement to talking to it [over ACP](https://agentclientprotocol.com), Zed relies on the [Claude Code SDK](https://docs.anthropic.com/en/docs/claude-code/sdk/sdk-overview) to support some of its specific features.
|
||||
However, the SDK doesn't yet expose everything needed to fully support all of them:
|
||||
|
||||
- Slash Commands: A subset of [built-in commands](https://docs.anthropic.com/en/docs/claude-code/slash-commands#built-in-slash-commands) are supported, while [custom slash commands](https://docs.anthropic.com/en/docs/claude-code/slash-commands#custom-slash-commands) are fully supported.
|
||||
- [Subagents](https://docs.anthropic.com/en/docs/claude-code/sub-agents) are supported.
|
||||
- [Hooks](https://docs.anthropic.com/en/docs/claude-code/hooks-guide) are currently _not_ supported.
|
||||
|
||||
> Also note that some [first-party agent](./agent-panel.md) features don't yet work with Claude Code: editing past messages, resuming threads from history, checkpointing, and using the agent in SSH projects.
|
||||
> We hope to add these features in the near future.
|
||||
|
||||
#### CLAUDE.md
|
||||
|
||||
Claude Code in Zed will automatically use any `CLAUDE.md` file found in your project root, project subdirectories, or root `.claude` directory.
|
||||
|
||||
If you don't have a `CLAUDE.md` file, you can ask Claude Code to create one for you through the `init` slash command.
|
||||
|
||||
## Add Custom Agents {#add-custom-agents}
|
||||
|
||||
You can run any agent speaking ACP in Zed by changing your settings as follows:
|
||||
|
||||
@@ -4155,8 +4155,7 @@ Run the {#action theme_selector::Toggle} action in the command palette to see a
|
||||
},
|
||||
"hide_root": false,
|
||||
"hide_hidden": false,
|
||||
"starts_open": true,
|
||||
"open_file_on_paste": true
|
||||
"starts_open": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user