Compare commits

..

1 Commits

Author SHA1 Message Date
Peter Tripp
f855eb7470 ci: Switch Linux arm from buildjet to github 2025-06-07 15:01:30 -04:00
30 changed files with 381 additions and 816 deletions

View File

@@ -183,9 +183,6 @@ jobs:
- name: Check for todo! and FIXME comments - name: Check for todo! and FIXME comments
run: script/check-todos run: script/check-todos
- name: Check modifier use in keymaps
run: script/check-keymaps
- name: Run style checks - name: Run style checks
uses: ./.github/actions/check_style uses: ./.github/actions/check_style
@@ -686,7 +683,7 @@ jobs:
timeout-minutes: 60 timeout-minutes: 60
name: Linux arm64 release bundle name: Linux arm64 release bundle
runs-on: runs-on:
- buildjet-16vcpu-ubuntu-2204-arm - hosted-linux-arm-1
if: | if: |
startsWith(github.ref, 'refs/tags/v') startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') || contains(github.event.pull_request.labels.*.name, 'run-bundling')

View File

@@ -35,7 +35,7 @@
"ctrl-shift-f5": "debugger::Restart", "ctrl-shift-f5": "debugger::Restart",
"f6": "debugger::Pause", "f6": "debugger::Pause",
"f7": "debugger::StepOver", "f7": "debugger::StepOver",
"ctrl-f11": "debugger::StepInto", "cmd-f11": "debugger::StepInto",
"shift-f11": "debugger::StepOut", "shift-f11": "debugger::StepOut",
"f11": "zed::ToggleFullScreen", "f11": "zed::ToggleFullScreen",
"ctrl-alt-z": "edit_prediction::RateCompletions", "ctrl-alt-z": "edit_prediction::RateCompletions",
@@ -59,6 +59,7 @@
"tab": "editor::Tab", "tab": "editor::Tab",
"shift-tab": "editor::Backtab", "shift-tab": "editor::Backtab",
"ctrl-k": "editor::CutToEndOfLine", "ctrl-k": "editor::CutToEndOfLine",
// "ctrl-t": "editor::Transpose",
"ctrl-k ctrl-q": "editor::Rewrap", "ctrl-k ctrl-q": "editor::Rewrap",
"ctrl-k q": "editor::Rewrap", "ctrl-k q": "editor::Rewrap",
"ctrl-backspace": "editor::DeleteToPreviousWordStart", "ctrl-backspace": "editor::DeleteToPreviousWordStart",
@@ -99,16 +100,21 @@
"shift-down": "editor::SelectDown", "shift-down": "editor::SelectDown",
"shift-left": "editor::SelectLeft", "shift-left": "editor::SelectLeft",
"shift-right": "editor::SelectRight", "shift-right": "editor::SelectRight",
"ctrl-shift-left": "editor::SelectToPreviousWordStart", "ctrl-shift-left": "editor::SelectToPreviousWordStart", // cursorWordLeftSelect
"ctrl-shift-right": "editor::SelectToNextWordEnd", "ctrl-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
"ctrl-shift-home": "editor::SelectToBeginning", "ctrl-shift-home": "editor::SelectToBeginning",
"ctrl-shift-end": "editor::SelectToEnd", "ctrl-shift-end": "editor::SelectToEnd",
"ctrl-a": "editor::SelectAll", "ctrl-a": "editor::SelectAll",
"ctrl-l": "editor::SelectLine", "ctrl-l": "editor::SelectLine",
"ctrl-shift-i": "editor::Format", "ctrl-shift-i": "editor::Format",
"alt-shift-o": "editor::OrganizeImports", "alt-shift-o": "editor::OrganizeImports",
// "cmd-shift-left": ["editor::SelectToBeginningOfLine", {"stop_at_soft_wraps": true, "stop_at_indent": true }],
// "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
// "cmd-shift-right": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
// "ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }], "shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
// "alt-v": ["editor::MovePageUp", { "center_cursor": true }],
"ctrl-alt-space": "editor::ShowCharacterPalette", "ctrl-alt-space": "editor::ShowCharacterPalette",
"ctrl-;": "editor::ToggleLineNumbers", "ctrl-;": "editor::ToggleLineNumbers",
"ctrl-'": "editor::ToggleSelectedDiffHunks", "ctrl-'": "editor::ToggleSelectedDiffHunks",
@@ -134,6 +140,7 @@
"find": "buffer_search::Deploy", "find": "buffer_search::Deploy",
"ctrl-f": "buffer_search::Deploy", "ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace", "ctrl-h": "buffer_search::DeployReplace",
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
"ctrl->": "assistant::QuoteSelection", "ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor", "ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol", "ctrl-alt-e": "editor::SelectEnclosingSymbol",
@@ -260,8 +267,8 @@
{ {
"context": "AgentPanel && prompt_editor", "context": "AgentPanel && prompt_editor",
"bindings": { "bindings": {
"ctrl-n": "agent::NewTextThread", "cmd-n": "agent::NewTextThread",
"ctrl-alt-t": "agent::NewThread" "cmd-alt-t": "agent::NewThread"
} }
}, },
{ {

View File

@@ -38,7 +38,7 @@
"ctrl-shift-d": "editor::DuplicateSelection", "ctrl-shift-d": "editor::DuplicateSelection",
"alt-f3": "editor::SelectAllMatches", // find_all_under "alt-f3": "editor::SelectAllMatches", // find_all_under
// "ctrl-f3": "", // find_under (cancels any selections) // "ctrl-f3": "", // find_under (cancels any selections)
// "ctrl-alt-shift-g": "" // find_under_prev (cancels any selections) // "cmd-alt-shift-g": "" // find_under_prev (cancels any selections)
"f9": "editor::SortLinesCaseSensitive", "f9": "editor::SortLinesCaseSensitive",
"ctrl-f9": "editor::SortLinesCaseInsensitive", "ctrl-f9": "editor::SortLinesCaseInsensitive",
"f12": "editor::GoToDefinition", "f12": "editor::GoToDefinition",

View File

@@ -28,8 +28,7 @@
"context": "InlineAssistEditor", "context": "InlineAssistEditor",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"cmd-shift-backspace": "editor::Cancel", "cmd-shift-backspace": "editor::Cancel"
"cmd-enter": "menu::Confirm"
// "alt-enter": // Quick Question // "alt-enter": // Quick Question
// "cmd-shift-enter": // Full File Context // "cmd-shift-enter": // Full File Context
// "cmd-shift-k": // Toggle input focus (editor <> inline assist) // "cmd-shift-k": // Toggle input focus (editor <> inline assist)

View File

@@ -101,12 +101,9 @@
// The second option is decimal. // The second option is decimal.
"unit": "binary" "unit": "binary"
}, },
// Determines the modifier to be used to add multiple cursors with the mouse. The open hover link mouse gestures will adapt such that it do not conflict with the multicursor modifier. // The key to use for adding multiple cursors
// // Currently "alt" or "cmd_or_ctrl" (also aliased as
// 1. Maps to `Alt` on Linux and Windows and to `Option` on MacOS: // "cmd" and "ctrl") are supported.
// "alt"
// 2. Maps `Control` on Linux and Windows and to `Command` on MacOS:
// "cmd_or_ctrl" (alias: "cmd", "ctrl")
"multi_cursor_modifier": "alt", "multi_cursor_modifier": "alt",
// Whether to enable vim modes and key bindings. // Whether to enable vim modes and key bindings.
"vim_mode": false, "vim_mode": false,
@@ -217,8 +214,6 @@
"show_signature_help_after_edits": false, "show_signature_help_after_edits": false,
// Whether to show code action button at start of buffer line. // Whether to show code action button at start of buffer line.
"inline_code_actions": true, "inline_code_actions": true,
// Whether to allow drag and drop text selection in buffer.
"drag_and_drop_selection": true,
// What to do when go to definition yields no results. // What to do when go to definition yields no results.
// //
// 1. Do nothing: `none` // 1. Do nothing: `none`
@@ -604,9 +599,7 @@
// 2. Never show indent guides: // 2. Never show indent guides:
// "never" // "never"
"show": "always" "show": "always"
}, }
/// Hide main root dir
"hide_root": false
}, },
"outline_panel": { "outline_panel": {
// Whether to show the outline panel button in the status bar // Whether to show the outline panel button in the status bar

View File

@@ -57,7 +57,7 @@ use zed_llm_client::{CompletionIntent, UsageLimit};
use crate::active_thread::{self, ActiveThread, ActiveThreadEvent}; use crate::active_thread::{self, ActiveThread, ActiveThreadEvent};
use crate::agent_configuration::{AgentConfiguration, AssistantConfigurationEvent}; use crate::agent_configuration::{AgentConfiguration, AssistantConfigurationEvent};
use crate::agent_diff::AgentDiff; use crate::agent_diff::AgentDiff;
use crate::history_store::{HistoryEntryId, HistoryStore}; use crate::history_store::{HistoryStore, RecentEntry};
use crate::message_editor::{MessageEditor, MessageEditorEvent}; use crate::message_editor::{MessageEditor, MessageEditorEvent};
use crate::thread::{Thread, ThreadError, ThreadId, ThreadSummary, TokenUsageRatio}; use crate::thread::{Thread, ThreadError, ThreadId, ThreadSummary, TokenUsageRatio};
use crate::thread_history::{HistoryEntryElement, ThreadHistory}; use crate::thread_history::{HistoryEntryElement, ThreadHistory};
@@ -257,7 +257,6 @@ impl ActiveView {
pub fn prompt_editor( pub fn prompt_editor(
context_editor: Entity<ContextEditor>, context_editor: Entity<ContextEditor>,
history_store: Entity<HistoryStore>,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
@@ -323,19 +322,6 @@ impl ActiveView {
editor.set_text(summary, window, cx); editor.set_text(summary, window, cx);
}) })
} }
ContextEvent::PathChanged { old_path, new_path } => {
history_store.update(cx, |history_store, cx| {
if let Some(old_path) = old_path {
history_store
.replace_recently_opened_text_thread(old_path, new_path, cx);
} else {
history_store.push_recently_opened_entry(
HistoryEntryId::Context(new_path.clone()),
cx,
);
}
});
}
_ => {} _ => {}
} }
}), }),
@@ -530,7 +516,8 @@ impl AgentPanel {
HistoryStore::new( HistoryStore::new(
thread_store.clone(), thread_store.clone(),
context_store.clone(), context_store.clone(),
[HistoryEntryId::Thread(thread_id)], [RecentEntry::Thread(thread_id, thread.clone())],
window,
cx, cx,
) )
}); });
@@ -557,13 +544,7 @@ impl AgentPanel {
editor.insert_default_prompt(window, cx); editor.insert_default_prompt(window, cx);
editor editor
}); });
ActiveView::prompt_editor( ActiveView::prompt_editor(context_editor, language_registry.clone(), window, cx)
context_editor,
history_store.clone(),
language_registry.clone(),
window,
cx,
)
} }
}; };
@@ -600,9 +581,86 @@ impl AgentPanel {
let panel = weak_panel.clone(); let panel = weak_panel.clone();
let assistant_navigation_menu = let assistant_navigation_menu =
ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| { ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
if let Some(panel) = panel.upgrade() { let recently_opened = panel
menu = Self::populate_recently_opened_menu_section(menu, panel, cx); .update(cx, |this, cx| {
this.history_store.update(cx, |history_store, cx| {
history_store.recently_opened_entries(cx)
})
})
.unwrap_or_default();
if !recently_opened.is_empty() {
menu = menu.header("Recently Opened");
for entry in recently_opened.iter() {
if let RecentEntry::Context(context) = entry {
if context.read(cx).path().is_none() {
log::error!(
"bug: text thread in recent history list was never saved"
);
continue;
}
}
let summary = entry.summary(cx);
menu = menu.entry_with_end_slot_on_hover(
summary,
None,
{
let panel = panel.clone();
let entry = entry.clone();
move |window, cx| {
panel
.update(cx, {
let entry = entry.clone();
move |this, cx| match entry {
RecentEntry::Thread(_, thread) => {
this.open_thread(thread, window, cx)
}
RecentEntry::Context(context) => {
let Some(path) = context.read(cx).path()
else {
return;
};
this.open_saved_prompt_editor(
path.clone(),
window,
cx,
)
.detach_and_log_err(cx)
}
}
})
.ok();
}
},
IconName::Close,
"Close Entry".into(),
{
let panel = panel.clone();
let entry = entry.clone();
move |_window, cx| {
panel
.update(cx, |this, cx| {
this.history_store.update(
cx,
|history_store, cx| {
history_store.remove_recently_opened_entry(
&entry, cx,
);
},
);
})
.ok();
}
},
);
}
menu = menu.separator();
} }
menu.action("View All", Box::new(OpenHistory)) menu.action("View All", Box::new(OpenHistory))
.end_slot_action(DeleteRecentlyOpenThread.boxed_clone()) .end_slot_action(DeleteRecentlyOpenThread.boxed_clone())
.fixed_width(px(320.).into()) .fixed_width(px(320.).into())
@@ -840,7 +898,6 @@ impl AgentPanel {
self.set_active_view( self.set_active_view(
ActiveView::prompt_editor( ActiveView::prompt_editor(
context_editor.clone(), context_editor.clone(),
self.history_store.clone(),
self.language_registry.clone(), self.language_registry.clone(),
window, window,
cx, cx,
@@ -927,13 +984,7 @@ impl AgentPanel {
) )
}); });
self.set_active_view( self.set_active_view(
ActiveView::prompt_editor( ActiveView::prompt_editor(editor.clone(), self.language_registry.clone(), window, cx),
editor.clone(),
self.history_store.clone(),
self.language_registry.clone(),
window,
cx,
),
window, window,
cx, cx,
); );
@@ -1332,6 +1383,16 @@ impl AgentPanel {
} }
} }
} }
ActiveView::TextThread { context_editor, .. } => {
let context = context_editor.read(cx).context();
// When switching away from an unsaved text thread, delete its entry.
if context.read(cx).path().is_none() {
let context = context.clone();
self.history_store.update(cx, |store, cx| {
store.remove_recently_opened_entry(&RecentEntry::Context(context), cx);
});
}
}
_ => {} _ => {}
} }
@@ -1339,14 +1400,13 @@ impl AgentPanel {
ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| { ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
if let Some(thread) = thread.upgrade() { if let Some(thread) = thread.upgrade() {
let id = thread.read(cx).id().clone(); let id = thread.read(cx).id().clone();
store.push_recently_opened_entry(HistoryEntryId::Thread(id), cx); store.push_recently_opened_entry(RecentEntry::Thread(id, thread), cx);
} }
}), }),
ActiveView::TextThread { context_editor, .. } => { ActiveView::TextThread { context_editor, .. } => {
self.history_store.update(cx, |store, cx| { self.history_store.update(cx, |store, cx| {
if let Some(path) = context_editor.read(cx).context().read(cx).path() { let context = context_editor.read(cx).context().clone();
store.push_recently_opened_entry(HistoryEntryId::Context(path.clone()), cx) store.push_recently_opened_entry(RecentEntry::Context(context), cx)
}
}) })
} }
_ => {} _ => {}
@@ -1365,70 +1425,6 @@ impl AgentPanel {
self.focus_handle(cx).focus(window); self.focus_handle(cx).focus(window);
} }
fn populate_recently_opened_menu_section(
mut menu: ContextMenu,
panel: Entity<Self>,
cx: &mut Context<ContextMenu>,
) -> ContextMenu {
let entries = panel
.read(cx)
.history_store
.read(cx)
.recently_opened_entries(cx);
if entries.is_empty() {
return menu;
}
menu = menu.header("Recently Opened");
for entry in entries {
let title = entry.title().clone();
let id = entry.id();
menu = menu.entry_with_end_slot_on_hover(
title,
None,
{
let panel = panel.downgrade();
let id = id.clone();
move |window, cx| {
let id = id.clone();
panel
.update(cx, move |this, cx| match id {
HistoryEntryId::Thread(id) => this
.open_thread_by_id(&id, window, cx)
.detach_and_log_err(cx),
HistoryEntryId::Context(path) => this
.open_saved_prompt_editor(path.clone(), window, cx)
.detach_and_log_err(cx),
})
.ok();
}
},
IconName::Close,
"Close Entry".into(),
{
let panel = panel.downgrade();
let id = id.clone();
move |_window, cx| {
panel
.update(cx, |this, cx| {
this.history_store.update(cx, |history_store, cx| {
history_store.remove_recently_opened_entry(&id, cx);
});
})
.ok();
}
},
);
}
menu = menu.separator();
menu
}
} }
impl Focusable for AgentPanel { impl Focusable for AgentPanel {

View File

@@ -282,18 +282,15 @@ pub fn unordered_thread_entries(
text_thread_store: Entity<TextThreadStore>, text_thread_store: Entity<TextThreadStore>,
cx: &App, cx: &App,
) -> impl Iterator<Item = (DateTime<Utc>, ThreadContextEntry)> { ) -> impl Iterator<Item = (DateTime<Utc>, ThreadContextEntry)> {
let threads = thread_store let threads = thread_store.read(cx).unordered_threads().map(|thread| {
.read(cx) (
.reverse_chronological_threads() thread.updated_at,
.map(|thread| { ThreadContextEntry::Thread {
( id: thread.id.clone(),
thread.updated_at, title: thread.summary.clone(),
ThreadContextEntry::Thread { },
id: thread.id.clone(), )
title: thread.summary.clone(), });
},
)
});
let text_threads = text_thread_store let text_threads = text_thread_store
.read(cx) .read(cx)
@@ -303,7 +300,7 @@ pub fn unordered_thread_entries(
context.mtime.to_utc(), context.mtime.to_utc(),
ThreadContextEntry::Context { ThreadContextEntry::Context {
path: context.path.clone(), path: context.path.clone(),
title: context.title.clone(), title: context.title.clone().into(),
}, },
) )
}); });

