vim: Improve lifecycle (#16477)
Closes #13579 A major painpoint in the Vim crate has been life-cycle management. We used to have one global Vim instance that tried to track per-editor state; this led to a number of subtle issues (e.g. #13579, the mode indicator being global, and quick toggling between windows letting vim mode's notion of the active editor get out of sync). This PR changes the internal structure of the code so that there is now one `Vim` instance per `Editor` (stored as an `Addon`); and the global stuff is separated out. This fixes the above problems, and tidies up a bunch of the mess in the codebase. Release Notes: * vim: Fixed accidental visual mode in project search and go to references ([#13579](https://github.com/zed-industries/zed/issues/13579)).
This commit is contained in:
+35
-24
@@ -462,6 +462,14 @@ struct ResolvedTasks {
|
||||
struct MultiBufferOffset(usize);
|
||||
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
|
||||
struct BufferOffset(usize);
|
||||
|
||||
// Addons allow storing per-editor state in other crates (e.g. Vim)
|
||||
pub trait Addon: 'static {
|
||||
fn extend_key_context(&self, _: &mut KeyContext, _: &AppContext) {}
|
||||
|
||||
fn to_any(&self) -> &dyn std::any::Any;
|
||||
}
|
||||
|
||||
/// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
|
||||
///
|
||||
/// See the [module level documentation](self) for more information.
|
||||
@@ -533,7 +541,6 @@ pub struct Editor {
|
||||
collapse_matches: bool,
|
||||
autoindent_mode: Option<AutoindentMode>,
|
||||
workspace: Option<(WeakView<Workspace>, Option<WorkspaceId>)>,
|
||||
keymap_context_layers: BTreeMap<TypeId, KeyContext>,
|
||||
input_enabled: bool,
|
||||
use_modal_editing: bool,
|
||||
read_only: bool,
|
||||
@@ -551,7 +558,6 @@ pub struct Editor {
|
||||
_subscriptions: Vec<Subscription>,
|
||||
pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
|
||||
gutter_dimensions: GutterDimensions,
|
||||
pub vim_replace_map: HashMap<Range<usize>, String>,
|
||||
style: Option<EditorStyle>,
|
||||
next_editor_action_id: EditorActionId,
|
||||
editor_actions: Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut ViewContext<Self>)>>>>,
|
||||
@@ -581,6 +587,7 @@ pub struct Editor {
|
||||
breadcrumb_header: Option<String>,
|
||||
focused_block: Option<FocusedBlock>,
|
||||
next_scroll_position: NextScrollCursorCenterTopBottom,
|
||||
addons: HashMap<TypeId, Box<dyn Addon>>,
|
||||
_scroll_cursor_center_top_bottom_task: Task<()>,
|
||||
}
|
||||
|
||||
@@ -1875,7 +1882,6 @@ impl Editor {
|
||||
autoindent_mode: Some(AutoindentMode::EachLine),
|
||||
collapse_matches: false,
|
||||
workspace: None,
|
||||
keymap_context_layers: Default::default(),
|
||||
input_enabled: true,
|
||||
use_modal_editing: mode == EditorMode::Full,
|
||||
read_only: false,
|
||||
@@ -1900,7 +1906,6 @@ impl Editor {
|
||||
hovered_cursors: Default::default(),
|
||||
next_editor_action_id: EditorActionId::default(),
|
||||
editor_actions: Rc::default(),
|
||||
vim_replace_map: Default::default(),
|
||||
show_inline_completions: mode == EditorMode::Full,
|
||||
custom_context_menu: None,
|
||||
show_git_blame_gutter: false,
|
||||
@@ -1939,6 +1944,7 @@ impl Editor {
|
||||
breadcrumb_header: None,
|
||||
focused_block: None,
|
||||
next_scroll_position: NextScrollCursorCenterTopBottom::default(),
|
||||
addons: HashMap::default(),
|
||||
_scroll_cursor_center_top_bottom_task: Task::ready(()),
|
||||
};
|
||||
this.tasks_update_task = Some(this.refresh_runnables(cx));
|
||||
@@ -1961,13 +1967,13 @@ impl Editor {
|
||||
this
|
||||
}
|
||||
|
||||
pub fn mouse_menu_is_focused(&self, cx: &mut WindowContext) -> bool {
|
||||
pub fn mouse_menu_is_focused(&self, cx: &WindowContext) -> bool {
|
||||
self.mouse_context_menu
|
||||
.as_ref()
|
||||
.is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(cx))
|
||||
}
|
||||
|
||||
fn key_context(&self, cx: &AppContext) -> KeyContext {
|
||||
fn key_context(&self, cx: &ViewContext<Self>) -> KeyContext {
|
||||
let mut key_context = KeyContext::new_with_defaults();
|
||||
key_context.add("Editor");
|
||||
let mode = match self.mode {
|
||||
@@ -1998,8 +2004,13 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
for layer in self.keymap_context_layers.values() {
|
||||
key_context.extend(layer);
|
||||
// Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
|
||||
if !self.focus_handle(cx).contains_focused(cx)
|
||||
|| (self.is_focused(cx) || self.mouse_menu_is_focused(cx))
|
||||
{
|
||||
for addon in self.addons.values() {
|
||||
addon.extend_key_context(&mut key_context, cx)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(extension) = self
|
||||
@@ -2241,21 +2252,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_keymap_context_layer<Tag: 'static>(
|
||||
&mut self,
|
||||
context: KeyContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.keymap_context_layers
|
||||
.insert(TypeId::of::<Tag>(), context);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn remove_keymap_context_layer<Tag: 'static>(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.keymap_context_layers.remove(&TypeId::of::<Tag>());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_input_enabled(&mut self, input_enabled: bool) {
|
||||
self.input_enabled = input_enabled;
|
||||
}
|
||||
@@ -11864,7 +11860,6 @@ impl Editor {
|
||||
self.editor_actions.borrow_mut().insert(
|
||||
id,
|
||||
Box::new(move |cx| {
|
||||
let _view = cx.view().clone();
|
||||
let cx = cx.window_context();
|
||||
let listener = listener.clone();
|
||||
cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
|
||||
@@ -11950,6 +11945,22 @@ impl Editor {
|
||||
menu.visible() && matches!(menu, ContextMenu::Completions(_))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn register_addon<T: Addon>(&mut self, instance: T) {
|
||||
self.addons
|
||||
.insert(std::any::TypeId::of::<T>(), Box::new(instance));
|
||||
}
|
||||
|
||||
pub fn unregister_addon<T: Addon>(&mut self) {
|
||||
self.addons.remove(&std::any::TypeId::of::<T>());
|
||||
}
|
||||
|
||||
pub fn addon<T: Addon>(&self) -> Option<&T> {
|
||||
let type_id = std::any::TypeId::of::<T>();
|
||||
self.addons
|
||||
.get(&type_id)
|
||||
.and_then(|item| item.to_any().downcast_ref::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
fn hunks_for_selections(
|
||||
|
||||
@@ -5613,7 +5613,7 @@ impl Element for EditorElement {
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
let key_context = self.editor.read(cx).key_context(cx);
|
||||
let key_context = self.editor.update(cx, |editor, cx| editor.key_context(cx));
|
||||
cx.set_key_context(key_context);
|
||||
cx.handle_input(
|
||||
&focus_handle,
|
||||
|
||||
Reference in New Issue
Block a user