View File

@@ -1,17 +1,18 @@
use std::{collections::VecDeque, path::Path, sync::Arc}; use std::{collections::VecDeque, path::Path, sync::Arc};
use anyhow::{Context as _, Result}; use anyhow::Context as _;
use assistant_context_editor::SavedContextMetadata; use assistant_context_editor::{AssistantContext, SavedContextMetadata};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use gpui::{AsyncApp, Entity, SharedString, Task, prelude::*}; use futures::future::{TryFutureExt as _, join_all};
use itertools::Itertools; use gpui::{Entity, Task, prelude::*};
use paths::contexts_dir;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smol::future::FutureExt;
use std::time::Duration; use std::time::Duration;
use ui::App; use ui::{App, SharedString, Window};
use util::ResultExt as _; use util::ResultExt as _;
use crate::{ use crate::{
Thread,
thread::ThreadId, thread::ThreadId,
thread_store::{SerializedThreadMetadata, ThreadStore}, thread_store::{SerializedThreadMetadata, ThreadStore},
}; };
@@ -40,34 +41,52 @@ impl HistoryEntry {
HistoryEntry::Context(context) => HistoryEntryId::Context(context.path.clone()), HistoryEntry::Context(context) => HistoryEntryId::Context(context.path.clone()),
} }
} }
pub fn title(&self) -> &SharedString {
match self {
HistoryEntry::Thread(thread) => &thread.summary,
HistoryEntry::Context(context) => &context.title,
}
}
} }
/// Generic identifier for a history entry. /// Generic identifier for a history entry.
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq)]
pub enum HistoryEntryId { pub enum HistoryEntryId {
Thread(ThreadId), Thread(ThreadId),
Context(Arc<Path>), Context(Arc<Path>),
} }
#[derive(Clone, Debug)]
pub(crate) enum RecentEntry {
Thread(ThreadId, Entity<Thread>),
Context(Entity<AssistantContext>),
}
impl PartialEq for RecentEntry {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Thread(l0, _), Self::Thread(r0, _)) => l0 == r0,
(Self::Context(l0), Self::Context(r0)) => l0 == r0,
_ => false,
}
}
}
impl Eq for RecentEntry {}
impl RecentEntry {
pub(crate) fn summary(&self, cx: &App) -> SharedString {
match self {
RecentEntry::Thread(_, thread) => thread.read(cx).summary().or_default(),
RecentEntry::Context(context) => context.read(cx).summary().or_default(),
}
}
}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
enum SerializedRecentOpen { enum SerializedRecentEntry {
Thread(String), Thread(String),
ContextName(String),
/// Old format which stores the full path
Context(String), Context(String),
} }
pub struct HistoryStore { pub struct HistoryStore {
thread_store: Entity<ThreadStore>, thread_store: Entity<ThreadStore>,
context_store: Entity<assistant_context_editor::ContextStore>, context_store: Entity<assistant_context_editor::ContextStore>,
recently_opened_entries: VecDeque<HistoryEntryId>, recently_opened_entries: VecDeque<RecentEntry>,
_subscriptions: Vec<gpui::Subscription>, _subscriptions: Vec<gpui::Subscription>,
_save_recently_opened_entries_task: Task<()>, _save_recently_opened_entries_task: Task<()>,
} }
@@ -76,7 +95,8 @@ impl HistoryStore {
pub fn new( pub fn new(
thread_store: Entity<ThreadStore>, thread_store: Entity<ThreadStore>,
context_store: Entity<assistant_context_editor::ContextStore>, context_store: Entity<assistant_context_editor::ContextStore>,
initial_recent_entries: impl IntoIterator<Item = HistoryEntryId>, initial_recent_entries: impl IntoIterator<Item = RecentEntry>,
window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
let subscriptions = vec![ let subscriptions = vec![
@@ -84,20 +104,68 @@ impl HistoryStore {
cx.observe(&context_store, |_, _, cx| cx.notify()), cx.observe(&context_store, |_, _, cx| cx.notify()),
]; ];
cx.spawn(async move |this, cx| { window
let entries = Self::load_recently_opened_entries(cx).await.log_err()?; .spawn(cx, {
this.update(cx, |this, _| { let thread_store = thread_store.downgrade();
this.recently_opened_entries let context_store = context_store.downgrade();
.extend( let this = cx.weak_entity();
entries.into_iter().take( async move |cx| {
MAX_RECENTLY_OPENED_ENTRIES let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
.saturating_sub(this.recently_opened_entries.len()), let contents = cx
), .background_spawn(async move { std::fs::read_to_string(path) })
); .await
.ok()?;
let entries = serde_json::from_str::<Vec<SerializedRecentEntry>>(&contents)
.context("deserializing persisted agent panel navigation history")
.log_err()?
.into_iter()
.take(MAX_RECENTLY_OPENED_ENTRIES)
.map(|serialized| match serialized {
SerializedRecentEntry::Thread(id) => thread_store
.update_in(cx, |thread_store, window, cx| {
let thread_id = ThreadId::from(id.as_str());
thread_store
.open_thread(&thread_id, window, cx)
.map_ok(|thread| RecentEntry::Thread(thread_id, thread))
.boxed()
})
.unwrap_or_else(|_| {
async {
anyhow::bail!("no thread store");
}
.boxed()
}),
SerializedRecentEntry::Context(id) => context_store
.update(cx, |context_store, cx| {
context_store
.open_local_context(Path::new(&id).into(), cx)
.map_ok(RecentEntry::Context)
.boxed()
})
.unwrap_or_else(|_| {
async {
anyhow::bail!("no context store");
}
.boxed()
}),
});
let entries = join_all(entries)
.await
.into_iter()
.filter_map(|result| result.log_with_level(log::Level::Debug))
.collect::<VecDeque<_>>();
this.update(cx, |this, _| {
this.recently_opened_entries.extend(entries);
this.recently_opened_entries
.truncate(MAX_RECENTLY_OPENED_ENTRIES);
})
.ok();
Some(())
}
}) })
.ok() .detach();
})
.detach();
Self { Self {
thread_store, thread_store,
@@ -116,20 +184,19 @@ impl HistoryStore {
return history_entries; return history_entries;
} }
history_entries.extend( for thread in self
self.thread_store .thread_store
.read(cx) .update(cx, |this, _cx| this.reverse_chronological_threads())
.reverse_chronological_threads() {
.cloned() history_entries.push(HistoryEntry::Thread(thread));
.map(HistoryEntry::Thread), }
);
history_entries.extend( for context in self
self.context_store .context_store
.read(cx) .update(cx, |this, _cx| this.reverse_chronological_contexts())
.unordered_contexts() {
.cloned() history_entries.push(HistoryEntry::Context(context));
.map(HistoryEntry::Context), }
);
history_entries.sort_unstable_by_key(|entry| std::cmp::Reverse(entry.updated_at())); history_entries.sort_unstable_by_key(|entry| std::cmp::Reverse(entry.updated_at()));
history_entries history_entries
@@ -139,62 +206,15 @@ impl HistoryStore {
self.entries(cx).into_iter().take(limit).collect() self.entries(cx).into_iter().take(limit).collect()
} }
pub fn recently_opened_entries(&self, cx: &App) -> Vec<HistoryEntry> {
#[cfg(debug_assertions)]
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
return Vec::new();
}
let thread_entries = self
.thread_store
.read(cx)
.reverse_chronological_threads()
.flat_map(|thread| {
self.recently_opened_entries
.iter()
.enumerate()
.flat_map(|(index, entry)| match entry {
HistoryEntryId::Thread(id) if &thread.id == id => {
Some((index, HistoryEntry::Thread(thread.clone())))
}
_ => None,
})
});
let context_entries =
self.context_store
.read(cx)
.unordered_contexts()
.flat_map(|context| {
self.recently_opened_entries
.iter()
.enumerate()
.flat_map(|(index, entry)| match entry {
HistoryEntryId::Context(path) if &context.path == path => {
Some((index, HistoryEntry::Context(context.clone())))
}
_ => None,
})
});
thread_entries
.chain(context_entries)
// optimization to halt iteration early
.take(self.recently_opened_entries.len())
.sorted_unstable_by_key(|(index, _)| *index)
.map(|(_, entry)| entry)
.collect()
}
fn save_recently_opened_entries(&mut self, cx: &mut Context<Self>) { fn save_recently_opened_entries(&mut self, cx: &mut Context<Self>) {
let serialized_entries = self let serialized_entries = self
.recently_opened_entries .recently_opened_entries
.iter() .iter()
.filter_map(|entry| match entry { .filter_map(|entry| match entry {
HistoryEntryId::Context(path) => path.file_name().map(|file| { RecentEntry::Context(context) => Some(SerializedRecentEntry::Context(
SerializedRecentOpen::ContextName(file.to_string_lossy().to_string()) context.read(cx).path()?.to_str()?.to_owned(),
}), )),
HistoryEntryId::Thread(id) => Some(SerializedRecentOpen::Thread(id.to_string())), RecentEntry::Thread(id, _) => Some(SerializedRecentEntry::Thread(id.to_string())),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@@ -213,33 +233,7 @@ impl HistoryStore {
}); });
} }
fn load_recently_opened_entries(cx: &AsyncApp) -> Task<Result<Vec<HistoryEntryId>>> { pub fn push_recently_opened_entry(&mut self, entry: RecentEntry, cx: &mut Context<Self>) {
cx.background_spawn(async move {
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
let contents = smol::fs::read_to_string(path).await?;
let entries = serde_json::from_str::<Vec<SerializedRecentOpen>>(&contents)
.context("deserializing persisted agent panel navigation history")?
.into_iter()
.take(MAX_RECENTLY_OPENED_ENTRIES)
.flat_map(|entry| match entry {
SerializedRecentOpen::Thread(id) => {
Some(HistoryEntryId::Thread(id.as_str().into()))
}
SerializedRecentOpen::ContextName(file_name) => Some(HistoryEntryId::Context(
contexts_dir().join(file_name).into(),
)),
SerializedRecentOpen::Context(path) => {
Path::new(&path).file_name().map(|file_name| {
HistoryEntryId::Context(contexts_dir().join(file_name).into())
})
}
})
.collect::<Vec<_>>();
Ok(entries)
})
}
pub fn push_recently_opened_entry(&mut self, entry: HistoryEntryId, cx: &mut Context<Self>) {
self.recently_opened_entries self.recently_opened_entries
.retain(|old_entry| old_entry != &entry); .retain(|old_entry| old_entry != &entry);
self.recently_opened_entries.push_front(entry); self.recently_opened_entries.push_front(entry);
@@ -250,33 +244,24 @@ impl HistoryStore {
pub fn remove_recently_opened_thread(&mut self, id: ThreadId, cx: &mut Context<Self>) { pub fn remove_recently_opened_thread(&mut self, id: ThreadId, cx: &mut Context<Self>) {
self.recently_opened_entries.retain(|entry| match entry { self.recently_opened_entries.retain(|entry| match entry {
HistoryEntryId::Thread(thread_id) if thread_id == &id => false, RecentEntry::Thread(thread_id, _) if thread_id == &id => false,
_ => true, _ => true,
}); });
self.save_recently_opened_entries(cx); self.save_recently_opened_entries(cx);
} }
pub fn replace_recently_opened_text_thread( pub fn remove_recently_opened_entry(&mut self, entry: &RecentEntry, cx: &mut Context<Self>) {
&mut self,
old_path: &Path,
new_path: &Arc<Path>,
cx: &mut Context<Self>,
) {
for entry in &mut self.recently_opened_entries {
match entry {
HistoryEntryId::Context(path) if path.as_ref() == old_path => {
*entry = HistoryEntryId::Context(new_path.clone());
break;
}
_ => {}
}
}
self.save_recently_opened_entries(cx);
}
pub fn remove_recently_opened_entry(&mut self, entry: &HistoryEntryId, cx: &mut Context<Self>) {
self.recently_opened_entries self.recently_opened_entries
.retain(|old_entry| old_entry != entry); .retain(|old_entry| old_entry != entry);
self.save_recently_opened_entries(cx); self.save_recently_opened_entries(cx);
} }
pub fn recently_opened_entries(&self, _cx: &mut Context<Self>) -> VecDeque<RecentEntry> {
#[cfg(debug_assertions)]
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
return VecDeque::new();
}
self.recently_opened_entries.clone()
}
} }

View File

@@ -671,7 +671,7 @@ impl RenderOnce for HistoryEntryElement {
), ),
HistoryEntry::Context(context) => ( HistoryEntry::Context(context) => (
context.path.to_string_lossy().to_string(), context.path.to_string_lossy().to_string(),
context.title.clone(), context.title.clone().into(),
context.mtime.timestamp(), context.mtime.timestamp(),
), ),
}; };

View File

@@ -393,11 +393,16 @@ impl ThreadStore {
self.threads.len() self.threads.len()
} }
pub fn reverse_chronological_threads(&self) -> impl Iterator<Item = &SerializedThreadMetadata> { pub fn unordered_threads(&self) -> impl Iterator<Item = &SerializedThreadMetadata> {
// ordering is from "ORDER BY" in `list_threads`
self.threads.iter() self.threads.iter()
} }
pub fn reverse_chronological_threads(&self) -> Vec<SerializedThreadMetadata> {
let mut threads = self.threads.iter().cloned().collect::<Vec<_>>();
threads.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.updated_at));
threads
}
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> { pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
cx.new(|cx| { cx.new(|cx| {
Thread::new( Thread::new(

View File

@@ -11,7 +11,7 @@ use assistant_slash_commands::FileCommandMetadata;
use client::{self, proto, telemetry::Telemetry}; use client::{self, proto, telemetry::Telemetry};
use clock::ReplicaId; use clock::ReplicaId;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use fs::{Fs, RenameOptions}; use fs::{Fs, RemoveOptions};
use futures::{FutureExt, StreamExt, future::Shared}; use futures::{FutureExt, StreamExt, future::Shared};
use gpui::{ use gpui::{
App, AppContext as _, Context, Entity, EventEmitter, RenderImage, SharedString, Subscription, App, AppContext as _, Context, Entity, EventEmitter, RenderImage, SharedString, Subscription,
@@ -452,10 +452,6 @@ pub enum ContextEvent {
MessagesEdited, MessagesEdited,
SummaryChanged, SummaryChanged,
SummaryGenerated, SummaryGenerated,
PathChanged {
old_path: Option<Arc<Path>>,
new_path: Arc<Path>,
},
StreamedCompletion, StreamedCompletion,
StartedThoughtProcess(Range<language::Anchor>), StartedThoughtProcess(Range<language::Anchor>),
EndedThoughtProcess(language::Anchor), EndedThoughtProcess(language::Anchor),
@@ -2898,34 +2894,22 @@ impl AssistantContext {
} }
fs.create_dir(contexts_dir().as_ref()).await?; fs.create_dir(contexts_dir().as_ref()).await?;
fs.atomic_write(new_path.clone(), serde_json::to_string(&context).unwrap())
// rename before write ensures that only one file exists .await?;
if let Some(old_path) = old_path.as_ref() { if let Some(old_path) = old_path {
if new_path.as_path() != old_path.as_ref() { if new_path.as_path() != old_path.as_ref() {
fs.rename( fs.remove_file(
&old_path, &old_path,
&new_path, RemoveOptions {
RenameOptions { recursive: false,
overwrite: true, ignore_if_not_exists: true,
ignore_if_exists: true,
}, },
) )
.await?; .await?;
} }
} }
// update path before write in case it fails this.update(cx, |this, _| this.path = Some(new_path.into()))?;
this.update(cx, {
let new_path: Arc<Path> = new_path.clone().into();
move |this, cx| {
this.path = Some(new_path.clone());
cx.emit(ContextEvent::PathChanged { old_path, new_path });
}
})
.ok();
fs.atomic_write(new_path, serde_json::to_string(&context).unwrap())
.await?;
} }
Ok(()) Ok(())
@@ -3293,7 +3277,7 @@ impl SavedContextV0_1_0 {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SavedContextMetadata { pub struct SavedContextMetadata {
pub title: SharedString, pub title: String,
pub path: Arc<Path>, pub path: Arc<Path>,
pub mtime: chrono::DateTime<chrono::Local>, pub mtime: chrono::DateTime<chrono::Local>,
} }

View File

@@ -580,7 +580,6 @@ impl ContextEditor {
}); });
} }
ContextEvent::SummaryGenerated => {} ContextEvent::SummaryGenerated => {}
ContextEvent::PathChanged { .. } => {}
ContextEvent::StartedThoughtProcess(range) => { ContextEvent::StartedThoughtProcess(range) => {
let creases = self.insert_thought_process_output_sections( let creases = self.insert_thought_process_output_sections(
[( [(

View File

@@ -347,6 +347,12 @@ impl ContextStore {
self.contexts_metadata.iter() self.contexts_metadata.iter()
} }
pub fn reverse_chronological_contexts(&self) -> Vec<SavedContextMetadata> {
let mut contexts = self.contexts_metadata.iter().cloned().collect::<Vec<_>>();
contexts.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.mtime));
contexts
}
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> { pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
let context = cx.new(|cx| { let context = cx.new(|cx| {
AssistantContext::local( AssistantContext::local(
@@ -612,16 +618,6 @@ impl ContextStore {
ContextEvent::SummaryChanged => { ContextEvent::SummaryChanged => {
self.advertise_contexts(cx); self.advertise_contexts(cx);
} }
ContextEvent::PathChanged { old_path, new_path } => {
if let Some(old_path) = old_path.as_ref() {
for metadata in &mut self.contexts_metadata {
if &metadata.path == old_path {
metadata.path = new_path.clone();
break;
}
}
}
}
ContextEvent::Operation(operation) => { ContextEvent::Operation(operation) => {
let context_id = context.read(cx).id().to_proto(); let context_id = context.read(cx).id().to_proto();
let operation = operation.to_proto(); let operation = operation.to_proto();
@@ -796,7 +792,7 @@ impl ContextStore {
.next() .next()
{ {
contexts.push(SavedContextMetadata { contexts.push(SavedContextMetadata {
title: title.to_string().into(), title: title.to_string(),
path: path.into(), path: path.into(),
mtime: metadata.mtime.timestamp_for_user().into(), mtime: metadata.mtime.timestamp_for_user().into(),
}); });

View File

@@ -213,14 +213,11 @@ use workspace::{
searchable::SearchEvent, searchable::SearchEvent,
}; };
use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
use crate::{ use crate::{
code_context_menus::CompletionsMenuSource, code_context_menus::CompletionsMenuSource,
hover_links::{find_url, find_url_from_range}, hover_links::{find_url, find_url_from_range},
}; };
use crate::{
editor_settings::MultiCursorModifier,
signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
};
pub const FILE_HEADER_HEIGHT: u32 = 2; pub const FILE_HEADER_HEIGHT: u32 = 2;
pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1; pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
@@ -256,6 +253,14 @@ pub type RenderDiffHunkControlsFn = Arc<
) -> AnyElement, ) -> AnyElement,
>; >;
const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
alt: true,
shift: true,
control: false,
platform: false,
function: false,
};
struct InlineValueCache { struct InlineValueCache {
enabled: bool, enabled: bool,
inlays: Vec<InlayId>, inlays: Vec<InlayId>,
@@ -906,18 +911,6 @@ struct InlineBlamePopover {
popover_state: InlineBlamePopoverState, popover_state: InlineBlamePopoverState,
} }
enum SelectionDragState {
/// State when no drag related activity is detected.
None,
/// State when the mouse is down on a selection that is about to be dragged.
ReadyToDrag { selection: Selection<Anchor> },
/// State when the mouse is dragging the selection in the editor.
Dragging {
selection: Selection<Anchor>,
drop_cursor: Selection<Anchor>,
},
}
/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have /// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
/// a breakpoint on them. /// a breakpoint on them.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@@ -1103,8 +1096,6 @@ pub struct Editor {
hide_mouse_mode: HideMouseMode, hide_mouse_mode: HideMouseMode,
pub change_list: ChangeList, pub change_list: ChangeList,
inline_value_cache: InlineValueCache, inline_value_cache: InlineValueCache,
selection_drag_state: SelectionDragState,
drag_and_drop_selection_enabled: bool,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@@ -1999,8 +1990,6 @@ impl Editor {
.unwrap_or_default(), .unwrap_or_default(),
change_list: ChangeList::new(), change_list: ChangeList::new(),
mode, mode,
selection_drag_state: SelectionDragState::None,
drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
}; };
if let Some(breakpoints) = editor.breakpoint_store.as_ref() { if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
editor editor
@@ -3546,7 +3535,6 @@ impl Editor {
pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) { pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
self.selection_mark_mode = false; self.selection_mark_mode = false;
self.selection_drag_state = SelectionDragState::None;
if self.clear_expanded_diff_hunks(cx) { if self.clear_expanded_diff_hunks(cx) {
cx.notify(); cx.notify();
@@ -7103,29 +7091,6 @@ impl Editor {
) )
} }
fn multi_cursor_modifier(
cursor_event: bool,
modifiers: &Modifiers,
cx: &mut Context<Self>,
) -> bool {
let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
if cursor_event {
match multi_cursor_setting {
MultiCursorModifier::Alt => modifiers.alt,
MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
}
} else {
match multi_cursor_setting {
MultiCursorModifier::Alt => modifiers.secondary(),
MultiCursorModifier::CmdOrCtrl => modifiers.alt,
}
}
}
fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
}
fn update_selection_mode( fn update_selection_mode(
&mut self, &mut self,
modifiers: &Modifiers, modifiers: &Modifiers,
@@ -7133,10 +7098,7 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx); if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
|| self.selections.pending.is_none()
{
return; return;
} }
@@ -10601,56 +10563,6 @@ impl Editor {
}); });
} }
pub fn drop_selection(
&mut self,
point_for_position: Option<PointForPosition>,
is_cut: bool,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
if let Some(point_for_position) = point_for_position {
match self.selection_drag_state {
SelectionDragState::Dragging { ref selection, .. } => {
let snapshot = self.snapshot(window, cx);
let selection_display =
selection.map(|anchor| anchor.to_display_point(&snapshot));
if !point_for_position.intersects_selection(&selection_display) {
let point = point_for_position.previous_valid;
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
let mut edits = Vec::new();
let insert_point = display_map
.clip_point(point, Bias::Left)
.to_point(&display_map);
let text = buffer
.text_for_range(selection.start..selection.end)
.collect::<String>();
if is_cut {
edits.push(((selection.start..selection.end), String::new()));
}
let insert_anchor = buffer.anchor_before(insert_point);
edits.push(((insert_anchor..insert_anchor), text));
let last_edit_start = insert_anchor.bias_left(buffer);
let last_edit_end = insert_anchor.bias_right(buffer);
self.transact(window, cx, |this, window, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
});
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
s.select_anchor_ranges([last_edit_start..last_edit_end]);
});
});
self.selection_drag_state = SelectionDragState::None;
return true;
}
}
_ => {}
}
}
self.selection_drag_state = SelectionDragState::None;
false
}
pub fn duplicate( pub fn duplicate(
&mut self, &mut self,
upwards: bool, upwards: bool,
@@ -18858,11 +18770,6 @@ impl Editor {
cx.emit(EditorEvent::BufferEdited); cx.emit(EditorEvent::BufferEdited);
cx.emit(SearchEvent::MatchesInvalidated); cx.emit(SearchEvent::MatchesInvalidated);
if *singleton_buffer_edited { if *singleton_buffer_edited {
if let Some(buffer) = multibuffer.read(cx).as_singleton() {
if buffer.read(cx).file().is_none() {
cx.emit(EditorEvent::TitleChanged);
}
}
if let Some(project) = &self.project { if let Some(project) = &self.project {
#[allow(clippy::mutable_key_type)] #[allow(clippy::mutable_key_type)]
let languages_affected = multibuffer.update(cx, |multibuffer, cx| { let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
@@ -19054,7 +18961,6 @@ impl Editor {
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs; self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default(); self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default(); self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
} }
if old_cursor_shape != self.cursor_shape { if old_cursor_shape != self.cursor_shape {

View File

@@ -49,7 +49,6 @@ pub struct EditorSettings {
#[serde(default)] #[serde(default)]
pub diagnostics_max_severity: Option<DiagnosticSeverity>, pub diagnostics_max_severity: Option<DiagnosticSeverity>,
pub inline_code_actions: bool, pub inline_code_actions: bool,
pub drag_and_drop_selection: bool,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -423,7 +422,7 @@ pub struct EditorSettingsContent {
/// Default: always /// Default: always
pub seed_search_query_from_cursor: Option<SeedQuerySetting>, pub seed_search_query_from_cursor: Option<SeedQuerySetting>,
pub use_smartcase_search: Option<bool>, pub use_smartcase_search: Option<bool>,
/// Determines the modifier to be used to add multiple cursors with the mouse. The open hover link mouse gestures will adapt such that it do not conflict with the multicursor modifier. /// The key to use for adding multiple cursors
/// ///
/// Default: alt /// Default: alt
pub multi_cursor_modifier: Option<MultiCursorModifier>, pub multi_cursor_modifier: Option<MultiCursorModifier>,
@@ -496,11 +495,6 @@ pub struct EditorSettingsContent {
/// ///
/// Default: true /// Default: true
pub inline_code_actions: Option<bool>, pub inline_code_actions: Option<bool>,
/// Whether to allow drag and drop text selection in buffer.
///
/// Default: true
pub drag_and_drop_selection: Option<bool>,
} }
// Toolbar related settings // Toolbar related settings

View File

@@ -1,14 +1,14 @@
use crate::{ use crate::{
ActiveDiagnostic, BlockId, CURSORS_VISIBLE_FOR, ChunkRendererContext, ChunkReplacement, ActiveDiagnostic, BlockId, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
CodeActionSource, ConflictsOurs, ConflictsOursMarker, ConflictsOuter, ConflictsTheirs, ChunkRendererContext, ChunkReplacement, CodeActionSource, ConflictsOurs, ConflictsOursMarker,
ConflictsTheirsMarker, ContextMenuPlacement, CursorShape, CustomBlockId, DisplayDiffHunk, ConflictsOuter, ConflictsTheirs, ConflictsTheirsMarker, ContextMenuPlacement, CursorShape,
DisplayPoint, DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, CustomBlockId, DisplayDiffHunk, DisplayPoint, DisplayRow, DocumentHighlightRead,
Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, FILE_HEADER_HEIGHT, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode, EditorSettings, EditorSnapshot,
FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, EditorStyle, FILE_HEADER_HEIGHT, FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp,
InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineHighlight, LineUp, HandleInput, HoveredCursor, InlayHintRefreshReason, InlineCompletion, JumpData, LineDown,
MAX_LINE_LEN, MIN_LINE_NUMBER_DIGITS, MINIMAP_FONT_SIZE, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, LineHighlight, LineUp, MAX_LINE_LEN, MIN_LINE_NUMBER_DIGITS, MINIMAP_FONT_SIZE,
OpenExcerpts, PageDown, PageUp, PhantomBreakpointIndicator, Point, RowExt, RowRangeExt, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts, PageDown, PageUp, PhantomBreakpointIndicator,
SelectPhase, SelectedTextHighlight, Selection, SelectionDragState, SoftWrap, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
StickyHeaderExcerpt, ToPoint, ToggleFold, StickyHeaderExcerpt, ToPoint, ToggleFold,
code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP}, code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
display_map::{ display_map::{
@@ -17,7 +17,8 @@ use crate::{
}, },
editor_settings::{ editor_settings::{
CurrentLineHighlight, DoubleClickInMultibuffer, MinimapThumb, MinimapThumbBorder, CurrentLineHighlight, DoubleClickInMultibuffer, MinimapThumb, MinimapThumbBorder,
ScrollBeyondLastLine, ScrollbarAxes, ScrollbarDiagnostics, ShowMinimap, ShowScrollbar, MultiCursorModifier, ScrollBeyondLastLine, ScrollbarAxes, ScrollbarDiagnostics,
ShowMinimap, ShowScrollbar,
}, },
git::blame::{BlameRenderer, GitBlame, GlobalBlameRenderer}, git::blame::{BlameRenderer, GitBlame, GlobalBlameRenderer},
hover_popover::{ hover_popover::{
@@ -78,11 +79,10 @@ use std::{
time::Duration, time::Duration,
}; };
use sum_tree::Bias; use sum_tree::Bias;
use text::{BufferId, SelectionGoal}; use text::BufferId;
use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor}; use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor};
use ui::{ButtonLike, KeyBinding, POPOVER_Y_PADDING, Tooltip, h_flex, prelude::*}; use ui::{ButtonLike, KeyBinding, POPOVER_Y_PADDING, Tooltip, h_flex, prelude::*};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use util::post_inc;
use util::{RangeExt, ResultExt, debug_panic}; use util::{RangeExt, ResultExt, debug_panic};
use workspace::{CollaboratorId, Workspace, item::Item, notifications::NotifyTaskExt}; use workspace::{CollaboratorId, Workspace, item::Item, notifications::NotifyTaskExt};
@@ -620,7 +620,6 @@ impl EditorElement {
let text_hitbox = &position_map.text_hitbox; let text_hitbox = &position_map.text_hitbox;
let gutter_hitbox = &position_map.gutter_hitbox; let gutter_hitbox = &position_map.gutter_hitbox;
let point_for_position = position_map.point_for_position(event.position);
let mut click_count = event.click_count; let mut click_count = event.click_count;
let mut modifiers = event.modifiers; let mut modifiers = event.modifiers;
@@ -634,19 +633,6 @@ impl EditorElement {
return; return;
} }
if editor.drag_and_drop_selection_enabled && click_count == 1 {
let newest_anchor = editor.selections.newest_anchor();
let snapshot = editor.snapshot(window, cx);
let selection = newest_anchor.map(|anchor| anchor.to_display_point(&snapshot));
if point_for_position.intersects_selection(&selection) {
editor.selection_drag_state = SelectionDragState::ReadyToDrag {
selection: newest_anchor.clone(),
};
cx.stop_propagation();
return;
}
}
let is_singleton = editor.buffer().read(cx).is_singleton(); let is_singleton = editor.buffer().read(cx).is_singleton();
if click_count == 2 && !is_singleton { if click_count == 2 && !is_singleton {
@@ -690,9 +676,9 @@ impl EditorElement {
} }
} }
let point_for_position = position_map.point_for_position(event.position);
let position = point_for_position.previous_valid; let position = point_for_position.previous_valid;
let multi_cursor_modifier = Editor::multi_cursor_modifier(true, &modifiers, cx); if modifiers == COLUMNAR_SELECTION_MODIFIERS {
if Editor::columnar_selection_modifiers(multi_cursor_modifier, &modifiers) {
editor.select( editor.select(
SelectPhase::BeginColumnar { SelectPhase::BeginColumnar {
position, position,
@@ -713,6 +699,11 @@ impl EditorElement {
cx, cx,
); );
} else { } else {
let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
let multi_cursor_modifier = match multi_cursor_setting {
MultiCursorModifier::Alt => modifiers.alt,
MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
};
editor.select( editor.select(
SelectPhase::Begin { SelectPhase::Begin {
position, position,
@@ -830,12 +821,6 @@ impl EditorElement {
let text_hitbox = &position_map.text_hitbox; let text_hitbox = &position_map.text_hitbox;
let end_selection = editor.has_pending_selection(); let end_selection = editor.has_pending_selection();
let pending_nonempty_selections = editor.has_pending_nonempty_selection(); let pending_nonempty_selections = editor.has_pending_nonempty_selection();
let point_for_position = position_map.point_for_position(event.position);
let is_cut = !event.modifiers.control;
if editor.drop_selection(Some(point_for_position), is_cut, window, cx) {
return;
}
if end_selection { if end_selection {
editor.select(SelectPhase::End, window, cx); editor.select(SelectPhase::End, window, cx);
@@ -882,9 +867,13 @@ impl EditorElement {
let text_hitbox = &position_map.text_hitbox; let text_hitbox = &position_map.text_hitbox;
let pending_nonempty_selections = editor.has_pending_nonempty_selection(); let pending_nonempty_selections = editor.has_pending_nonempty_selection();
let hovered_link_modifier = Editor::multi_cursor_modifier(false, &event.modifiers(), cx); let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
let multi_cursor_modifier = match multi_cursor_setting {
MultiCursorModifier::Alt => event.modifiers().secondary(),
MultiCursorModifier::CmdOrCtrl => event.modifiers().alt,
};
if !pending_nonempty_selections && hovered_link_modifier && text_hitbox.is_hovered(window) { if !pending_nonempty_selections && multi_cursor_modifier && text_hitbox.is_hovered(window) {
let point = position_map.point_for_position(event.up.position); let point = position_map.point_for_position(event.up.position);
editor.handle_click_hovered_link(point, event.modifiers(), window, cx); editor.handle_click_hovered_link(point, event.modifiers(), window, cx);
@@ -899,15 +888,12 @@ impl EditorElement {
window: &mut Window, window: &mut Window,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) { ) {
if !editor.has_pending_selection() if !editor.has_pending_selection() {
&& matches!(editor.selection_drag_state, SelectionDragState::None)
{
return; return;
} }
let text_bounds = position_map.text_hitbox.bounds; let text_bounds = position_map.text_hitbox.bounds;
let point_for_position = position_map.point_for_position(event.position); let point_for_position = position_map.point_for_position(event.position);
let mut scroll_delta = gpui::Point::<f32>::default(); let mut scroll_delta = gpui::Point::<f32>::default();
let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0); let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
let top = text_bounds.origin.y + vertical_margin; let top = text_bounds.origin.y + vertical_margin;
@@ -939,46 +925,15 @@ impl EditorElement {
scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right); scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
} }
if !editor.has_pending_selection() { editor.select(
let drop_anchor = position_map SelectPhase::Update {
.snapshot position: point_for_position.previous_valid,
.display_point_to_anchor(point_for_position.previous_valid, Bias::Left); goal_column: point_for_position.exact_unclipped.column(),
match editor.selection_drag_state { scroll_delta,
SelectionDragState::Dragging { },
ref mut drop_cursor, window,
.. cx,
} => { );
drop_cursor.start = drop_anchor;
drop_cursor.end = drop_anchor;
}
SelectionDragState::ReadyToDrag { ref selection } => {
let drop_cursor = Selection {
id: post_inc(&mut editor.selections.next_selection_id),
start: drop_anchor,
end: drop_anchor,
reversed: false,
goal: SelectionGoal::None,
};
editor.selection_drag_state = SelectionDragState::Dragging {
selection: selection.clone(),
drop_cursor,
};
}
_ => {}
}
editor.apply_scroll_delta(scroll_delta, window, cx);
cx.notify();
} else {
editor.select(
SelectPhase::Update {
position: point_for_position.previous_valid,
goal_column: point_for_position.exact_unclipped.column(),
scroll_delta,
},
window,
cx,
);
}
} }
fn mouse_moved( fn mouse_moved(
@@ -1207,34 +1162,6 @@ impl EditorElement {
let player = editor.current_user_player_color(cx); let player = editor.current_user_player_color(cx);
selections.push((player, layouts)); selections.push((player, layouts));
if let SelectionDragState::Dragging {
ref selection,
ref drop_cursor,
} = editor.selection_drag_state
{
if drop_cursor
.start
.cmp(&selection.start, &snapshot.buffer_snapshot)
.eq(&Ordering::Less)
|| drop_cursor
.end
.cmp(&selection.end, &snapshot.buffer_snapshot)
.eq(&Ordering::Greater)
{
let drag_cursor_layout = SelectionLayout::new(
drop_cursor.clone(),
false,
CursorShape::Bar,
&snapshot.display_snapshot,
false,
false,
None,
);
let absent_color = cx.theme().players().absent();
selections.push((absent_color, vec![drag_cursor_layout]));
}
}
} }
if let Some(collaboration_hub) = &editor.collaboration_hub { if let Some(collaboration_hub) = &editor.collaboration_hub {
@@ -9315,35 +9242,6 @@ impl PointForPosition {
None None
} }
} }
pub fn intersects_selection(&self, selection: &Selection<DisplayPoint>) -> bool {
let Some(valid_point) = self.as_valid() else {
return false;
};
let range = selection.range();
let candidate_row = valid_point.row();
let candidate_col = valid_point.column();
let start_row = range.start.row();
let start_col = range.start.column();
let end_row = range.end.row();
let end_col = range.end.column();
if candidate_row < start_row || candidate_row > end_row {
false
} else if start_row == end_row {
candidate_col >= start_col && candidate_col < end_col
} else {
if candidate_row == start_row {
candidate_col >= start_col
} else if candidate_row == end_row {
candidate_col < end_col
} else {
true
}
}
}
} }
impl PositionMap { impl PositionMap {

View File

@@ -1,7 +1,7 @@
use crate::{ use crate::{
Anchor, Editor, EditorSettings, EditorSnapshot, FindAllReferences, GoToDefinition, Anchor, Editor, EditorSettings, EditorSnapshot, FindAllReferences, GoToDefinition,
GoToTypeDefinition, GotoDefinitionKind, InlayId, Navigated, PointForPosition, SelectPhase, GoToTypeDefinition, GotoDefinitionKind, InlayId, Navigated, PointForPosition, SelectPhase,
editor_settings::GoToDefinitionFallback, editor_settings::{GoToDefinitionFallback, MultiCursorModifier},
hover_popover::{self, InlayHover}, hover_popover::{self, InlayHover},
scroll::ScrollAmount, scroll::ScrollAmount,
}; };
@@ -120,7 +120,11 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let hovered_link_modifier = Editor::multi_cursor_modifier(false, &modifiers, cx); let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
let hovered_link_modifier = match multi_cursor_setting {
MultiCursorModifier::Alt => modifiers.secondary(),
MultiCursorModifier::CmdOrCtrl => modifiers.alt,
};
if !hovered_link_modifier || self.has_pending_selection() { if !hovered_link_modifier || self.has_pending_selection() {
self.hide_hovered_link(cx); self.hide_hovered_link(cx);
return; return;

View File

@@ -2600,27 +2600,13 @@ impl MultiBuffer {
return title.into(); return title.into();
} }
self.as_singleton() if let Some(buffer) = self.as_singleton() {
.and_then(|buffer| { if let Some(file) = buffer.read(cx).file() {
let buffer = buffer.read(cx); return file.file_name(cx).to_string_lossy();
}
}
if let Some(file) = buffer.file() { "untitled".into()
return Some(file.file_name(cx).to_string_lossy());
}
let title = buffer
.snapshot()
.chars()
.skip_while(|ch| ch.is_whitespace())
.take_while(|&ch| ch != '\n')
.take(40)
.collect::<String>()
.trim_end()
.to_string();
(!title.is_empty()).then(|| title.into())
})
.unwrap_or("untitled".into())
} }
pub fn set_title(&mut self, title: String, cx: &mut Context<Self>) { pub fn set_title(&mut self, title: String, cx: &mut Context<Self>) {

View File

@@ -3651,59 +3651,3 @@ fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
"reversed_line_indents({max_row})" "reversed_line_indents({max_row})"
); );
} }
#[gpui::test]
fn test_new_empty_buffer_uses_untitled_title(cx: &mut App) {
let buffer = cx.new(|cx| Buffer::local("", cx));
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
assert_eq!(multibuffer.read(cx).title(cx), "untitled");
}
#[gpui::test]
fn test_new_empty_buffer_uses_untitled_title_when_only_contains_whitespace(cx: &mut App) {
let buffer = cx.new(|cx| Buffer::local("\n ", cx));
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
assert_eq!(multibuffer.read(cx).title(cx), "untitled");
}
#[gpui::test]
fn test_new_empty_buffer_takes_first_line_for_title(cx: &mut App) {
let buffer = cx.new(|cx| Buffer::local("Hello World\nSecond line", cx));
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
}
#[gpui::test]
fn test_new_empty_buffer_takes_trimmed_first_line_for_title(cx: &mut App) {
let buffer = cx.new(|cx| Buffer::local("\nHello, World ", cx));
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
assert_eq!(multibuffer.read(cx).title(cx), "Hello, World");
}
#[gpui::test]
fn test_new_empty_buffer_uses_truncated_first_line_for_title(cx: &mut App) {
let title_after = ["a", "b", "c", "d"]
.map(|letter| letter.repeat(10))
.join("");
let title = format!("{}{}", title_after, "e".repeat(10));
let buffer = cx.new(|cx| Buffer::local(title, cx));
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
assert_eq!(multibuffer.read(cx).title(cx), title_after);
}
#[gpui::test]
fn test_new_empty_buffers_title_can_be_set(cx: &mut App) {
let buffer = cx.new(|cx| Buffer::local("Hello World", cx));
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
multibuffer.update(cx, |multibuffer, cx| {
multibuffer.set_title("Hey".into(), cx)
});
assert_eq!(multibuffer.read(cx).title(cx), "Hey");
}

View File

@@ -1107,14 +1107,38 @@ impl OutlinePanel {
}); });
} else { } else {
let mut offset = Point::default(); let mut offset = Point::default();
let expand_excerpt_control_height = 1.0;
if let Some(buffer_id) = scroll_to_buffer { if let Some(buffer_id) = scroll_to_buffer {
if multi_buffer_snapshot.as_singleton().is_none() let current_folded = active_editor.read(cx).is_buffer_folded(buffer_id, cx);
&& !active_editor.read(cx).is_buffer_folded(buffer_id, cx) if current_folded {
{ let previous_buffer_id = self
offset.y = -(active_editor.read(cx).file_header_size() as f32); .fs_entries
.iter()
.rev()
.filter_map(|entry| match entry {
FsEntry::File(file) => Some(file.buffer_id),
FsEntry::ExternalFile(external_file) => {
Some(external_file.buffer_id)
}
FsEntry::Directory(..) => None,
})
.skip_while(|id| *id != buffer_id)
.nth(1);
if let Some(previous_buffer_id) = previous_buffer_id {
if !active_editor
.read(cx)
.is_buffer_folded(previous_buffer_id, cx)
{
offset.y += expand_excerpt_control_height;
}
}
} else {
if multi_buffer_snapshot.as_singleton().is_none() {
offset.y = -(active_editor.read(cx).file_header_size() as f32);
}
offset.y -= expand_excerpt_control_height;
} }
} }
active_editor.update(cx, |editor, cx| { active_editor.update(cx, |editor, cx| {
editor.set_scroll_anchor(ScrollAnchor { offset, anchor }, window, cx); editor.set_scroll_anchor(ScrollAnchor { offset, anchor }, window, cx);
}); });

View File

@@ -2790,9 +2790,7 @@ impl ProjectPanel {
let old_ancestors = std::mem::take(&mut self.ancestors); let old_ancestors = std::mem::take(&mut self.ancestors);
self.visible_entries.clear(); self.visible_entries.clear();
let mut max_width_item = None; let mut max_width_item = None;
let visible_worktrees: Vec<_> = project.visible_worktrees(cx).collect(); for worktree in project.visible_worktrees(cx) {
let hide_root = settings.hide_root && visible_worktrees.len() == 1;
for worktree in visible_worktrees {
let worktree_snapshot = worktree.read(cx).snapshot(); let worktree_snapshot = worktree.read(cx).snapshot();
let worktree_id = worktree_snapshot.id(); let worktree_id = worktree_snapshot.id();
@@ -2827,10 +2825,6 @@ impl ProjectPanel {
GitTraversal::new(&repo_snapshots, worktree_snapshot.entries(true, 0)); GitTraversal::new(&repo_snapshots, worktree_snapshot.entries(true, 0));
let mut auto_folded_ancestors = vec![]; let mut auto_folded_ancestors = vec![];
while let Some(entry) = entry_iter.entry() { while let Some(entry) = entry_iter.entry() {
if hide_root && Some(entry.entry) == worktree.read(cx).root_entry() {
entry_iter.advance();
continue;
}
if auto_collapse_dirs && entry.kind.is_dir() { if auto_collapse_dirs && entry.kind.is_dir() {
auto_folded_ancestors.push(entry.id); auto_folded_ancestors.push(entry.id);
if !self.unfolded_dir_ids.contains(&entry.id) { if !self.unfolded_dir_ids.contains(&entry.id) {

View File

@@ -44,7 +44,6 @@ pub struct ProjectPanelSettings {
pub auto_fold_dirs: bool, pub auto_fold_dirs: bool,
pub scrollbar: ScrollbarSettings, pub scrollbar: ScrollbarSettings,
pub show_diagnostics: ShowDiagnostics, pub show_diagnostics: ShowDiagnostics,
pub hide_root: bool,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@@ -146,10 +145,6 @@ pub struct ProjectPanelSettingsContent {
pub show_diagnostics: Option<ShowDiagnostics>, pub show_diagnostics: Option<ShowDiagnostics>,
/// Settings related to indent guides in the project panel. /// Settings related to indent guides in the project panel.
pub indent_guides: Option<IndentGuidesSettingsContent>, pub indent_guides: Option<IndentGuidesSettingsContent>,
/// Hide main root dir
///
/// Default: false
pub hide_root: Option<bool>,
} }
impl Settings for ProjectPanelSettings { impl Settings for ProjectPanelSettings {

View File

@@ -4849,87 +4849,6 @@ async fn test_expand_all_for_entry(cx: &mut gpui::TestAppContext) {
); );
} }
/// Test the functionality of hiding the worktree root in the project panel.
#[gpui::test]
async fn test_hide_worktree_root(cx: &mut gpui::TestAppContext) {
// Initialize the test environment.
init_test(cx);
// Create a fake file system with a specific directory structure.
let fs = FakeFs::new(cx.executor().clone());
fs.insert_tree(
path!("/root"),
json!({
"dir1": {
"subdir1": {
"nested1": {
"file1.txt": "",
"file2.txt": ""
},
},
"subdir2": {
"file4.txt": ""
}
},
"dir2": {
"single_file": {
"file5.txt": ""
}
},
"file3.txt": ""
}),
)
.await;
// Create a project using the fake file system.
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
// Add a new window with the project panel.
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
// Create a visual test context from the window.
let cx = &mut VisualTestContext::from_window(*workspace, cx);
// Initialize the project panel.
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
// Update the global settings to hide the worktree root.
cx.update(|_, cx| {
let settings = *ProjectPanelSettings::get_global(cx);
ProjectPanelSettings::override_global(
ProjectPanelSettings {
hide_root: true,
auto_fold_dirs: true,
..settings
},
cx,
);
});
// Refresh the panel to apply the settings.
panel.update_in(cx, |panel, window, cx| {
panel.cancel(&menu::Cancel, window, cx);
});
// Assert that the worktree root is not visible in the panel.
/*
assert_eq!(
visible_entries_as_strings(&panel, 0..50, cx),
&[
"> dir1",
"> dir2",
" file3.txt"
]
);
*/
assert_eq!(
visible_entries_as_strings(&panel, 0..50, cx),
&[
"> dir1",
"> dir2/single_file",
" file3.txt"
]
);
}
#[gpui::test] #[gpui::test]
async fn test_collapse_all_for_entry(cx: &mut gpui::TestAppContext) { async fn test_collapse_all_for_entry(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);

View File

@@ -915,9 +915,6 @@ impl Vim {
if mode == Mode::Normal || mode != last_mode { if mode == Mode::Normal || mode != last_mode {
self.current_tx.take(); self.current_tx.take();
self.current_anchor.take(); self.current_anchor.take();
self.update_editor(window, cx, |_, editor, window, cx| {
editor.drop_selection(None, false, window, cx);
});
} }
Vim::take_forced_motion(cx); Vim::take_forced_motion(cx);
if mode != Mode::Insert && mode != Mode::Replace { if mode != Mode::Insert && mode != Mode::Replace {

View File

@@ -2925,9 +2925,15 @@ impl Pane {
let ix = if moved_right { ix - 1 } else { ix }; let ix = if moved_right { ix - 1 } else { ix };
let is_pinned_in_to_pane = this.is_tab_pinned(ix); let is_pinned_in_to_pane = this.is_tab_pinned(ix);
if !was_pinned_in_from_pane && is_pinned_in_to_pane { if (was_pinned_in_from_pane && is_pinned_in_to_pane)
|| (!was_pinned_in_from_pane && !is_pinned_in_to_pane)
{
return;
}
if is_pinned_in_to_pane {
this.pinned_tab_count += 1; this.pinned_tab_count += 1;
} else if was_pinned_in_from_pane && !is_pinned_in_to_pane { } else {
this.pinned_tab_count -= 1; this.pinned_tab_count -= 1;
} }
} else if this.items.len() >= to_pane_old_length { } else if this.items.len() >= to_pane_old_length {

View File

@@ -3027,7 +3027,7 @@ mod tests {
}); });
cx.read(|cx| { cx.read(|cx| {
assert!(editor.is_dirty(cx)); assert!(editor.is_dirty(cx));
assert_eq!(editor.read(cx).title(cx), "hi"); assert_eq!(editor.read(cx).title(cx), "untitled");
}); });
// When the save completes, the buffer's title is updated and the language is assigned based // When the save completes, the buffer's title is updated and the language is assigned based

View File

@@ -1216,16 +1216,6 @@ or
`boolean` values `boolean` values
### Drag And Drop Selection
- Description: Whether to allow drag and drop text selection in buffer.
- Setting: `drag_and_drop_selection`
- Default: `true`
**Options**
`boolean` values
## Editor Toolbar ## Editor Toolbar
- Description: Whether or not to show various elements in the editor toolbar. - Description: Whether or not to show various elements in the editor toolbar.
@@ -1934,30 +1924,6 @@ Example:
`boolean` values `boolean` values
## Multi Cursor Modifier
- Description: Determines the modifier to be used to add multiple cursors with the mouse. The open hover link mouse gestures will adapt such that it do not conflict with the multicursor modifier.
- Setting: `multi_cursor_modifier`
- Default: `alt`
**Options**
1. Maps to `Alt` on Linux and Windows and to `Option` on MacOS:
```jsonc
{
"multi_cursor_modifier": "alt",
}
```
2. Maps `Control` on Linux and Windows and to `Command` on MacOS:
```jsonc
{
"multi_cursor_modifier": "cmd_or_ctrl", // alias: "cmd", "ctrl"
}
```
## Hover Popover Enabled ## Hover Popover Enabled
- Description: Whether or not to show the informational hover box when moving the mouse over symbols in the editor. - Description: Whether or not to show the informational hover box when moving the mouse over symbols in the editor.

View File

@@ -42,7 +42,7 @@ You can add this to Zed project settings (`.zed/settings.json`) or via your Zed
### Advanced Formatting ### Advanced Formatting
Sql-formatter also allows more precise control by providing [sql-formatter configuration options](https://github.com/sql-formatter-org/sql-formatter#configuration-options). To provide these, create a `.sql-formatter.json` file in your project: Sql-formatter also allows more precise control by providing [sql-formatter configuration options](https://github.com/sql-formatter-org/sql-formatter#configuration-options). To provide these, create a `sql-formatter.json` file in your project:
```json ```json
{ {
@@ -53,7 +53,7 @@ Sql-formatter also allows more precise control by providing [sql-formatter confi
} }
``` ```
When using a `.sql-formatter.json` file you can use a more simplified set of Zed settings since the language need not be specified inline: When using a `sql-formatter.json` file you can use a more simplified set of Zed settings since the language need not be specified inline:
```json ```json
"languages": { "languages": {

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
pattern='cmd-'
result=$(git grep --no-color --line-number --fixed-strings -e "$pattern" -- \
'assets/keymaps/' \
':(exclude)assets/keymaps/storybook.json' \
':(exclude)assets/keymaps/default-macos.json' \
':(exclude)assets/keymaps/macos/*.json' || true)
if [[ -n "${result}" ]]; then
echo "${result}"
echo "Error: Found 'cmd-' in non-macOS keymap files."
exit 1
fi
pattern='super-|win-|fn-'
result=$(git grep --no-color --line-number --fixed-strings -e "$pattern" -- \
'assets/keymaps/' || true)
if [[ -n "${result}" ]]; then
echo "${result}"
echo "Error: Found 'super-', 'win-', or 'fn-' in keymap files. Currently these aren't used."
exit 1
fi

View File

@@ -8,7 +8,7 @@ result=$(git grep --no-color --ignore-case --line-number --extended-regexp -e $p
':(exclude).github/workflows/ci.yml' \ ':(exclude).github/workflows/ci.yml' \
':(exclude)*criteria.md' \ ':(exclude)*criteria.md' \
':(exclude)*prompt.md' || true) ':(exclude)*prompt.md' || true)
echo "${result}"
if [[ -n "${result}" ]]; then if [[ -n "${result}" ]]; then
echo "${result}"
exit 1 exit 1
fi fi