Detailed changes
@@ -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,
@@ -1,64 +1,55 @@
use editor::{display_map::ToDisplayPoint, movement, scroll::Autoscroll, Bias, Direction, Editor};
-use gpui::{actions, View};
-use ui::{ViewContext, WindowContext};
-use workspace::Workspace;
+use gpui::{actions, ViewContext};
use crate::{state::Mode, Vim};
actions!(vim, [ChangeListOlder, ChangeListNewer]);
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_, _: &ChangeListOlder, cx| {
- Vim::update(cx, |vim, cx| {
- move_to_change(vim, Direction::Prev, cx);
- })
+pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(editor, cx, |vim, _: &ChangeListOlder, cx| {
+ vim.move_to_change(Direction::Prev, cx);
});
- workspace.register_action(|_, _: &ChangeListNewer, cx| {
- Vim::update(cx, |vim, cx| {
- move_to_change(vim, Direction::Next, cx);
- })
+ Vim::action(editor, cx, |vim, _: &ChangeListNewer, cx| {
+ vim.move_to_change(Direction::Next, cx);
});
}
-fn move_to_change(vim: &mut Vim, direction: Direction, cx: &mut WindowContext) {
- let count = vim.take_count(cx).unwrap_or(1);
- let selections = vim.update_state(|state| {
- if state.change_list.is_empty() {
- return None;
+impl Vim {
+ fn move_to_change(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
+ let count = self.take_count(cx).unwrap_or(1);
+ if self.change_list.is_empty() {
+ return;
}
- let prev = state
- .change_list_position
- .unwrap_or(state.change_list.len());
+ let prev = self.change_list_position.unwrap_or(self.change_list.len());
let next = if direction == Direction::Prev {
prev.saturating_sub(count)
} else {
- (prev + count).min(state.change_list.len() - 1)
+ (prev + count).min(self.change_list.len() - 1)
};
- state.change_list_position = Some(next);
- state.change_list.get(next).cloned()
- });
-
- let Some(selections) = selections else {
- return;
- };
- vim.update_active_editor(cx, |_, editor, cx| {
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- let map = s.display_map();
- s.select_display_ranges(selections.into_iter().map(|a| {
- let point = a.to_display_point(&map);
- point..point
- }))
- })
- });
-}
+ self.change_list_position = Some(next);
+ let Some(selections) = self.change_list.get(next).cloned() else {
+ return;
+ };
+ self.update_editor(cx, |_, editor, cx| {
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ let map = s.display_map();
+ s.select_display_ranges(selections.into_iter().map(|a| {
+ let point = a.to_display_point(&map);
+ point..point
+ }))
+ })
+ });
+ }
-pub(crate) fn push_to_change_list(vim: &mut Vim, editor: View<Editor>, cx: &mut WindowContext) {
- let (map, selections) =
- editor.update(cx, |editor, cx| editor.selections.all_adjusted_display(cx));
+ pub(crate) fn push_to_change_list(&mut self, cx: &mut ViewContext<Self>) {
+ let Some((map, selections)) = self.update_editor(cx, |_, editor, cx| {
+ editor.selections.all_adjusted_display(cx)
+ }) else {
+ return;
+ };
- let pop_state =
- vim.state()
+ let pop_state = self
.change_list
.last()
.map(|previous| {
@@ -69,25 +60,24 @@ pub(crate) fn push_to_change_list(vim: &mut Vim, editor: View<Editor>, cx: &mut
})
.unwrap_or(false);
- let new_positions = selections
- .into_iter()
- .map(|s| {
- let point = if vim.state().mode == Mode::Insert {
- movement::saturating_left(&map, s.head())
- } else {
- s.head()
- };
- map.display_point_to_anchor(point, Bias::Left)
- })
- .collect();
-
- vim.update_state(|state| {
- state.change_list_position.take();
+ let new_positions = selections
+ .into_iter()
+ .map(|s| {
+ let point = if self.mode == Mode::Insert {
+ movement::saturating_left(&map, s.head())
+ } else {
+ s.head()
+ };
+ map.display_point_to_anchor(point, Bias::Left)
+ })
+ .collect();
+
+ self.change_list_position.take();
if pop_state {
- state.change_list.pop();
+ self.change_list.pop();
}
- state.change_list.push(new_positions);
- })
+ self.change_list.push(new_positions);
+ }
}
#[cfg(test)]
@@ -12,12 +12,11 @@ use multi_buffer::MultiBufferRow;
use serde::Deserialize;
use ui::WindowContext;
use util::ResultExt;
-use workspace::{notifications::NotifyResultExt, SaveIntent, Workspace};
+use workspace::{notifications::NotifyResultExt, SaveIntent};
use crate::{
motion::{EndOfDocument, Motion, StartOfDocument},
normal::{
- move_cursor,
search::{FindCommand, ReplaceCommand, Replacement},
JoinLines,
},
@@ -66,77 +65,89 @@ impl Clone for WithRange {
}
}
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|workspace, _: &VisualCommand, cx| {
- command_palette::CommandPalette::toggle(workspace, "'<,'>", cx);
- });
-
- workspace.register_action(|workspace, _: &CountCommand, cx| {
- let count = Vim::update(cx, |vim, cx| vim.take_count(cx)).unwrap_or(1);
- command_palette::CommandPalette::toggle(
- workspace,
- &format!(".,.+{}", count.saturating_sub(1)),
- cx,
- );
+pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(editor, cx, |vim, _: &VisualCommand, cx| {
+ let Some(workspace) = vim.workspace(cx) else {
+ return;
+ };
+ workspace.update(cx, |workspace, cx| {
+ command_palette::CommandPalette::toggle(workspace, "'<,'>", cx);
+ })
});
- workspace.register_action(|workspace: &mut Workspace, action: &GoToLine, cx| {
- Vim::update(cx, |vim, cx| {
- vim.switch_mode(Mode::Normal, false, cx);
- let result = vim.update_active_editor(cx, |vim, editor, cx| {
- action.range.head().buffer_row(vim, editor, cx)
- });
- let Some(buffer_row) = result else {
- return anyhow::Ok(());
- };
- move_cursor(
- vim,
- Motion::StartOfDocument,
- Some(buffer_row?.0 as usize + 1),
+ Vim::action(editor, cx, |vim, _: &CountCommand, cx| {
+ let Some(workspace) = vim.workspace(cx) else {
+ return;
+ };
+ let count = vim.take_count(cx).unwrap_or(1);
+ workspace.update(cx, |workspace, cx| {
+ command_palette::CommandPalette::toggle(
+ workspace,
+ &format!(".,.+{}", count.saturating_sub(1)),
cx,
);
- Ok(())
})
- .notify_err(workspace, cx);
});
- workspace.register_action(|workspace: &mut Workspace, action: &WithRange, cx| {
+ Vim::action(editor, cx, |vim, action: &GoToLine, cx| {
+ vim.switch_mode(Mode::Normal, false, cx);
+ let result = vim.update_editor(cx, |vim, editor, cx| {
+ action.range.head().buffer_row(vim, editor, cx)
+ });
+ let buffer_row = match result {
+ None => return,
+ Some(e @ Err(_)) => {
+ let Some(workspace) = vim.workspace(cx) else {
+ return;
+ };
+ workspace.update(cx, |workspace, cx| {
+ e.notify_err(workspace, cx);
+ });
+ return;
+ }
+ Some(Ok(result)) => result,
+ };
+ vim.move_cursor(Motion::StartOfDocument, Some(buffer_row.0 as usize + 1), cx);
+ });
+
+ Vim::action(editor, cx, |vim, action: &WithRange, cx| {
if action.is_count {
for _ in 0..action.range.as_count() {
cx.dispatch_action(action.action.boxed_clone())
}
- } else {
- Vim::update(cx, |vim, cx| {
- let result = vim.update_active_editor(cx, |vim, editor, cx| {
- action.range.buffer_range(vim, editor, cx)
- });
- let Some(range) = result else {
- return anyhow::Ok(());
+ return;
+ }
+ let result = vim.update_editor(cx, |vim, editor, cx| {
+ action.range.buffer_range(vim, editor, cx)
+ });
+
+ let range = match result {
+ None => return,
+ Some(e @ Err(_)) => {
+ let Some(workspace) = vim.workspace(cx) else {
+ return;
};
- let range = range?;
- vim.update_active_editor(cx, |_, editor, cx| {
- editor.change_selections(None, cx, |s| {
- let end = Point::new(range.end.0, s.buffer().line_len(range.end));
- s.select_ranges([end..Point::new(range.start.0, 0)]);
- })
- });
- cx.dispatch_action(action.action.boxed_clone());
- cx.defer(move |cx| {
- Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |_, editor, cx| {
- editor.change_selections(None, cx, |s| {
- s.select_ranges([
- Point::new(range.start.0, 0)..Point::new(range.start.0, 0)
- ]);
- })
- });
- })
+ workspace.update(cx, |workspace, cx| {
+ e.notify_err(workspace, cx);
});
-
- Ok(())
+ return;
+ }
+ Some(Ok(result)) => result,
+ };
+ vim.update_editor(cx, |_, editor, cx| {
+ editor.change_selections(None, cx, |s| {
+ let end = Point::new(range.end.0, s.buffer().line_len(range.end));
+ s.select_ranges([end..Point::new(range.start.0, 0)]);
})
- .notify_err(workspace, cx);
- }
+ });
+ cx.dispatch_action(action.action.boxed_clone());
+ cx.defer(move |vim, cx| {
+ vim.update_editor(cx, |_, editor, cx| {
+ editor.change_selections(None, cx, |s| {
+ s.select_ranges([Point::new(range.start.0, 0)..Point::new(range.start.0, 0)]);
+ })
+ });
+ });
});
}
@@ -343,12 +354,7 @@ impl Position {
let target = match self {
Position::Line { row, offset } => row.saturating_add_signed(offset.saturating_sub(1)),
Position::Mark { name, offset } => {
- let Some(mark) = vim
- .state()
- .marks
- .get(&name.to_string())
- .and_then(|vec| vec.last())
- else {
+ let Some(mark) = vim.marks.get(&name.to_string()).and_then(|vec| vec.last()) else {
return Err(anyhow!("mark {} not set", name));
};
mark.to_point(&snapshot.buffer_snapshot)
@@ -4,7 +4,7 @@ use collections::HashMap;
use gpui::AppContext;
use settings::Settings;
use std::sync::LazyLock;
-use ui::WindowContext;
+use ui::ViewContext;
use crate::{Vim, VimSettings};
@@ -34,16 +34,21 @@ fn lookup_digraph(a: char, b: char, cx: &AppContext) -> Arc<str> {
.unwrap_or_else(|| b.to_string().into())
}
-pub fn insert_digraph(first_char: char, second_char: char, cx: &mut WindowContext) {
- let text = lookup_digraph(first_char, second_char, &cx);
-
- Vim::update(cx, |vim, cx| vim.pop_operator(cx));
- if Vim::read(cx).state().editor_input_enabled() {
- Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |_, editor, cx| editor.insert(&text, cx));
- });
- } else {
- Vim::active_editor_input_ignored(text, cx);
+impl Vim {
+ pub fn insert_digraph(
+ &mut self,
+ first_char: char,
+ second_char: char,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let text = lookup_digraph(first_char, second_char, &cx);
+
+ self.pop_operator(cx);
+ if self.editor_input_enabled() {
+ self.update_editor(cx, |_, editor, cx| editor.insert(&text, cx));
+ } else {
+ self.input_ignored(text, cx);
+ }
}
}
@@ -1,153 +0,0 @@
-use crate::{insert::NormalBefore, Vim, VimModeSetting};
-use editor::{Editor, EditorEvent};
-use gpui::{Action, AppContext, Entity, EntityId, UpdateGlobal, View, ViewContext, WindowContext};
-use settings::{Settings, SettingsStore};
-
-pub fn init(cx: &mut AppContext) {
- cx.observe_new_views(|_, cx: &mut ViewContext<Editor>| {
- let editor = cx.view().clone();
- cx.subscribe(&editor, |_, editor, event: &EditorEvent, cx| match event {
- EditorEvent::Focused => cx.window_context().defer(|cx| focused(editor, cx)),
- EditorEvent::Blurred => cx.window_context().defer(|cx| blurred(editor, cx)),
- _ => {}
- })
- .detach();
-
- let mut enabled = VimModeSetting::get_global(cx).0;
- cx.observe_global::<SettingsStore>(move |editor, cx| {
- if VimModeSetting::get_global(cx).0 != enabled {
- enabled = VimModeSetting::get_global(cx).0;
- if !enabled {
- Vim::unhook_vim_settings(editor, cx);
- }
- }
- })
- .detach();
-
- let id = cx.view().entity_id();
- cx.on_release(move |_, _, cx| released(id, cx)).detach();
- })
- .detach();
-}
-fn focused(editor: View<Editor>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- if !vim.enabled {
- return;
- }
- vim.activate_editor(editor.clone(), cx);
- });
-}
-
-fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- if !vim.enabled {
- return;
- }
- if let Some(previous_editor) = vim.active_editor.clone() {
- vim.stop_recording_immediately(NormalBefore.boxed_clone());
- if previous_editor
- .upgrade()
- .is_some_and(|previous| previous == editor.clone())
- {
- vim.store_visual_marks(cx);
- vim.clear_operator(cx);
- }
- }
- editor.update(cx, |editor, cx| {
- if editor.use_modal_editing() {
- editor.set_cursor_shape(language::CursorShape::Hollow, cx);
- }
- });
- });
-}
-
-fn released(entity_id: EntityId, cx: &mut AppContext) {
- Vim::update_global(cx, |vim, _cx| {
- if vim
- .active_editor
- .as_ref()
- .is_some_and(|previous| previous.entity_id() == entity_id)
- {
- vim.active_editor = None;
- vim.editor_subscription = None;
- }
- vim.editor_states.remove(&entity_id)
- });
-}
-
-#[cfg(test)]
-mod test {
- use crate::{test::VimTestContext, Vim};
- use editor::Editor;
- use gpui::{Context, Entity, VisualTestContext};
- use language::Buffer;
-
- // regression test for blur called with a different active editor
- #[gpui::test]
- async fn test_blur_focus(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- let buffer = cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx));
- let window2 = cx.add_window(|cx| Editor::for_buffer(buffer, None, cx));
- let editor2 = cx
- .update(|cx| {
- window2.update(cx, |_, cx| {
- cx.activate_window();
- cx.focus_self();
- cx.view().clone()
- })
- })
- .unwrap();
- cx.run_until_parked();
-
- cx.update(|cx| {
- let vim = Vim::read(cx);
- assert_eq!(
- vim.active_editor.as_ref().unwrap().entity_id(),
- editor2.entity_id(),
- )
- });
-
- // no panic when blurring an editor in a different window.
- cx.update_editor(|editor1, cx| {
- editor1.handle_blur(cx);
- });
- }
-
- // regression test for focus_in/focus_out being called on window activation
- #[gpui::test]
- async fn test_focus_across_windows(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- let mut cx1 = VisualTestContext::from_window(cx.window, &cx);
- let editor1 = cx.editor.clone();
-
- let buffer = cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx));
- let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx));
-
- editor2.update(cx2, |_, cx| {
- cx.focus_self();
- cx.activate_window();
- });
- cx.run_until_parked();
-
- cx1.update(|cx| {
- assert_eq!(
- Vim::read(cx).active_editor.as_ref().unwrap().entity_id(),
- editor2.entity_id(),
- )
- });
-
- cx1.update(|cx| {
- cx.activate_window();
- });
- cx.run_until_parked();
-
- cx.update(|cx| {
- assert_eq!(
- Vim::read(cx).active_editor.as_ref().unwrap().entity_id(),
- editor1.entity_id(),
- )
- });
- }
-}
@@ -1,31 +1,26 @@
-use crate::{
- normal::{mark::create_mark, repeat},
- state::Mode,
- Vim,
-};
-use editor::{scroll::Autoscroll, Bias};
+use crate::{state::Mode, Vim};
+use editor::{scroll::Autoscroll, Bias, Editor};
use gpui::{actions, Action, ViewContext};
use language::SelectionGoal;
-use workspace::Workspace;
actions!(vim, [NormalBefore]);
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(normal_before);
+pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(editor, cx, Vim::normal_before);
}
-fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<Workspace>) {
- let should_repeat = Vim::update(cx, |vim, cx| {
- if vim.state().active_operator().is_some() {
- vim.update_state(|state| state.operator_stack.clear());
- vim.sync_vim_settings(cx);
- return false;
+impl Vim {
+ fn normal_before(&mut self, action: &NormalBefore, cx: &mut ViewContext<Self>) {
+ if self.active_operator().is_some() {
+ self.operator_stack.clear();
+ self.sync_vim_settings(cx);
+ return;
}
- let count = vim.take_count(cx).unwrap_or(1);
- vim.stop_recording_immediately(action.boxed_clone());
- if count <= 1 || vim.workspace_state.dot_replaying {
- create_mark(vim, "^".into(), false, cx);
- vim.update_active_editor(cx, |_, editor, cx| {
+ let count = self.take_count(cx).unwrap_or(1);
+ self.stop_recording_immediately(action.boxed_clone(), cx);
+ if count <= 1 || Vim::globals(cx).dot_replaying {
+ self.create_mark("^".into(), false, cx);
+ self.update_editor(cx, |_, editor, cx| {
editor.dismiss_menus_and_popups(false, cx);
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_cursors_with(|map, mut cursor, _| {
@@ -34,15 +29,11 @@ fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<
});
});
});
- vim.switch_mode(Mode::Normal, false, cx);
- false
- } else {
- true
+ self.switch_mode(Mode::Normal, false, cx);
+ return;
}
- });
- if should_repeat {
- repeat::repeat(cx, true)
+ self.repeat(true, cx)
}
}
@@ -1,93 +1,95 @@
-use gpui::{div, Element, Render, Subscription, ViewContext};
+use gpui::{div, Element, Render, Subscription, View, ViewContext, WeakView};
use itertools::Itertools;
use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView};
-use crate::{state::Mode, Vim};
+use crate::{Vim, VimEvent};
/// The ModeIndicator displays the current mode in the status bar.
pub struct ModeIndicator {
- pub(crate) mode: Option<Mode>,
- pub(crate) operators: String,
+ vim: Option<WeakView<Vim>>,
pending_keys: Option<String>,
- _subscriptions: Vec<Subscription>,
+ vim_subscription: Option<Subscription>,
}
impl ModeIndicator {
/// Construct a new mode indicator in this window.
pub fn new(cx: &mut ViewContext<Self>) -> Self {
- let _subscriptions = vec![
- cx.observe_global::<Vim>(|this, cx| this.update_mode(cx)),
- cx.observe_pending_input(|this, cx| {
- this.update_pending_keys(cx);
- cx.notify();
- }),
- ];
+ cx.observe_pending_input(|this, cx| {
+ this.update_pending_keys(cx);
+ cx.notify();
+ })
+ .detach();
- let mut this = Self {
- mode: None,
- operators: "".to_string(),
- pending_keys: None,
- _subscriptions,
- };
- this.update_mode(cx);
- this
- }
+ let handle = cx.view().clone();
+ let window = cx.window_handle();
+ cx.observe_new_views::<Vim>(move |_, cx| {
+ if cx.window_handle() != window {
+ return;
+ }
+ let vim = cx.view().clone();
+ handle.update(cx, |_, cx| {
+ cx.subscribe(&vim, |mode_indicator, vim, event, cx| match event {
+ VimEvent::Focused => {
+ mode_indicator.vim_subscription =
+ Some(cx.observe(&vim, |_, _, cx| cx.notify()));
+ mode_indicator.vim = Some(vim.downgrade());
+ }
+ })
+ .detach()
+ })
+ })
+ .detach();
- fn update_mode(&mut self, cx: &mut ViewContext<Self>) {
- if let Some(vim) = self.vim(cx) {
- self.mode = Some(vim.state().mode);
- self.operators = self.current_operators_description(&vim);
- } else {
- self.mode = None;
+ Self {
+ vim: None,
+ pending_keys: None,
+ vim_subscription: None,
}
}
fn update_pending_keys(&mut self, cx: &mut ViewContext<Self>) {
- if self.vim(cx).is_some() {
- self.pending_keys = cx.pending_input_keystrokes().map(|keystrokes| {
- keystrokes
- .iter()
- .map(|keystroke| format!("{}", keystroke))
- .join(" ")
- });
- } else {
- self.pending_keys = None;
- }
+ self.pending_keys = cx.pending_input_keystrokes().map(|keystrokes| {
+ keystrokes
+ .iter()
+ .map(|keystroke| format!("{}", keystroke))
+ .join(" ")
+ });
}
- fn vim<'a>(&self, cx: &'a mut ViewContext<Self>) -> Option<&'a Vim> {
- // In some tests Vim isn't enabled, so we use try_global.
- cx.try_global::<Vim>().filter(|vim| vim.enabled)
+ fn vim(&self) -> Option<View<Vim>> {
+ self.vim.as_ref().and_then(|vim| vim.upgrade())
}
- fn current_operators_description(&self, vim: &Vim) -> String {
- vim.workspace_state
+ fn current_operators_description(&self, vim: View<Vim>, cx: &mut ViewContext<Self>) -> String {
+ let recording = Vim::globals(cx)
.recording_register
.map(|reg| format!("recording @{reg} "))
- .into_iter()
- .chain(vim.state().pre_count.map(|count| format!("{}", count)))
- .chain(vim.state().selected_register.map(|reg| format!("\"{reg}")))
- .chain(
- vim.state()
- .operator_stack
- .iter()
- .map(|item| item.id().to_string()),
- )
- .chain(vim.state().post_count.map(|count| format!("{}", count)))
+ .into_iter();
+
+ let vim = vim.read(cx);
+ recording
+ .chain(vim.pre_count.map(|count| format!("{}", count)))
+ .chain(vim.selected_register.map(|reg| format!("\"{reg}")))
+ .chain(vim.operator_stack.iter().map(|item| item.id().to_string()))
+ .chain(vim.post_count.map(|count| format!("{}", count)))
.collect::<Vec<_>>()
.join("")
}
}
impl Render for ModeIndicator {
- fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
- let Some(mode) = self.mode.as_ref() else {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+ let vim = self.vim();
+ let Some(vim) = vim else {
return div().into_any();
};
- let pending = self.pending_keys.as_ref().unwrap_or(&self.operators);
-
- Label::new(format!("{} -- {} --", pending, mode))
+ let current_operators_description = self.current_operators_description(vim.clone(), cx);
+ let pending = self
+ .pending_keys
+ .as_ref()
+ .unwrap_or(¤t_operators_description);
+ Label::new(format!("{} -- {} --", pending, vim.read(cx).mode))
.size(LabelSize::Small)
.line_height_style(LineHeightStyle::UiLabel)
.into_any_element()
@@ -100,6 +102,5 @@ impl StatusItemView for ModeIndicator {
_active_pane_item: Option<&dyn ItemHandle>,
_cx: &mut ViewContext<Self>,
) {
- // nothing to do.
}
}
@@ -4,20 +4,18 @@ use editor::{
self, find_boundary, find_preceding_boundary_display_point, FindRange, TextLayoutDetails,
},
scroll::Autoscroll,
- Anchor, Bias, DisplayPoint, RowExt, ToOffset,
+ Anchor, Bias, DisplayPoint, Editor, RowExt, ToOffset,
};
-use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
+use gpui::{actions, impl_actions, px, ViewContext};
use language::{char_kind, CharKind, Point, Selection, SelectionGoal};
use multi_buffer::MultiBufferRow;
use serde::Deserialize;
use std::ops::Range;
-use workspace::Workspace;
use crate::{
- normal::{mark, normal_motion},
+ normal::mark,
state::{Mode, Operator},
surrounds::SurroundsType,
- visual::visual_motion,
Vim,
};
@@ -248,214 +246,227 @@ actions!(
]
);
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
- workspace
- .register_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
- workspace.register_action(|_: &mut Workspace, action: &Down, cx: _| {
- motion(
+pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(editor, cx, |vim, _: &Left, cx| vim.motion(Motion::Left, cx));
+ Vim::action(editor, cx, |vim, _: &Backspace, cx| {
+ vim.motion(Motion::Backspace, cx)
+ });
+ Vim::action(editor, cx, |vim, action: &Down, cx| {
+ vim.motion(
Motion::Down {
display_lines: action.display_lines,
},
cx,
)
});
- workspace.register_action(|_: &mut Workspace, action: &Up, cx: _| {
- motion(
+ Vim::action(editor, cx, |vim, action: &Up, cx| {
+ vim.motion(
Motion::Up {
display_lines: action.display_lines,
},
cx,
)
});
- workspace.register_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
- workspace.register_action(|_: &mut Workspace, _: &Space, cx: _| motion(Motion::Space, cx));
- workspace.register_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {
- motion(
+ Vim::action(editor, cx, |vim, _: &Right, cx| {
+ vim.motion(Motion::Right, cx)
+ });
+ Vim::action(editor, cx, |vim, _: &Space, cx| {
+ vim.motion(Motion::Space, cx)
+ });
+ Vim::action(editor, cx, |vim, action: &FirstNonWhitespace, cx| {
+ vim.motion(
Motion::FirstNonWhitespace {
display_lines: action.display_lines,
},
cx,
)
});
- workspace.register_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {
- motion(
+ Vim::action(editor, cx, |vim, action: &StartOfLine, cx| {
+ vim.motion(
Motion::StartOfLine {
display_lines: action.display_lines,
},
cx,
)
});
- workspace.register_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {
- motion(
+ Vim::action(editor, cx, |vim, action: &EndOfLine, cx| {
+ vim.motion(
Motion::EndOfLine {
display_lines: action.display_lines,
},
cx,
)
});
- workspace.register_action(|_: &mut Workspace, _: &CurrentLine, cx: _| {
- motion(Motion::CurrentLine, cx)
+ Vim::action(editor, cx, |vim, _: &CurrentLine, cx| {
+ vim.motion(Motion::CurrentLine, cx)
});
- workspace.register_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
- motion(Motion::StartOfParagraph, cx)
+ Vim::action(editor, cx, |vim, _: &StartOfParagraph, cx| {
+ vim.motion(Motion::StartOfParagraph, cx)
});
- workspace.register_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| {
- motion(Motion::EndOfParagraph, cx)
+ Vim::action(editor, cx, |vim, _: &EndOfParagraph, cx| {
+ vim.motion(Motion::EndOfParagraph, cx)
});
- workspace.register_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| {
- motion(Motion::StartOfDocument, cx)
+ Vim::action(editor, cx, |vim, _: &StartOfDocument, cx| {
+ vim.motion(Motion::StartOfDocument, cx)
});
- workspace.register_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| {
- motion(Motion::EndOfDocument, cx)
+ Vim::action(editor, cx, |vim, _: &EndOfDocument, cx| {
+ vim.motion(Motion::EndOfDocument, cx)
+ });
+ Vim::action(editor, cx, |vim, _: &Matching, cx| {
+ vim.motion(Motion::Matching, cx)
});
- workspace
- .register_action(|_: &mut Workspace, _: &Matching, cx: _| motion(Motion::Matching, cx));
- workspace.register_action(
- |_: &mut Workspace, &NextWordStart { ignore_punctuation }: &NextWordStart, cx: _| {
- motion(Motion::NextWordStart { ignore_punctuation }, cx)
+ Vim::action(
+ editor,
+ cx,
+ |vim, &NextWordStart { ignore_punctuation }: &NextWordStart, cx| {
+ vim.motion(Motion::NextWordStart { ignore_punctuation }, cx)
},
);
- workspace.register_action(
- |_: &mut Workspace, &NextWordEnd { ignore_punctuation }: &NextWordEnd, cx: _| {
- motion(Motion::NextWordEnd { ignore_punctuation }, cx)
+ Vim::action(
+ editor,
+ cx,
+ |vim, &NextWordEnd { ignore_punctuation }: &NextWordEnd, cx| {
+ vim.motion(Motion::NextWordEnd { ignore_punctuation }, cx)
},
);
- workspace.register_action(
- |_: &mut Workspace,
- &PreviousWordStart { ignore_punctuation }: &PreviousWordStart,
- cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) },
+ Vim::action(
+ editor,
+ cx,
+ |vim, &PreviousWordStart { ignore_punctuation }: &PreviousWordStart, cx| {
+ vim.motion(Motion::PreviousWordStart { ignore_punctuation }, cx)
+ },
);
- workspace.register_action(
- |_: &mut Workspace, &PreviousWordEnd { ignore_punctuation }, cx: _| {
- motion(Motion::PreviousWordEnd { ignore_punctuation }, cx)
+ Vim::action(
+ editor,
+ cx,
+ |vim, &PreviousWordEnd { ignore_punctuation }, cx| {
+ vim.motion(Motion::PreviousWordEnd { ignore_punctuation }, cx)
},
);
- workspace.register_action(
- |_: &mut Workspace, &NextSubwordStart { ignore_punctuation }: &NextSubwordStart, cx: _| {
- motion(Motion::NextSubwordStart { ignore_punctuation }, cx)
+ Vim::action(
+ editor,
+ cx,
+ |vim, &NextSubwordStart { ignore_punctuation }: &NextSubwordStart, cx| {
+ vim.motion(Motion::NextSubwordStart { ignore_punctuation }, cx)
},
);
- workspace.register_action(
- |_: &mut Workspace, &NextSubwordEnd { ignore_punctuation }: &NextSubwordEnd, cx: _| {
- motion(Motion::NextSubwordEnd { ignore_punctuation }, cx)
+ Vim::action(
+ editor,
+ cx,
+ |vim, &NextSubwordEnd { ignore_punctuation }: &NextSubwordEnd, cx| {
+ vim.motion(Motion::NextSubwordEnd { ignore_punctuation }, cx)
},
);
- workspace.register_action(
- |_: &mut Workspace,
- &PreviousSubwordStart { ignore_punctuation }: &PreviousSubwordStart,
- cx: _| { motion(Motion::PreviousSubwordStart { ignore_punctuation }, cx) },
+ Vim::action(
+ editor,
+ cx,
+ |vim, &PreviousSubwordStart { ignore_punctuation }: &PreviousSubwordStart, cx| {
+ vim.motion(Motion::PreviousSubwordStart { ignore_punctuation }, cx)
+ },
);
- workspace.register_action(
- |_: &mut Workspace, &PreviousSubwordEnd { ignore_punctuation }, cx: _| {
- motion(Motion::PreviousSubwordEnd { ignore_punctuation }, cx)
+ Vim::action(
+ editor,
+ cx,
+ |vim, &PreviousSubwordEnd { ignore_punctuation }, cx| {
+ vim.motion(Motion::PreviousSubwordEnd { ignore_punctuation }, cx)
},
);
- workspace.register_action(|_: &mut Workspace, &NextLineStart, cx: _| {
- motion(Motion::NextLineStart, cx)
+ Vim::action(editor, cx, |vim, &NextLineStart, cx| {
+ vim.motion(Motion::NextLineStart, cx)
});
- workspace.register_action(|_: &mut Workspace, &PreviousLineStart, cx: _| {
- motion(Motion::PreviousLineStart, cx)
+ Vim::action(editor, cx, |vim, &PreviousLineStart, cx| {
+ vim.motion(Motion::PreviousLineStart, cx)
});
- workspace.register_action(|_: &mut Workspace, &StartOfLineDownward, cx: _| {
- motion(Motion::StartOfLineDownward, cx)
+ Vim::action(editor, cx, |vim, &StartOfLineDownward, cx| {
+ vim.motion(Motion::StartOfLineDownward, cx)
});
- workspace.register_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
- motion(Motion::EndOfLineDownward, cx)
+ Vim::action(editor, cx, |vim, &EndOfLineDownward, cx| {
+ vim.motion(Motion::EndOfLineDownward, cx)
});
- workspace
- .register_action(|_: &mut Workspace, &GoToColumn, cx: _| motion(Motion::GoToColumn, cx));
-
- workspace.register_action(|_: &mut Workspace, _: &RepeatFind, cx: _| {
- if let Some(last_find) = Vim::read(cx)
- .workspace_state
- .last_find
- .clone()
- .map(Box::new)
- {
- motion(Motion::RepeatFind { last_find }, cx);
+ Vim::action(editor, cx, |vim, &GoToColumn, cx| {
+ vim.motion(Motion::GoToColumn, cx)
+ });
+
+ Vim::action(editor, cx, |vim, _: &RepeatFind, cx| {
+ if let Some(last_find) = Vim::globals(cx).last_find.clone().map(Box::new) {
+ vim.motion(Motion::RepeatFind { last_find }, cx);
}
});
- workspace.register_action(|_: &mut Workspace, _: &RepeatFindReversed, cx: _| {
- if let Some(last_find) = Vim::read(cx)
- .workspace_state
- .last_find
- .clone()
- .map(Box::new)
- {
- motion(Motion::RepeatFindReversed { last_find }, cx);
+ Vim::action(editor, cx, |vim, _: &RepeatFindReversed, cx| {
+ if let Some(last_find) = Vim::globals(cx).last_find.clone().map(Box::new) {
+ vim.motion(Motion::RepeatFindReversed { last_find }, cx);
}
});
- workspace.register_action(|_: &mut Workspace, &WindowTop, cx: _| motion(Motion::WindowTop, cx));
- workspace.register_action(|_: &mut Workspace, &WindowMiddle, cx: _| {
- motion(Motion::WindowMiddle, cx)
+ Vim::action(editor, cx, |vim, &WindowTop, cx| {
+ vim.motion(Motion::WindowTop, cx)
});
- workspace.register_action(|_: &mut Workspace, &WindowBottom, cx: _| {
- motion(Motion::WindowBottom, cx)
+ Vim::action(editor, cx, |vim, &WindowMiddle, cx| {
+ vim.motion(Motion::WindowMiddle, cx)
+ });
+ Vim::action(editor, cx, |vim, &WindowBottom, cx| {
+ vim.motion(Motion::WindowBottom, cx)
});
}
-pub(crate) fn search_motion(m: Motion, cx: &mut WindowContext) {
- if let Motion::ZedSearchResult {
- prior_selections, ..
- } = &m
- {
- match Vim::read(cx).state().mode {
- Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
- if !prior_selections.is_empty() {
- Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |_, editor, cx| {
+impl Vim {
+ pub(crate) fn search_motion(&mut self, m: Motion, cx: &mut ViewContext<Self>) {
+ if let Motion::ZedSearchResult {
+ prior_selections, ..
+ } = &m
+ {
+ match self.mode {
+ Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
+ if !prior_selections.is_empty() {
+ self.update_editor(cx, |_, editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges(prior_selections.iter().cloned())
})
});
- });
+ }
}
- }
- Mode::Normal | Mode::Replace | Mode::Insert => {
- if Vim::read(cx).active_operator().is_none() {
- return;
+ Mode::Normal | Mode::Replace | Mode::Insert => {
+ if self.active_operator().is_none() {
+ return;
+ }
}
}
}
- }
- motion(m, cx)
-}
-
-pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
- if let Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) =
- Vim::read(cx).active_operator()
- {
- Vim::update(cx, |vim, cx| vim.pop_operator(cx));
+ self.motion(m, cx)
}
- let count = Vim::update(cx, |vim, cx| vim.take_count(cx));
- let active_operator = Vim::read(cx).active_operator();
- let mut waiting_operator: Option<Operator> = None;
- match Vim::read(cx).state().mode {
- Mode::Normal | Mode::Replace | Mode::Insert => {
- if active_operator == Some(Operator::AddSurrounds { target: None }) {
- waiting_operator = Some(Operator::AddSurrounds {
- target: Some(SurroundsType::Motion(motion)),
- });
- } else {
- normal_motion(motion.clone(), active_operator.clone(), count, cx)
- }
+ pub(crate) fn motion(&mut self, motion: Motion, cx: &mut ViewContext<Self>) {
+ if let Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) =
+ self.active_operator()
+ {
+ self.pop_operator(cx);
}
- Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
- visual_motion(motion.clone(), count, cx)
+
+ let count = self.take_count(cx);
+ let active_operator = self.active_operator();
+ let mut waiting_operator: Option<Operator> = None;
+ match self.mode {
+ Mode::Normal | Mode::Replace | Mode::Insert => {
+ if active_operator == Some(Operator::AddSurrounds { target: None }) {
+ waiting_operator = Some(Operator::AddSurrounds {
+ target: Some(SurroundsType::Motion(motion)),
+ });
+ } else {
+ self.normal_motion(motion.clone(), active_operator.clone(), count, cx)
+ }
+ }
+ Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
+ self.visual_motion(motion.clone(), count, cx)
+ }
}
- }
- Vim::update(cx, |vim, cx| {
- vim.clear_operator(cx);
+ self.clear_operator(cx);
if let Some(operator) = waiting_operator {
- vim.push_operator(operator, cx);
- vim.update_state(|state| state.pre_count = count)
+ self.push_operator(operator, cx);
+ self.pre_count = count
}
- });
+ }
}
// Motion handling is specified here:
@@ -19,30 +19,22 @@ use crate::{
motion::{self, first_non_whitespace, next_line_end, right, Motion},
object::Object,
state::{Mode, Operator},
- surrounds::{check_and_move_to_valid_bracket_pair, SurroundsType},
+ surrounds::SurroundsType,
Vim,
};
-use case::{change_case_motion, change_case_object, CaseTarget};
+use case::CaseTarget;
use collections::BTreeSet;
use editor::scroll::Autoscroll;
use editor::Anchor;
use editor::Bias;
use editor::Editor;
use editor::{display_map::ToDisplayPoint, movement};
-use gpui::{actions, ViewContext, WindowContext};
+use gpui::{actions, ViewContext};
use language::{Point, SelectionGoal};
use log::error;
use multi_buffer::MultiBufferRow;
-use workspace::Workspace;
-
-use self::{
- case::{change_case, convert_to_lower_case, convert_to_upper_case},
- change::{change_motion, change_object},
- delete::{delete_motion, delete_object},
- indent::{indent_motion, indent_object, IndentDirection},
- toggle_comments::{toggle_comments_motion, toggle_comments_object},
- yank::{yank_motion, yank_object},
-};
+
+use self::indent::IndentDirection;
actions!(
vim,
@@ -73,216 +65,195 @@ actions!(
]
);
-pub(crate) fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
- workspace.register_action(insert_after);
- workspace.register_action(insert_before);
- workspace.register_action(insert_first_non_whitespace);
- workspace.register_action(insert_end_of_line);
- workspace.register_action(insert_line_above);
- workspace.register_action(insert_line_below);
- workspace.register_action(insert_at_previous);
- workspace.register_action(change_case);
- workspace.register_action(convert_to_upper_case);
- workspace.register_action(convert_to_lower_case);
- workspace.register_action(yank_line);
- workspace.register_action(yank_to_end_of_line);
- workspace.register_action(toggle_comments);
-
- workspace.register_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let times = vim.take_count(cx);
- delete_motion(vim, Motion::Left, times, cx);
- })
+pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(editor, cx, Vim::insert_after);
+ Vim::action(editor, cx, Vim::insert_before);
+ Vim::action(editor, cx, Vim::insert_first_non_whitespace);
+ Vim::action(editor, cx, Vim::insert_end_of_line);
+ Vim::action(editor, cx, Vim::insert_line_above);
+ Vim::action(editor, cx, Vim::insert_line_below);
+ Vim::action(editor, cx, Vim::insert_at_previous);
+ Vim::action(editor, cx, Vim::change_case);
+ Vim::action(editor, cx, Vim::convert_to_upper_case);
+ Vim::action(editor, cx, Vim::convert_to_lower_case);
+ Vim::action(editor, cx, Vim::yank_line);
+ Vim::action(editor, cx, Vim::yank_to_end_of_line);
+ Vim::action(editor, cx, Vim::toggle_comments);
+ Vim::action(editor, cx, Vim::paste);
+
+ Vim::action(editor, cx, |vim, _: &DeleteLeft, cx| {
+ vim.record_current_action(cx);
+ let times = vim.take_count(cx);
+ vim.delete_motion(Motion::Left, times, cx);
});
- workspace.register_action(|_: &mut Workspace, _: &DeleteRight, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let times = vim.take_count(cx);
- delete_motion(vim, Motion::Right, times, cx);
- })
+ Vim::action(editor, cx, |vim, _: &DeleteRight, cx| {
+ vim.record_current_action(cx);
+ let times = vim.take_count(cx);
+ vim.delete_motion(Motion::Right, times, cx);
});
- workspace.register_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- let times = vim.take_count(cx);
- change_motion(
- vim,
- Motion::EndOfLine {
- display_lines: false,
- },
- times,
- cx,
- );
- })
+ Vim::action(editor, cx, |vim, _: &ChangeToEndOfLine, cx| {
+ vim.start_recording(cx);
+ let times = vim.take_count(cx);
+ vim.change_motion(
+ Motion::EndOfLine {
+ display_lines: false,
+ },
+ times,
+ cx,
+ );
});
- workspace.register_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let times = vim.take_count(cx);
- delete_motion(
- vim,
- Motion::EndOfLine {
- display_lines: false,
- },
- times,
- cx,
- );
- })
+ Vim::action(editor, cx, |vim, _: &DeleteToEndOfLine, cx| {
+ vim.record_current_action(cx);
+ let times = vim.take_count(cx);
+ vim.delete_motion(
+ Motion::EndOfLine {
+ display_lines: false,
+ },
+ times,
+ cx,
+ );
});
- workspace.register_action(|_: &mut Workspace, _: &JoinLines, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let mut times = vim.take_count(cx).unwrap_or(1);
- if vim.state().mode.is_visual() {
- times = 1;
- } else if times > 1 {
- // 2J joins two lines together (same as J or 1J)
- times -= 1;
- }
+ Vim::action(editor, cx, |vim, _: &JoinLines, cx| {
+ vim.record_current_action(cx);
+ let mut times = vim.take_count(cx).unwrap_or(1);
+ if vim.mode.is_visual() {
+ times = 1;
+ } else if times > 1 {
+ // 2J joins two lines together (same as J or 1J)
+ times -= 1;
+ }
- vim.update_active_editor(cx, |_, editor, cx| {
- editor.transact(cx, |editor, cx| {
- for _ in 0..times {
- editor.join_lines(&Default::default(), cx)
- }
- })
- });
- if vim.state().mode.is_visual() {
- vim.switch_mode(Mode::Normal, false, cx)
- }
+ vim.update_editor(cx, |_, editor, cx| {
+ editor.transact(cx, |editor, cx| {
+ for _ in 0..times {
+ editor.join_lines(&Default::default(), cx)
+ }
+ })
});
+ if vim.mode.is_visual() {
+ vim.switch_mode(Mode::Normal, false, cx)
+ }
});
- workspace.register_action(|_: &mut Workspace, _: &Indent, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let count = vim.take_count(cx).unwrap_or(1);
- vim.update_active_editor(cx, |_, editor, cx| {
- editor.transact(cx, |editor, cx| {
- let mut original_positions = save_selection_starts(editor, cx);
- for _ in 0..count {
- editor.indent(&Default::default(), cx);
- }
- restore_selection_cursors(editor, cx, &mut original_positions);
- });
+ Vim::action(editor, cx, |vim, _: &Indent, cx| {
+ vim.record_current_action(cx);
+ let count = vim.take_count(cx).unwrap_or(1);
+ vim.update_editor(cx, |_, editor, cx| {
+ editor.transact(cx, |editor, cx| {
+ let mut original_positions = save_selection_starts(editor, cx);
+ for _ in 0..count {
+ editor.indent(&Default::default(), cx);
+ }
+ restore_selection_cursors(editor, cx, &mut original_positions);
});
- if vim.state().mode.is_visual() {
- vim.switch_mode(Mode::Normal, false, cx)
- }
});
+ if vim.mode.is_visual() {
+ vim.switch_mode(Mode::Normal, false, cx)
+ }
});
- workspace.register_action(|_: &mut Workspace, _: &Outdent, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let count = vim.take_count(cx).unwrap_or(1);
- vim.update_active_editor(cx, |_, editor, cx| {
- editor.transact(cx, |editor, cx| {
- let mut original_positions = save_selection_starts(editor, cx);
- for _ in 0..count {
- editor.outdent(&Default::default(), cx);
- }
- restore_selection_cursors(editor, cx, &mut original_positions);
- });
+ Vim::action(editor, cx, |vim, _: &Outdent, cx| {
+ vim.record_current_action(cx);
+ let count = vim.take_count(cx).unwrap_or(1);
+ vim.update_editor(cx, |_, editor, cx| {
+ editor.transact(cx, |editor, cx| {
+ let mut original_positions = save_selection_starts(editor, cx);
+ for _ in 0..count {
+ editor.outdent(&Default::default(), cx);
+ }
+ restore_selection_cursors(editor, cx, &mut original_positions);
});
- if vim.state().mode.is_visual() {
- vim.switch_mode(Mode::Normal, false, cx)
- }
});
+ if vim.mode.is_visual() {
+ vim.switch_mode(Mode::Normal, false, cx)
+ }
});
- workspace.register_action(|_: &mut Workspace, _: &Undo, cx| {
- Vim::update(cx, |vim, cx| {
- let times = vim.take_count(cx);
- vim.update_active_editor(cx, |_, editor, cx| {
- for _ in 0..times.unwrap_or(1) {
- editor.undo(&editor::actions::Undo, cx);
- }
- });
- })
+ Vim::action(editor, cx, |vim, _: &Undo, cx| {
+ let times = vim.take_count(cx);
+ vim.update_editor(cx, |_, editor, cx| {
+ for _ in 0..times.unwrap_or(1) {
+ editor.undo(&editor::actions::Undo, cx);
+ }
+ });
});
- workspace.register_action(|_: &mut Workspace, _: &Redo, cx| {
- Vim::update(cx, |vim, cx| {
- let times = vim.take_count(cx);
- vim.update_active_editor(cx, |_, editor, cx| {
- for _ in 0..times.unwrap_or(1) {
- editor.redo(&editor::actions::Redo, cx);
- }
- });
- })
+ Vim::action(editor, cx, |vim, _: &Redo, cx| {
+ let times = vim.take_count(cx);
+ vim.update_editor(cx, |_, editor, cx| {
+ for _ in 0..times.unwrap_or(1) {
+ editor.redo(&editor::actions::Redo, cx);
+ }
+ });
});
- paste::register(workspace, cx);
- repeat::register(workspace, cx);
- scroll::register(workspace, cx);
- search::register(workspace, cx);
- substitute::register(workspace, cx);
- increment::register(workspace, cx);
+ repeat::register(editor, cx);
+ scroll::register(editor, cx);
+ search::register(editor, cx);
+ substitute::register(editor, cx);
+ increment::register(editor, cx);
}
-pub fn normal_motion(
- motion: Motion,
- operator: Option<Operator>,
- times: Option<usize>,
- cx: &mut WindowContext,
-) {
- Vim::update(cx, |vim, cx| {
+impl Vim {
+ pub fn normal_motion(
+ &mut self,
+ motion: Motion,
+ operator: Option<Operator>,
+ times: Option<usize>,
+ cx: &mut ViewContext<Self>,
+ ) {
match operator {
- None => move_cursor(vim, motion, times, cx),
- Some(Operator::Change) => change_motion(vim, motion, times, cx),
- Some(Operator::Delete) => delete_motion(vim, motion, times, cx),
- Some(Operator::Yank) => yank_motion(vim, motion, times, cx),
+ None => self.move_cursor(motion, times, cx),
+ Some(Operator::Change) => self.change_motion(motion, times, cx),
+ Some(Operator::Delete) => self.delete_motion(motion, times, cx),
+ Some(Operator::Yank) => self.yank_motion(motion, times, cx),
Some(Operator::AddSurrounds { target: None }) => {}
- Some(Operator::Indent) => indent_motion(vim, motion, times, IndentDirection::In, cx),
- Some(Operator::Outdent) => indent_motion(vim, motion, times, IndentDirection::Out, cx),
+ Some(Operator::Indent) => self.indent_motion(motion, times, IndentDirection::In, cx),
+ Some(Operator::Outdent) => self.indent_motion(motion, times, IndentDirection::Out, cx),
Some(Operator::Lowercase) => {
- change_case_motion(vim, motion, times, CaseTarget::Lowercase, cx)
+ self.change_case_motion(motion, times, CaseTarget::Lowercase, cx)
}
Some(Operator::Uppercase) => {
- change_case_motion(vim, motion, times, CaseTarget::Uppercase, cx)
+ self.change_case_motion(motion, times, CaseTarget::Uppercase, cx)
}
Some(Operator::OppositeCase) => {
- change_case_motion(vim, motion, times, CaseTarget::OppositeCase, cx)
+ self.change_case_motion(motion, times, CaseTarget::OppositeCase, cx)
}
- Some(Operator::ToggleComments) => toggle_comments_motion(vim, motion, times, cx),
+ Some(Operator::ToggleComments) => self.toggle_comments_motion(motion, times, cx),
Some(operator) => {
// Can't do anything for text objects, Ignoring
error!("Unexpected normal mode motion operator: {:?}", operator)
}
}
- });
-}
+ }
-pub fn normal_object(object: Object, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
+ pub fn normal_object(&mut self, object: Object, cx: &mut ViewContext<Self>) {
let mut waiting_operator: Option<Operator> = None;
- match vim.maybe_pop_operator() {
- Some(Operator::Object { around }) => match vim.maybe_pop_operator() {
- Some(Operator::Change) => change_object(vim, object, around, cx),
- Some(Operator::Delete) => delete_object(vim, object, around, cx),
- Some(Operator::Yank) => yank_object(vim, object, around, cx),
+ match self.maybe_pop_operator() {
+ Some(Operator::Object { around }) => match self.maybe_pop_operator() {
+ Some(Operator::Change) => self.change_object(object, around, cx),
+ Some(Operator::Delete) => self.delete_object(object, around, cx),
+ Some(Operator::Yank) => self.yank_object(object, around, cx),
Some(Operator::Indent) => {
- indent_object(vim, object, around, IndentDirection::In, cx)
+ self.indent_object(object, around, IndentDirection::In, cx)
}
Some(Operator::Outdent) => {
- indent_object(vim, object, around, IndentDirection::Out, cx)
+ self.indent_object(object, around, IndentDirection::Out, cx)
}
Some(Operator::Lowercase) => {
- change_case_object(vim, object, around, CaseTarget::Lowercase, cx)
+ self.change_case_object(object, around, CaseTarget::Lowercase, cx)
}
Some(Operator::Uppercase) => {
- change_case_object(vim, object, around, CaseTarget::Uppercase, cx)
+ self.change_case_object(object, around, CaseTarget::Uppercase, cx)
}
Some(Operator::OppositeCase) => {
- change_case_object(vim, object, around, CaseTarget::OppositeCase, cx)
+ self.change_case_object(object, around, CaseTarget::OppositeCase, cx)
}
Some(Operator::AddSurrounds { target: None }) => {
waiting_operator = Some(Operator::AddSurrounds {
target: Some(SurroundsType::Object(object)),
});
}
- Some(Operator::ToggleComments) => toggle_comments_object(vim, object, around, cx),
+ Some(Operator::ToggleComments) => self.toggle_comments_object(object, around, cx),
_ => {
// Can't do anything for namespace operators. Ignoring
}
@@ -291,7 +262,7 @@ pub fn normal_object(object: Object, cx: &mut WindowContext) {
waiting_operator = Some(Operator::DeleteSurrounds);
}
Some(Operator::ChangeSurrounds { target: None }) => {
- if check_and_move_to_valid_bracket_pair(vim, object, cx) {
+ if self.check_and_move_to_valid_bracket_pair(object, cx) {
waiting_operator = Some(Operator::ChangeSurrounds {
target: Some(object),
});
@@ -301,59 +272,53 @@ pub fn normal_object(object: Object, cx: &mut WindowContext) {
// Can't do anything with change/delete/yank/surrounds and text objects. Ignoring
}
}
- vim.clear_operator(cx);
+ self.clear_operator(cx);
if let Some(operator) = waiting_operator {
- vim.push_operator(operator, cx);
+ self.push_operator(operator, cx);
}
- });
-}
+ }
-pub(crate) fn move_cursor(
- vim: &mut Vim,
- motion: Motion,
- times: Option<usize>,
- cx: &mut WindowContext,
-) {
- vim.update_active_editor(cx, |_, editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_cursors_with(|map, cursor, goal| {
- motion
- .move_point(map, cursor, goal, times, &text_layout_details)
- .unwrap_or((cursor, goal))
+ pub(crate) fn move_cursor(
+ &mut self,
+ motion: Motion,
+ times: Option<usize>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.update_editor(cx, |_, editor, cx| {
+ let text_layout_details = editor.text_layout_details(cx);
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.move_cursors_with(|map, cursor, goal| {
+ motion
+ .move_point(map, cursor, goal, times, &text_layout_details)
+ .unwrap_or((cursor, goal))
+ })
})
- })
- });
-}
+ });
+ }
-fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- vim.update_active_editor(cx, |_, editor, cx| {
+ fn insert_after(&mut self, _: &InsertAfter, cx: &mut ViewContext<Self>) {
+ self.start_recording(cx);
+ self.switch_mode(Mode::Insert, false, cx);
+ self.update_editor(cx, |_, editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_cursors_with(|map, cursor, _| (right(map, cursor, 1), SelectionGoal::None));
});
});
- });
-}
+ }
-fn insert_before(_: &mut Workspace, _: &InsertBefore, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- });
-}
+ fn insert_before(&mut self, _: &InsertBefore, cx: &mut ViewContext<Self>) {
+ self.start_recording(cx);
+ self.switch_mode(Mode::Insert, false, cx);
+ }
-fn insert_first_non_whitespace(
- _: &mut Workspace,
- _: &InsertFirstNonWhitespace,
- cx: &mut ViewContext<Workspace>,
-) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- vim.update_active_editor(cx, |_, editor, cx| {
+ fn insert_first_non_whitespace(
+ &mut self,
+ _: &InsertFirstNonWhitespace,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.start_recording(cx);
+ self.switch_mode(Mode::Insert, false, cx);
+ self.update_editor(cx, |_, editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_cursors_with(|map, cursor, _| {
(
@@ -363,42 +328,36 @@ fn insert_first_non_whitespace(
});
});
});
- });
-}
+ }
-fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- vim.update_active_editor(cx, |_, editor, cx| {
+ fn insert_end_of_line(&mut self, _: &InsertEndOfLine, cx: &mut ViewContext<Self>) {
+ self.start_recording(cx);
+ self.switch_mode(Mode::Insert, false, cx);
+ self.update_editor(cx, |_, editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_cursors_with(|map, cursor, _| {
(next_line_end(map, cursor, 1), SelectionGoal::None)
});
});
});
- });
-}
+ }
-fn insert_at_previous(_: &mut Workspace, _: &InsertAtPrevious, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- vim.update_active_editor(cx, |vim, editor, cx| {
- if let Some(marks) = vim.state().marks.get("^") {
+ fn insert_at_previous(&mut self, _: &InsertAtPrevious, cx: &mut ViewContext<Self>) {
+ self.start_recording(cx);
+ self.switch_mode(Mode::Insert, false, cx);
+ self.update_editor(cx, |vim, editor, cx| {
+ if let Some(marks) = vim.marks.get("^") {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_anchor_ranges(marks.iter().map(|mark| *mark..*mark))
});
}
});
- });
-}
+ }
-fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- vim.update_active_editor(cx, |_, editor, cx| {
+ fn insert_line_above(&mut self, _: &InsertLineAbove, cx: &mut ViewContext<Self>) {
+ self.start_recording(cx);
+ self.switch_mode(Mode::Insert, false, cx);
+ self.update_editor(cx, |_, editor, cx| {
editor.transact(cx, |editor, cx| {
let selections = editor.selections.all::<Point>(cx);
let snapshot = editor.buffer().read(cx).snapshot(cx);
@@ -425,14 +384,12 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex
});
});
});
- });
-}
+ }
-fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- vim.update_active_editor(cx, |_, editor, cx| {
+ fn insert_line_below(&mut self, _: &InsertLineBelow, cx: &mut ViewContext<Self>) {
+ self.start_recording(cx);
+ self.switch_mode(Mode::Insert, false, cx);
+ self.update_editor(cx, |_, editor, cx| {
let text_layout_details = editor.text_layout_details(cx);
editor.transact(cx, |editor, cx| {
let selections = editor.selections.all::<Point>(cx);
@@ -464,79 +421,43 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex
editor.edit_with_autoindent(edits, cx);
});
});
- });
-}
+ }
-fn yank_line(_: &mut Workspace, _: &YankLine, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- let count = vim.take_count(cx);
- yank_motion(vim, motion::Motion::CurrentLine, count, cx)
- })
-}
+ fn yank_line(&mut self, _: &YankLine, cx: &mut ViewContext<Self>) {
+ let count = self.take_count(cx);
+ self.yank_motion(motion::Motion::CurrentLine, count, cx)
+ }
-fn yank_to_end_of_line(_: &mut Workspace, _: &YankToEndOfLine, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let count = vim.take_count(cx);
- yank_motion(
- vim,
+ fn yank_to_end_of_line(&mut self, _: &YankToEndOfLine, cx: &mut ViewContext<Self>) {
+ self.record_current_action(cx);
+ let count = self.take_count(cx);
+ self.yank_motion(
motion::Motion::EndOfLine {
display_lines: false,
},
count,
cx,
)
- })
-}
+ }
-fn toggle_comments(_: &mut Workspace, _: &ToggleComments, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- vim.update_active_editor(cx, |_, editor, cx| {
+ fn toggle_comments(&mut self, _: &ToggleComments, cx: &mut ViewContext<Self>) {
+ self.record_current_action(cx);
+ self.update_editor(cx, |_, editor, cx| {
editor.transact(cx, |editor, cx| {
let mut original_positions = save_selection_starts(editor, cx);
editor.toggle_comments(&Default::default(), cx);
restore_selection_cursors(editor, cx, &mut original_positions);
});
});
- if vim.state().mode.is_visual() {
- vim.switch_mode(Mode::Normal, false, cx)
+ if self.mode.is_visual() {
+ self.switch_mode(Mode::Normal, false, cx)
}
- });
-}
-
-fn save_selection_starts(editor: &Editor, cx: &mut ViewContext<Editor>) -> HashMap<usize, Anchor> {
- let (map, selections) = editor.selections.all_display(cx);
- selections
- .iter()
- .map(|selection| {
- (
- selection.id,
- map.display_point_to_anchor(selection.start, Bias::Right),
- )
- })
- .collect::<HashMap<_, _>>()
-}
-
-fn restore_selection_cursors(
- editor: &mut Editor,
- cx: &mut ViewContext<Editor>,
- positions: &mut HashMap<usize, Anchor>,
-) {
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- if let Some(anchor) = positions.remove(&selection.id) {
- selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
- }
- });
- });
-}
+ }
-pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- let count = vim.take_count(cx).unwrap_or(1);
- vim.stop_recording();
- vim.update_active_editor(cx, |_, editor, cx| {
+ pub(crate) fn normal_replace(&mut self, text: Arc<str>, cx: &mut ViewContext<Self>) {
+ let count = self.take_count(cx).unwrap_or(1);
+ self.stop_recording(cx);
+ self.update_editor(cx, |_, editor, cx| {
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
let (map, display_selections) = editor.selections.all_display(cx);
@@ -571,10 +492,36 @@ pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
});
});
});
- vim.pop_operator(cx)
- });
+ self.pop_operator(cx);
+ }
}
+fn save_selection_starts(editor: &Editor, cx: &mut ViewContext<Editor>) -> HashMap<usize, Anchor> {
+ let (map, selections) = editor.selections.all_display(cx);
+ selections
+ .iter()
+ .map(|selection| {
+ (
+ selection.id,
+ map.display_point_to_anchor(selection.start, Bias::Right),
+ )
+ })
+ .collect::<HashMap<_, _>>()
+}
+
+fn restore_selection_cursors(
+ editor: &mut Editor,
+ cx: &mut ViewContext<Editor>,
+ positions: &mut HashMap<usize, Anchor>,
+) {
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.move_with(|map, selection| {
+ if let Some(anchor) = positions.remove(&selection.id) {
+ selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ }
+ });
+ });
+}
#[cfg(test)]
mod test {
use gpui::{KeyBinding, TestAppContext};
@@ -3,8 +3,6 @@ use editor::{display_map::ToDisplayPoint, scroll::Autoscroll};
use gpui::ViewContext;
use language::{Bias, Point, SelectionGoal};
use multi_buffer::MultiBufferRow;
-use ui::WindowContext;
-use workspace::Workspace;
use crate::{
motion::Motion,
@@ -20,120 +18,112 @@ pub enum CaseTarget {
OppositeCase,
}
-pub fn change_case_motion(
- vim: &mut Vim,
- motion: Motion,
- times: Option<usize>,
- mode: CaseTarget,
- cx: &mut WindowContext,
-) {
- vim.stop_recording();
- vim.update_active_editor(cx, |_, editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.transact(cx, |editor, cx| {
- let mut selection_starts: HashMap<_, _> = Default::default();
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let anchor = map.display_point_to_anchor(selection.head(), Bias::Left);
- selection_starts.insert(selection.id, anchor);
- motion.expand_selection(map, selection, times, false, &text_layout_details);
+impl Vim {
+ pub fn change_case_motion(
+ &mut self,
+ motion: Motion,
+ times: Option<usize>,
+ mode: CaseTarget,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.stop_recording(cx);
+ self.update_editor(cx, |_, editor, cx| {
+ let text_layout_details = editor.text_layout_details(cx);
+ editor.transact(cx, |editor, cx| {
+ let mut selection_starts: HashMap<_, _> = Default::default();
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let anchor = map.display_point_to_anchor(selection.head(), Bias::Left);
+ selection_starts.insert(selection.id, anchor);
+ motion.expand_selection(map, selection, times, false, &text_layout_details);
+ });
});
- });
- match mode {
- CaseTarget::Lowercase => editor.convert_to_lower_case(&Default::default(), cx),
- CaseTarget::Uppercase => editor.convert_to_upper_case(&Default::default(), cx),
- CaseTarget::OppositeCase => {
- editor.convert_to_opposite_case(&Default::default(), cx)
+ match mode {
+ CaseTarget::Lowercase => editor.convert_to_lower_case(&Default::default(), cx),
+ CaseTarget::Uppercase => editor.convert_to_upper_case(&Default::default(), cx),
+ CaseTarget::OppositeCase => {
+ editor.convert_to_opposite_case(&Default::default(), cx)
+ }
}
- }
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let anchor = selection_starts.remove(&selection.id).unwrap();
- selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let anchor = selection_starts.remove(&selection.id).unwrap();
+ selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ });
});
});
});
- });
-}
+ }
-pub fn change_case_object(
- vim: &mut Vim,
- object: Object,
- around: bool,
- mode: CaseTarget,
- cx: &mut WindowContext,
-) {
- vim.stop_recording();
- vim.update_active_editor(cx, |_, editor, cx| {
- editor.transact(cx, |editor, cx| {
- let mut original_positions: HashMap<_, _> = Default::default();
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- object.expand_selection(map, selection, around);
- original_positions.insert(
- selection.id,
- map.display_point_to_anchor(selection.start, Bias::Left),
- );
+ pub fn change_case_object(
+ &mut self,
+ object: Object,
+ around: bool,
+ mode: CaseTarget,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.stop_recording(cx);
+ self.update_editor(cx, |_, editor, cx| {
+ editor.transact(cx, |editor, cx| {
+ let mut original_positions: HashMap<_, _> = Default::default();
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ object.expand_selection(map, selection, around);
+ original_positions.insert(
+ selection.id,
+ map.display_point_to_anchor(selection.start, Bias::Left),
+ );
+ });
});
- });
- match mode {
- CaseTarget::Lowercase => editor.convert_to_lower_case(&Default::default(), cx),
- CaseTarget::Uppercase => editor.convert_to_upper_case(&Default::default(), cx),
- CaseTarget::OppositeCase => {
- editor.convert_to_opposite_case(&Default::default(), cx)
+ match mode {
+ CaseTarget::Lowercase => editor.convert_to_lower_case(&Default::default(), cx),
+ CaseTarget::Uppercase => editor.convert_to_upper_case(&Default::default(), cx),
+ CaseTarget::OppositeCase => {
+ editor.convert_to_opposite_case(&Default::default(), cx)
+ }
}
- }
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let anchor = original_positions.remove(&selection.id).unwrap();
- selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let anchor = original_positions.remove(&selection.id).unwrap();
+ selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ });
});
});
});
- });
-}
+ }
-pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext<Workspace>) {
- manipulate_text(cx, |c| {
- if c.is_lowercase() {
- c.to_uppercase().collect::<Vec<char>>()
- } else {
- c.to_lowercase().collect::<Vec<char>>()
- }
- })
-}
+ pub fn change_case(&mut self, _: &ChangeCase, cx: &mut ViewContext<Self>) {
+ self.manipulate_text(cx, |c| {
+ if c.is_lowercase() {
+ c.to_uppercase().collect::<Vec<char>>()
+ } else {
+ c.to_lowercase().collect::<Vec<char>>()
+ }
+ })
+ }
-pub fn convert_to_upper_case(
- _: &mut Workspace,
- _: &ConvertToUpperCase,
- cx: &mut ViewContext<Workspace>,
-) {
- manipulate_text(cx, |c| c.to_uppercase().collect::<Vec<char>>())
-}
+ pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext<Self>) {
+ self.manipulate_text(cx, |c| c.to_uppercase().collect::<Vec<char>>())
+ }
-pub fn convert_to_lower_case(
- _: &mut Workspace,
- _: &ConvertToLowerCase,
- cx: &mut ViewContext<Workspace>,
-) {
- manipulate_text(cx, |c| c.to_lowercase().collect::<Vec<char>>())
-}
+ pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
+ self.manipulate_text(cx, |c| c.to_lowercase().collect::<Vec<char>>())
+ }
-fn manipulate_text<F>(cx: &mut ViewContext<Workspace>, transform: F)
-where
- F: Fn(char) -> Vec<char> + Copy,
-{
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- vim.store_visual_marks(cx);
- let count = vim.take_count(cx).unwrap_or(1) as u32;
+ fn manipulate_text<F>(&mut self, cx: &mut ViewContext<Self>, transform: F)
+ where
+ F: Fn(char) -> Vec<char> + Copy,
+ {
+ self.record_current_action(cx);
+ self.store_visual_marks(cx);
+ let count = self.take_count(cx).unwrap_or(1) as u32;
- vim.update_active_editor(cx, |vim, editor, cx| {
+ self.update_editor(cx, |vim, editor, cx| {
let mut ranges = Vec::new();
let mut cursor_positions = Vec::new();
let snapshot = editor.buffer().read(cx).snapshot(cx);
for selection in editor.selections.all::<Point>(cx) {
- match vim.state().mode {
+ match vim.mode {
Mode::VisualLine => {
let start = Point::new(selection.start.row, 0);
let end = Point::new(
@@ -186,8 +176,8 @@ where
})
});
});
- vim.switch_mode(Mode::Normal, true, cx)
- })
+ self.switch_mode(Mode::Normal, true, cx)
+ }
}
#[cfg(test)]
@@ -1,6 +1,5 @@
use crate::{
motion::{self, Motion},
- normal::yank::copy_selections_content,
object::Object,
state::Mode,
Vim,
@@ -11,98 +10,108 @@ use editor::{
scroll::Autoscroll,
Bias, DisplayPoint,
};
-use gpui::WindowContext;
use language::{char_kind, CharKind, Selection};
-
-pub fn change_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
- // Some motions ignore failure when switching to normal mode
- let mut motion_succeeded = matches!(
- motion,
- Motion::Left
- | Motion::Right
- | Motion::EndOfLine { .. }
- | Motion::Backspace
- | Motion::StartOfLine { .. }
- );
- vim.update_active_editor(cx, |vim, editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.transact(cx, |editor, cx| {
- // We are swapping to insert mode anyway. Just set the line end clipping behavior now
- editor.set_clip_at_line_ends(false, cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- motion_succeeded |= match motion {
- Motion::NextWordStart { ignore_punctuation }
- | Motion::NextSubwordStart { ignore_punctuation } => {
- expand_changed_word_selection(
- map,
- selection,
- times,
- ignore_punctuation,
- &text_layout_details,
- motion == Motion::NextSubwordStart { ignore_punctuation },
- )
- }
- _ => {
- let result = motion.expand_selection(
- map,
- selection,
- times,
- false,
- &text_layout_details,
- );
- if let Motion::CurrentLine = motion {
- let mut start_offset = selection.start.to_offset(map, Bias::Left);
- let scope = map
- .buffer_snapshot
- .language_scope_at(selection.start.to_point(&map));
- for (ch, offset) in map.buffer_chars_at(start_offset) {
- if ch == '\n' || char_kind(&scope, ch) != CharKind::Whitespace {
- break;
+use ui::ViewContext;
+
+impl Vim {
+ pub fn change_motion(
+ &mut self,
+ motion: Motion,
+ times: Option<usize>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ // Some motions ignore failure when switching to normal mode
+ let mut motion_succeeded = matches!(
+ motion,
+ Motion::Left
+ | Motion::Right
+ | Motion::EndOfLine { .. }
+ | Motion::Backspace
+ | Motion::StartOfLine { .. }
+ );
+ self.update_editor(cx, |vim, editor, cx| {
+ let text_layout_details = editor.text_layout_details(cx);
+ editor.transact(cx, |editor, cx| {
+ // We are swapping to insert mode anyway. Just set the line end clipping behavior now
+ editor.set_clip_at_line_ends(false, cx);
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.move_with(|map, selection| {
+ motion_succeeded |= match motion {
+ Motion::NextWordStart { ignore_punctuation }
+ | Motion::NextSubwordStart { ignore_punctuation } => {
+ expand_changed_word_selection(
+ map,
+ selection,
+ times,
+ ignore_punctuation,
+ &text_layout_details,
+ motion == Motion::NextSubwordStart { ignore_punctuation },
+ )
+ }
+ _ => {
+ let result = motion.expand_selection(
+ map,
+ selection,
+ times,
+ false,
+ &text_layout_details,
+ );
+ if let Motion::CurrentLine = motion {
+ let mut start_offset =
+ selection.start.to_offset(map, Bias::Left);
+ let scope = map
+ .buffer_snapshot
+ .language_scope_at(selection.start.to_point(&map));
+ for (ch, offset) in map.buffer_chars_at(start_offset) {
+ if ch == '\n'
+ || char_kind(&scope, ch) != CharKind::Whitespace
+ {
+ break;
+ }
+ start_offset = offset + ch.len_utf8();
}
- start_offset = offset + ch.len_utf8();
+ selection.start = start_offset.to_display_point(map);
}
- selection.start = start_offset.to_display_point(map);
+ result
}
- result
}
- }
+ });
});
+ vim.copy_selections_content(editor, motion.linewise(), cx);
+ editor.insert("", cx);
});
- copy_selections_content(vim, editor, motion.linewise(), cx);
- editor.insert("", cx);
});
- });
- if motion_succeeded {
- vim.switch_mode(Mode::Insert, false, cx)
- } else {
- vim.switch_mode(Mode::Normal, false, cx)
+ if motion_succeeded {
+ self.switch_mode(Mode::Insert, false, cx)
+ } else {
+ self.switch_mode(Mode::Normal, false, cx)
+ }
}
-}
-pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
- let mut objects_found = false;
- vim.update_active_editor(cx, |vim, editor, cx| {
- // We are swapping to insert mode anyway. Just set the line end clipping behavior now
- editor.set_clip_at_line_ends(false, cx);
- editor.transact(cx, |editor, cx| {
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- objects_found |= object.expand_selection(map, selection, around);
+ pub fn change_object(&mut self, object: Object, around: bool, cx: &mut ViewContext<Self>) {
+ let mut objects_found = false;
+ self.update_editor(cx, |vim, editor, cx| {
+ // We are swapping to insert mode anyway. Just set the line end clipping behavior now
+ editor.set_clip_at_line_ends(false, cx);
+ editor.transact(cx, |editor, cx| {
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.move_with(|map, selection| {
+ objects_found |= object.expand_selection(map, selection, around);
+ });
});
+ if objects_found {
+ vim.copy_selections_content(editor, false, cx);
+ editor.insert("", cx);
+ }
});
- if objects_found {
- copy_selections_content(vim, editor, false, cx);
- editor.insert("", cx);
- }
});
- });
- if objects_found {
- vim.switch_mode(Mode::Insert, false, cx);
- } else {
- vim.switch_mode(Mode::Normal, false, cx);
+ if objects_found {
+ self.switch_mode(Mode::Insert, false, cx);
+ } else {
+ self.switch_mode(Mode::Normal, false, cx);
+ }
}
}
@@ -1,146 +1,154 @@
-use crate::{motion::Motion, normal::yank::copy_selections_content, object::Object, Vim};
+use crate::{motion::Motion, object::Object, Vim};
use collections::{HashMap, HashSet};
use editor::{
display_map::{DisplaySnapshot, ToDisplayPoint},
scroll::Autoscroll,
Bias, DisplayPoint,
};
-use gpui::WindowContext;
use language::{Point, Selection};
use multi_buffer::MultiBufferRow;
-
-pub fn delete_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
- vim.stop_recording();
- vim.update_active_editor(cx, |vim, editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.transact(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- let mut original_columns: HashMap<_, _> = Default::default();
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- let original_head = selection.head();
- original_columns.insert(selection.id, original_head.column());
- motion.expand_selection(map, selection, times, true, &text_layout_details);
-
- // Motion::NextWordStart on an empty line should delete it.
- if let Motion::NextWordStart {
- ignore_punctuation: _,
- } = motion
- {
- if selection.is_empty()
- && map
- .buffer_snapshot
- .line_len(MultiBufferRow(selection.start.to_point(&map).row))
- == 0
+use ui::ViewContext;
+
+impl Vim {
+ pub fn delete_motion(
+ &mut self,
+ motion: Motion,
+ times: Option<usize>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.stop_recording(cx);
+ self.update_editor(cx, |vim, editor, cx| {
+ let text_layout_details = editor.text_layout_details(cx);
+ editor.transact(cx, |editor, cx| {
+ editor.set_clip_at_line_ends(false, cx);
+ let mut original_columns: HashMap<_, _> = Default::default();
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.move_with(|map, selection| {
+ let original_head = selection.head();
+ original_columns.insert(selection.id, original_head.column());
+ motion.expand_selection(map, selection, times, true, &text_layout_details);
+
+ // Motion::NextWordStart on an empty line should delete it.
+ if let Motion::NextWordStart {
+ ignore_punctuation: _,
+ } = motion
{
- selection.end = map
- .buffer_snapshot
- .clip_point(
- Point::new(selection.start.to_point(&map).row + 1, 0),
- Bias::Left,
- )
- .to_display_point(map)
+ if selection.is_empty()
+ && map
+ .buffer_snapshot
+ .line_len(MultiBufferRow(selection.start.to_point(&map).row))
+ == 0
+ {
+ selection.end = map
+ .buffer_snapshot
+ .clip_point(
+ Point::new(selection.start.to_point(&map).row + 1, 0),
+ Bias::Left,
+ )
+ .to_display_point(map)
+ }
}
- }
+ });
});
- });
- copy_selections_content(vim, editor, motion.linewise(), cx);
- editor.insert("", cx);
-
- // Fixup cursor position after the deletion
- editor.set_clip_at_line_ends(true, cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- let mut cursor = selection.head();
- if motion.linewise() {
- if let Some(column) = original_columns.get(&selection.id) {
- *cursor.column_mut() = *column
+ vim.copy_selections_content(editor, motion.linewise(), cx);
+ editor.insert("", cx);
+
+ // Fixup cursor position after the deletion
+ editor.set_clip_at_line_ends(true, cx);
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.move_with(|map, selection| {
+ let mut cursor = selection.head();
+ if motion.linewise() {
+ if let Some(column) = original_columns.get(&selection.id) {
+ *cursor.column_mut() = *column
+ }
}
- }
- cursor = map.clip_point(cursor, Bias::Left);
- selection.collapse_to(cursor, selection.goal)
+ cursor = map.clip_point(cursor, Bias::Left);
+ selection.collapse_to(cursor, selection.goal)
+ });
});
});
});
- });
-}
+ }
-pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
- vim.stop_recording();
- vim.update_active_editor(cx, |vim, editor, cx| {
- editor.transact(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- // Emulates behavior in vim where if we expanded backwards to include a newline
- // the cursor gets set back to the start of the line
- let mut should_move_to_start: HashSet<_> = Default::default();
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- object.expand_selection(map, selection, around);
- let offset_range = selection.map(|p| p.to_offset(map, Bias::Left)).range();
- let mut move_selection_start_to_previous_line =
- |map: &DisplaySnapshot, selection: &mut Selection<DisplayPoint>| {
- let start = selection.start.to_offset(map, Bias::Left);
- if selection.start.row().0 > 0 {
- should_move_to_start.insert(selection.id);
- selection.start = (start - '\n'.len_utf8()).to_display_point(map);
+ pub fn delete_object(&mut self, object: Object, around: bool, cx: &mut ViewContext<Self>) {
+ self.stop_recording(cx);
+ self.update_editor(cx, |vim, editor, cx| {
+ editor.transact(cx, |editor, cx| {
+ editor.set_clip_at_line_ends(false, cx);
+ // Emulates behavior in vim where if we expanded backwards to include a newline
+ // the cursor gets set back to the start of the line
+ let mut should_move_to_start: HashSet<_> = Default::default();
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.move_with(|map, selection| {
+ object.expand_selection(map, selection, around);
+ let offset_range = selection.map(|p| p.to_offset(map, Bias::Left)).range();
+ let mut move_selection_start_to_previous_line =
+ |map: &DisplaySnapshot, selection: &mut Selection<DisplayPoint>| {
+ let start = selection.start.to_offset(map, Bias::Left);
+ if selection.start.row().0 > 0 {
+ should_move_to_start.insert(selection.id);
+ selection.start =
+ (start - '\n'.len_utf8()).to_display_point(map);
+ }
+ };
+ let range = selection.start.to_offset(map, Bias::Left)
+ ..selection.end.to_offset(map, Bias::Right);
+ let contains_only_newlines = map
+ .buffer_chars_at(range.start)
+ .take_while(|(_, p)| p < &range.end)
+ .all(|(char, _)| char == '\n')
+ && !offset_range.is_empty();
+ let end_at_newline = map
+ .buffer_chars_at(range.end)
+ .next()
+ .map(|(c, _)| c == '\n')
+ .unwrap_or(false);
+
+ // If expanded range contains only newlines and
+ // the object is around or sentence, expand to include a newline
+ // at the end or start
+ if (around || object == Object::Sentence) && contains_only_newlines {
+ if end_at_newline {
+ move_selection_end_to_next_line(map, selection);
+ } else {
+ move_selection_start_to_previous_line(map, selection);
}
- };
- let range = selection.start.to_offset(map, Bias::Left)
- ..selection.end.to_offset(map, Bias::Right);
- let contains_only_newlines = map
- .buffer_chars_at(range.start)
- .take_while(|(_, p)| p < &range.end)
- .all(|(char, _)| char == '\n')
- && !offset_range.is_empty();
- let end_at_newline = map
- .buffer_chars_at(range.end)
- .next()
- .map(|(c, _)| c == '\n')
- .unwrap_or(false);
-
- // If expanded range contains only newlines and
- // the object is around or sentence, expand to include a newline
- // at the end or start
- if (around || object == Object::Sentence) && contains_only_newlines {
- if end_at_newline {
- move_selection_end_to_next_line(map, selection);
- } else {
- move_selection_start_to_previous_line(map, selection);
- }
- }
-
- // Does post-processing for the trailing newline and EOF
- // when not cancelled.
- let cancelled = around && selection.start == selection.end;
- if object == Object::Paragraph && !cancelled {
- // EOF check should be done before including a trailing newline.
- if ends_at_eof(map, selection) {
- move_selection_start_to_previous_line(map, selection);
}
- if end_at_newline {
- move_selection_end_to_next_line(map, selection);
+ // Does post-processing for the trailing newline and EOF
+ // when not cancelled.
+ let cancelled = around && selection.start == selection.end;
+ if object == Object::Paragraph && !cancelled {
+ // EOF check should be done before including a trailing newline.
+ if ends_at_eof(map, selection) {
+ move_selection_start_to_previous_line(map, selection);
+ }
+
+ if end_at_newline {
+ move_selection_end_to_next_line(map, selection);
+ }
}
- }
+ });
});
- });
- copy_selections_content(vim, editor, false, cx);
- editor.insert("", cx);
-
- // Fixup cursor position after the deletion
- editor.set_clip_at_line_ends(true, cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- let mut cursor = selection.head();
- if should_move_to_start.contains(&selection.id) {
- *cursor.column_mut() = 0;
- }
- cursor = map.clip_point(cursor, Bias::Left);
- selection.collapse_to(cursor, selection.goal)
+ vim.copy_selections_content(editor, false, cx);
+ editor.insert("", cx);
+
+ // Fixup cursor position after the deletion
+ editor.set_clip_at_line_ends(true, cx);
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.move_with(|map, selection| {
+ let mut cursor = selection.head();
+ if should_move_to_start.contains(&selection.id) {
+ *cursor.column_mut() = 0;
+ }
+ cursor = map.clip_point(cursor, Bias::Left);
+ selection.collapse_to(cursor, selection.goal)
+ });
});
});
});
- });
+ }
}
fn move_selection_end_to_next_line(map: &DisplaySnapshot, selection: &mut Selection<DisplayPoint>) {
@@ -1,10 +1,9 @@
use std::ops::Range;
-use editor::{scroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint};
-use gpui::{impl_actions, ViewContext, WindowContext};
+use editor::{scroll::Autoscroll, Editor, MultiBufferSnapshot, ToOffset, ToPoint};
+use gpui::{impl_actions, ViewContext};
use language::{Bias, Point};
use serde::Deserialize;
-use workspace::Workspace;
use crate::{state::Mode, Vim};
@@ -24,92 +23,90 @@ struct Decrement {
impl_actions!(vim, [Increment, Decrement]);
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, action: &Increment, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let count = vim.take_count(cx).unwrap_or(1);
- let step = if action.step { 1 } else { 0 };
- increment(vim, count as i32, step, cx)
- })
+pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(editor, cx, |vim, action: &Increment, cx| {
+ vim.record_current_action(cx);
+ let count = vim.take_count(cx).unwrap_or(1);
+ let step = if action.step { 1 } else { 0 };
+ vim.increment(count as i32, step, cx)
});
- workspace.register_action(|_: &mut Workspace, action: &Decrement, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let count = vim.take_count(cx).unwrap_or(1);
- let step = if action.step { -1 } else { 0 };
- increment(vim, count as i32 * -1, step, cx)
- })
+ Vim::action(editor, cx, |vim, action: &Decrement, cx| {
+ vim.record_current_action(cx);
+ let count = vim.take_count(cx).unwrap_or(1);
+ let step = if action.step { -1 } else { 0 };
+ vim.increment(count as i32 * -1, step, cx)
});
}
-fn increment(vim: &mut Vim, mut delta: i32, step: i32, cx: &mut WindowContext) {
- vim.store_visual_marks(cx);
- vim.update_active_editor(cx, |vim, editor, cx| {
- let mut edits = Vec::new();
- let mut new_anchors = Vec::new();
-
- let snapshot = editor.buffer().read(cx).snapshot(cx);
- for selection in editor.selections.all_adjusted(cx) {
- if !selection.is_empty() {
- if vim.state().mode != Mode::VisualBlock || new_anchors.is_empty() {
- new_anchors.push((true, snapshot.anchor_before(selection.start)))
+impl Vim {
+ fn increment(&mut self, mut delta: i32, step: i32, cx: &mut ViewContext<Self>) {
+ self.store_visual_marks(cx);
+ self.update_editor(cx, |vim, editor, cx| {
+ let mut edits = Vec::new();
+ let mut new_anchors = Vec::new();
+
+ let snapshot = editor.buffer().read(cx).snapshot(cx);
+ for selection in editor.selections.all_adjusted(cx) {
+ if !selection.is_empty() {
+ if vim.mode != Mode::VisualBlock || new_anchors.is_empty() {
+ new_anchors.push((true, snapshot.anchor_before(selection.start)))
+ }
}
- }
- for row in selection.start.row..=selection.end.row {
- let start = if row == selection.start.row {
- selection.start
- } else {
- Point::new(row, 0)
- };
-
- if let Some((range, num, radix)) = find_number(&snapshot, start) {
- if let Ok(val) = i32::from_str_radix(&num, radix) {
- let result = val + delta;
- delta += step;
- let replace = match radix {
- 10 => format!("{}", result),
- 16 => {
- if num.to_ascii_lowercase() == num {
- format!("{:x}", result)
- } else {
- format!("{:X}", result)
+ for row in selection.start.row..=selection.end.row {
+ let start = if row == selection.start.row {
+ selection.start
+ } else {
+ Point::new(row, 0)
+ };
+
+ if let Some((range, num, radix)) = find_number(&snapshot, start) {
+ if let Ok(val) = i32::from_str_radix(&num, radix) {
+ let result = val + delta;
+ delta += step;
+ let replace = match radix {
+ 10 => format!("{}", result),
+ 16 => {
+ if num.to_ascii_lowercase() == num {
+ format!("{:x}", result)
+ } else {
+ format!("{:X}", result)
+ }
}
- }
- 2 => format!("{:b}", result),
- _ => unreachable!(),
- };
- edits.push((range.clone(), replace));
- }
- if selection.is_empty() {
- new_anchors.push((false, snapshot.anchor_after(range.end)))
- }
- } else {
- if selection.is_empty() {
- new_anchors.push((true, snapshot.anchor_after(start)))
+ 2 => format!("{:b}", result),
+ _ => unreachable!(),
+ };
+ edits.push((range.clone(), replace));
+ }
+ if selection.is_empty() {
+ new_anchors.push((false, snapshot.anchor_after(range.end)))
+ }
+ } else {
+ if selection.is_empty() {
+ new_anchors.push((true, snapshot.anchor_after(start)))
+ }
}
}
}
- }
- editor.transact(cx, |editor, cx| {
- editor.edit(edits, cx);
-
- let snapshot = editor.buffer().read(cx).snapshot(cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- let mut new_ranges = Vec::new();
- for (visual, anchor) in new_anchors.iter() {
- let mut point = anchor.to_point(&snapshot);
- if !*visual && point.column > 0 {
- point.column -= 1;
- point = snapshot.clip_point(point, Bias::Left)
+ editor.transact(cx, |editor, cx| {
+ editor.edit(edits, cx);
+
+ let snapshot = editor.buffer().read(cx).snapshot(cx);
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ let mut new_ranges = Vec::new();
+ for (visual, anchor) in new_anchors.iter() {
+ let mut point = anchor.to_point(&snapshot);
+ if !*visual && point.column > 0 {
+ point.column -= 1;
+ point = snapshot.clip_point(point, Bias::Left)
+ }
+ new_ranges.push(point..point);
}
- new_ranges.push(point..point);
- }
- s.select_ranges(new_ranges)
- })
+ s.select_ranges(new_ranges)
+ })
+ });
});
- });
- vim.switch_mode(Mode::Normal, true, cx)
+ self.switch_mode(Mode::Normal, true, cx)
+ }
}
fn find_number(
@@ -1,78 +1,80 @@
use crate::{motion::Motion, object::Object, Vim};
use collections::HashMap;
use editor::{display_map::ToDisplayPoint, Bias};
-use gpui::WindowContext;
use language::SelectionGoal;
+use ui::ViewContext;
#[derive(PartialEq, Eq)]
-pub(super) enum IndentDirection {
+pub(crate) enum IndentDirection {
In,
Out,
}
-pub fn indent_motion(
- vim: &mut Vim,
- motion: Motion,
- times: Option<usize>,
- dir: IndentDirection,
- cx: &mut WindowContext,
-) {
- vim.stop_recording();
- vim.update_active_editor(cx, |_, editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.transact(cx, |editor, cx| {
- let mut selection_starts: HashMap<_, _> = Default::default();
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
- selection_starts.insert(selection.id, anchor);
- motion.expand_selection(map, selection, times, false, &text_layout_details);
+impl Vim {
+ pub(crate) fn indent_motion(
+ &mut self,
+ motion: Motion,
+ times: Option<usize>,
+ dir: IndentDirection,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.stop_recording(cx);
+ self.update_editor(cx, |_, editor, cx| {
+ let text_layout_details = editor.text_layout_details(cx);
+ editor.transact(cx, |editor, cx| {
+ let mut selection_starts: HashMap<_, _> = Default::default();
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
+ selection_starts.insert(selection.id, anchor);
+ motion.expand_selection(map, selection, times, false, &text_layout_details);
+ });
});
- });
- if dir == IndentDirection::In {
- editor.indent(&Default::default(), cx);
- } else {
- editor.outdent(&Default::default(), cx);
- }
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let anchor = selection_starts.remove(&selection.id).unwrap();
- selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ if dir == IndentDirection::In {
+ editor.indent(&Default::default(), cx);
+ } else {
+ editor.outdent(&Default::default(), cx);
+ }
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let anchor = selection_starts.remove(&selection.id).unwrap();
+ selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ });
});
});
});
- });
-}
+ }
-pub fn indent_object(
- vim: &mut Vim,
- object: Object,
- around: bool,
- dir: IndentDirection,
- cx: &mut WindowContext,
-) {
- vim.stop_recording();
- vim.update_active_editor(cx, |_, editor, cx| {
- editor.transact(cx, |editor, cx| {
- let mut original_positions: HashMap<_, _> = Default::default();
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
- original_positions.insert(selection.id, anchor);
- object.expand_selection(map, selection, around);
+ pub(crate) fn indent_object(
+ &mut self,
+ object: Object,
+ around: bool,
+ dir: IndentDirection,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.stop_recording(cx);
+ self.update_editor(cx, |_, editor, cx| {
+ editor.transact(cx, |editor, cx| {
+ let mut original_positions: HashMap<_, _> = Default::default();
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
+ original_positions.insert(selection.id, anchor);
+ object.expand_selection(map, selection, around);
+ });
});
- });
- if dir == IndentDirection::In {
- editor.indent(&Default::default(), cx);
- } else {
- editor.outdent(&Default::default(), cx);
- }
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let anchor = original_positions.remove(&selection.id).unwrap();
- selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ if dir == IndentDirection::In {
+ editor.indent(&Default::default(), cx);
+ } else {
+ editor.outdent(&Default::default(), cx);
+ }
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let anchor = original_positions.remove(&selection.id).unwrap();
+ selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ });
});
});
});
- });
+ }
}
@@ -6,7 +6,7 @@ use editor::{
scroll::Autoscroll,
Anchor, Bias, DisplayPoint,
};
-use gpui::WindowContext;
+use gpui::ViewContext;
use language::SelectionGoal;
use crate::{
@@ -15,56 +15,62 @@ use crate::{
Vim,
};
-pub fn create_mark(vim: &mut Vim, text: Arc<str>, tail: bool, cx: &mut WindowContext) {
- let Some(anchors) = vim.update_active_editor(cx, |_, editor, _| {
- editor
- .selections
- .disjoint_anchors()
- .iter()
- .map(|s| if tail { s.tail() } else { s.head() })
- .collect::<Vec<_>>()
- }) else {
- return;
- };
- vim.update_state(|state| state.marks.insert(text.to_string(), anchors));
- vim.clear_operator(cx);
-}
-
-pub fn create_visual_marks(vim: &mut Vim, mode: Mode, cx: &mut WindowContext) {
- let mut starts = vec![];
- let mut ends = vec![];
- let mut reversed = vec![];
+impl Vim {
+ pub fn create_mark(&mut self, text: Arc<str>, tail: bool, cx: &mut ViewContext<Self>) {
+ let Some(anchors) = self.update_editor(cx, |_, editor, _| {
+ editor
+ .selections
+ .disjoint_anchors()
+ .iter()
+ .map(|s| if tail { s.tail() } else { s.head() })
+ .collect::<Vec<_>>()
+ }) else {
+ return;
+ };
+ self.marks.insert(text.to_string(), anchors);
+ self.clear_operator(cx);
+ }
- vim.update_active_editor(cx, |_, editor, cx| {
- let (map, selections) = editor.selections.all_display(cx);
- for selection in selections {
- let end = movement::saturating_left(&map, selection.end);
- ends.push(
- map.buffer_snapshot
- .anchor_before(end.to_offset(&map, Bias::Left)),
- );
- starts.push(
- map.buffer_snapshot
- .anchor_after(selection.start.to_offset(&map, Bias::Right)),
- );
- reversed.push(selection.reversed)
+ // When handling an action, you must create visual marks if you will switch to normal
+ // mode without the default selection behavior.
+ pub(crate) fn store_visual_marks(&mut self, cx: &mut ViewContext<Self>) {
+ if self.mode.is_visual() {
+ self.create_visual_marks(self.mode, cx);
}
- });
+ }
- vim.update_state(|state| {
- state.marks.insert("<".to_string(), starts);
- state.marks.insert(">".to_string(), ends);
- state.stored_visual_mode.replace((mode, reversed));
- });
- vim.clear_operator(cx);
-}
+ pub(crate) fn create_visual_marks(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
+ let mut starts = vec![];
+ let mut ends = vec![];
+ let mut reversed = vec![];
-pub fn jump(text: Arc<str>, line: bool, cx: &mut WindowContext) {
- let anchors = Vim::update(cx, |vim, cx| {
- vim.pop_operator(cx);
+ self.update_editor(cx, |_, editor, cx| {
+ let (map, selections) = editor.selections.all_display(cx);
+ for selection in selections {
+ let end = movement::saturating_left(&map, selection.end);
+ ends.push(
+ map.buffer_snapshot
+ .anchor_before(end.to_offset(&map, Bias::Left)),
+ );
+ starts.push(
+ map.buffer_snapshot
+ .anchor_after(selection.start.to_offset(&map, Bias::Right)),
+ );
+ reversed.push(selection.reversed)
+ }
+ });
- match &*text {
- "{" | "}" => vim.update_active_editor(cx, |_, editor, cx| {
+ self.marks.insert("<".to_string(), starts);
+ self.marks.insert(">".to_string(), ends);
+ self.stored_visual_mode.replace((mode, reversed));
+ self.clear_operator(cx);
+ }
+
+ pub fn jump(&mut self, text: Arc<str>, line: bool, cx: &mut ViewContext<Self>) {
+ self.pop_operator(cx);
+
+ let anchors = match &*text {
+ "{" | "}" => self.update_editor(cx, |_, editor, cx| {
let (map, selections) = editor.selections.all_display(cx);
selections
.into_iter()
@@ -79,28 +85,26 @@ pub fn jump(text: Arc<str>, line: bool, cx: &mut WindowContext) {
})
.collect::<Vec<Anchor>>()
}),
- "." => vim.state().change_list.last().cloned(),
- _ => vim.state().marks.get(&*text).cloned(),
- }
- });
+ "." => self.change_list.last().cloned(),
+ _ => self.marks.get(&*text).cloned(),
+ };
- let Some(anchors) = anchors else { return };
+ let Some(anchors) = anchors else { return };
- let is_active_operator = Vim::read(cx).state().active_operator().is_some();
- if is_active_operator {
- if let Some(anchor) = anchors.last() {
- motion::motion(
- Motion::Jump {
- anchor: *anchor,
- line,
- },
- cx,
- )
- }
- return;
- } else {
- Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |_, editor, cx| {
+ let is_active_operator = self.active_operator().is_some();
+ if is_active_operator {
+ if let Some(anchor) = anchors.last() {
+ self.motion(
+ Motion::Jump {
+ anchor: *anchor,
+ line,
+ },
+ cx,
+ )
+ }
+ return;
+ } else {
+ self.update_editor(cx, |_, editor, cx| {
let map = editor.snapshot(cx);
let mut ranges: Vec<Range<Anchor>> = Vec::new();
for mut anchor in anchors {
@@ -120,7 +124,7 @@ pub fn jump(text: Arc<str>, line: bool, cx: &mut WindowContext) {
s.select_anchor_ranges(ranges)
})
});
- })
+ }
}
}
@@ -4,17 +4,15 @@ use editor::{display_map::ToDisplayPoint, movement, scroll::Autoscroll, DisplayP
use gpui::{impl_actions, ViewContext};
use language::{Bias, SelectionGoal};
use serde::Deserialize;
-use workspace::Workspace;
use crate::{
- normal::yank::copy_selections_content,
state::{Mode, Register},
Vim,
};
#[derive(Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
-struct Paste {
+pub struct Paste {
#[serde(default)]
before: bool,
#[serde(default)]
@@ -23,37 +21,34 @@ struct Paste {
impl_actions!(vim, [Paste]);
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(paste);
-}
-
-fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- vim.store_visual_marks(cx);
- let count = vim.take_count(cx).unwrap_or(1);
+impl Vim {
+ pub fn paste(&mut self, action: &Paste, cx: &mut ViewContext<Self>) {
+ self.record_current_action(cx);
+ self.store_visual_marks(cx);
+ let count = self.take_count(cx).unwrap_or(1);
- vim.update_active_editor(cx, |vim, editor, cx| {
+ self.update_editor(cx, |vim, editor, cx| {
let text_layout_details = editor.text_layout_details(cx);
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
- let selected_register = vim.update_state(|state| state.selected_register.take());
+ let selected_register = vim.selected_register.take();
let Some(Register {
text,
clipboard_selections,
- }) = vim
- .read_register(selected_register, Some(editor), cx)
- .filter(|reg| !reg.text.is_empty())
+ }) = Vim::update_globals(cx, |globals, cx| {
+ globals.read_register(selected_register, Some(editor), cx)
+ })
+ .filter(|reg| !reg.text.is_empty())
else {
return;
};
let clipboard_selections = clipboard_selections
- .filter(|sel| sel.len() > 1 && vim.state().mode != Mode::VisualLine);
+ .filter(|sel| sel.len() > 1 && vim.mode != Mode::VisualLine);
- if !action.preserve_clipboard && vim.state().mode.is_visual() {
- copy_selections_content(vim, editor, vim.state().mode == Mode::VisualLine, cx);
+ if !action.preserve_clipboard && vim.mode.is_visual() {
+ vim.copy_selections_content(editor, vim.mode == Mode::VisualLine, cx);
}
let (display_map, current_selections) = editor.selections.all_adjusted_display(cx);
@@ -90,7 +85,7 @@ fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
.first()
.map(|selection| selection.first_line_indent)
});
- let before = action.before || vim.state().mode == Mode::VisualLine;
+ let before = action.before || vim.mode == Mode::VisualLine;
let mut edits = Vec::new();
let mut new_selections = Vec::new();
@@ -121,7 +116,7 @@ fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
} else {
to_insert = "\n".to_owned() + &to_insert;
}
- } else if !line_mode && vim.state().mode == Mode::VisualLine {
+ } else if !line_mode && vim.mode == Mode::VisualLine {
to_insert = to_insert + "\n";
}
@@ -145,7 +140,7 @@ fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
let point_range = display_range.start.to_point(&display_map)
..display_range.end.to_point(&display_map);
- let anchor = if is_multiline || vim.state().mode == Mode::VisualLine {
+ let anchor = if is_multiline || vim.mode == Mode::VisualLine {
display_map.buffer_snapshot.anchor_before(point_range.start)
} else {
display_map.buffer_snapshot.anchor_after(point_range.end)
@@ -185,7 +180,7 @@ fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
cursor = movement::saturating_left(map, cursor)
}
cursors.push(cursor);
- if vim.state().mode == Mode::VisualBlock {
+ if vim.mode == Mode::VisualBlock {
break;
}
}
@@ -195,8 +190,8 @@ fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
})
});
});
- vim.switch_mode(Mode::Normal, true, cx);
- });
+ self.switch_mode(Mode::Normal, true, cx);
+ }
}
#[cfg(test)]
@@ -1,12 +1,12 @@
-use std::{cell::RefCell, ops::Range, rc::Rc, sync::Arc};
+use std::{cell::RefCell, rc::Rc};
use crate::{
insert::NormalBefore,
motion::Motion,
- state::{Mode, Operator, RecordedSelection, ReplayableAction},
- visual::visual_motion,
+ state::{Mode, Operator, RecordedSelection, ReplayableAction, VimGlobals},
Vim,
};
+use editor::Editor;
use gpui::{actions, Action, ViewContext, WindowContext};
use util::ResultExt;
use workspace::Workspace;
@@ -44,30 +44,28 @@ fn repeatable_insert(action: &ReplayableAction) -> Option<Box<dyn Action>> {
}
}
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, _: &EndRepeat, cx| {
- Vim::update(cx, |vim, cx| {
- vim.workspace_state.dot_replaying = false;
- vim.switch_mode(Mode::Normal, false, cx)
- });
+pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(editor, cx, |vim, _: &EndRepeat, cx| {
+ Vim::globals(cx).dot_replaying = false;
+ vim.switch_mode(Mode::Normal, false, cx)
});
- workspace.register_action(|_: &mut Workspace, _: &Repeat, cx| repeat(cx, false));
- workspace.register_action(|_: &mut Workspace, _: &ToggleRecord, cx| {
- Vim::update(cx, |vim, cx| {
- if let Some(char) = vim.workspace_state.recording_register.take() {
- vim.workspace_state.last_recorded_register = Some(char)
- } else {
- vim.push_operator(Operator::RecordRegister, cx);
- }
- })
+ Vim::action(editor, cx, |vim, _: &Repeat, cx| vim.repeat(false, cx));
+
+ Vim::action(editor, cx, |vim, _: &ToggleRecord, cx| {
+ let globals = Vim::globals(cx);
+ if let Some(char) = globals.recording_register.take() {
+ globals.last_recorded_register = Some(char)
+ } else {
+ vim.push_operator(Operator::RecordRegister, cx);
+ }
});
- workspace.register_action(|_: &mut Workspace, _: &ReplayLastRecording, cx| {
- let Some(register) = Vim::read(cx).workspace_state.last_recorded_register else {
+ Vim::action(editor, cx, |vim, _: &ReplayLastRecording, cx| {
+ let Some(register) = Vim::globals(cx).last_recorded_register else {
return;
};
- replay_register(register, cx)
+ vim.replay_register(register, cx)
});
}
@@ -116,54 +114,60 @@ impl Replayer {
lock.ix += 1;
drop(lock);
let Some(action) = action else {
- Vim::update(cx, |vim, _| vim.workspace_state.replayer.take());
+ Vim::globals(cx).replayer.take();
return;
};
match action {
ReplayableAction::Action(action) => {
if should_replay(&*action) {
cx.dispatch_action(action.boxed_clone());
- cx.defer(move |cx| observe_action(action.boxed_clone(), cx));
+ cx.defer(move |cx| Vim::globals(cx).observe_action(action.boxed_clone()));
}
}
ReplayableAction::Insertion {
text,
utf16_range_to_replace,
} => {
- if let Some(editor) = Vim::read(cx).active_editor.clone() {
- editor
- .update(cx, |editor, cx| {
+ cx.window_handle()
+ .update(cx, |handle, cx| {
+ let Ok(workspace) = handle.downcast::<Workspace>() else {
+ return;
+ };
+ let Some(editor) = workspace.read(cx).active_item_as::<Editor>(cx) else {
+ return;
+ };
+ editor.update(cx, |editor, cx| {
editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)
})
- .log_err();
- }
+ })
+ .log_err();
}
}
cx.defer(move |cx| self.next(cx));
}
}
-pub(crate) fn record_register(register: char, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- vim.workspace_state.recording_register = Some(register);
- vim.workspace_state.recordings.remove(®ister);
- vim.workspace_state.ignore_current_insertion = true;
- vim.clear_operator(cx)
- })
-}
+impl Vim {
+ pub(crate) fn record_register(&mut self, register: char, cx: &mut ViewContext<Self>) {
+ let globals = Vim::globals(cx);
+ globals.recording_register = Some(register);
+ globals.recordings.remove(®ister);
+ globals.ignore_current_insertion = true;
+ self.clear_operator(cx)
+ }
-pub(crate) fn replay_register(mut register: char, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- let mut count = vim.take_count(cx).unwrap_or(1);
- vim.clear_operator(cx);
+ pub(crate) fn replay_register(&mut self, mut register: char, cx: &mut ViewContext<Self>) {
+ let mut count = self.take_count(cx).unwrap_or(1);
+ self.clear_operator(cx);
+ let globals = Vim::globals(cx);
if register == '@' {
- let Some(last) = vim.workspace_state.last_replayed_register else {
+ let Some(last) = globals.last_replayed_register else {
return;
};
register = last;
}
- let Some(actions) = vim.workspace_state.recordings.get(®ister) else {
+ let Some(actions) = globals.recordings.get(®ister) else {
return;
};
@@ -173,206 +177,148 @@ pub(crate) fn replay_register(mut register: char, cx: &mut WindowContext) {
count -= 1
}
- vim.workspace_state.last_replayed_register = Some(register);
-
- vim.workspace_state
+ globals.last_replayed_register = Some(register);
+ let mut replayer = globals
.replayer
.get_or_insert_with(|| Replayer::new())
- .replay(repeated_actions, cx);
- });
-}
-
-pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
- let Some((mut actions, selection)) = Vim::update(cx, |vim, cx| {
- let actions = vim.workspace_state.recorded_actions.clone();
- if actions.is_empty() {
- return None;
- }
-
- let count = vim.take_count(cx);
+ .clone();
+ replayer.replay(repeated_actions, cx);
+ }
- let selection = vim.workspace_state.recorded_selection.clone();
- match selection {
- RecordedSelection::SingleLine { .. } | RecordedSelection::Visual { .. } => {
- vim.workspace_state.recorded_count = None;
- vim.switch_mode(Mode::Visual, false, cx)
- }
- RecordedSelection::VisualLine { .. } => {
- vim.workspace_state.recorded_count = None;
- vim.switch_mode(Mode::VisualLine, false, cx)
+ pub(crate) fn repeat(&mut self, from_insert_mode: bool, cx: &mut ViewContext<Self>) {
+ let count = self.take_count(cx);
+ let Some((mut actions, selection, mode)) = Vim::update_globals(cx, |globals, _| {
+ let actions = globals.recorded_actions.clone();
+ if actions.is_empty() {
+ return None;
}
- RecordedSelection::VisualBlock { .. } => {
- vim.workspace_state.recorded_count = None;
- vim.switch_mode(Mode::VisualBlock, false, cx)
- }
- RecordedSelection::None => {
- if let Some(count) = count {
- vim.workspace_state.recorded_count = Some(count);
+ if globals.replayer.is_none() {
+ if let Some(recording_register) = globals.recording_register {
+ globals
+ .recordings
+ .entry(recording_register)
+ .or_default()
+ .push(ReplayableAction::Action(Repeat.boxed_clone()));
}
}
- }
- if vim.workspace_state.replayer.is_none() {
- if let Some(recording_register) = vim.workspace_state.recording_register {
- vim.workspace_state
- .recordings
- .entry(recording_register)
- .or_default()
- .push(ReplayableAction::Action(Repeat.boxed_clone()));
+ let mut mode = None;
+ let selection = globals.recorded_selection.clone();
+ match selection {
+ RecordedSelection::SingleLine { .. } | RecordedSelection::Visual { .. } => {
+ globals.recorded_count = None;
+ mode = Some(Mode::Visual);
+ }
+ RecordedSelection::VisualLine { .. } => {
+ globals.recorded_count = None;
+ mode = Some(Mode::VisualLine)
+ }
+ RecordedSelection::VisualBlock { .. } => {
+ globals.recorded_count = None;
+ mode = Some(Mode::VisualBlock)
+ }
+ RecordedSelection::None => {
+ if let Some(count) = count {
+ globals.recorded_count = Some(count);
+ }
+ }
}
- }
- Some((actions, selection))
- }) else {
- return;
- };
+ Some((actions, selection, mode))
+ }) else {
+ return;
+ };
+ if let Some(mode) = mode {
+ self.switch_mode(mode, false, cx)
+ }
- match selection {
- RecordedSelection::SingleLine { cols } => {
- if cols > 1 {
- visual_motion(Motion::Right, Some(cols as usize - 1), cx)
+ match selection {
+ RecordedSelection::SingleLine { cols } => {
+ if cols > 1 {
+ self.visual_motion(Motion::Right, Some(cols as usize - 1), cx)
+ }
}
- }
- RecordedSelection::Visual { rows, cols } => {
- visual_motion(
- Motion::Down {
- display_lines: false,
- },
- Some(rows as usize),
- cx,
- );
- visual_motion(
- Motion::StartOfLine {
- display_lines: false,
- },
- None,
- cx,
- );
- if cols > 1 {
- visual_motion(Motion::Right, Some(cols as usize - 1), cx)
+ RecordedSelection::Visual { rows, cols } => {
+ self.visual_motion(
+ Motion::Down {
+ display_lines: false,
+ },
+ Some(rows as usize),
+ cx,
+ );
+ self.visual_motion(
+ Motion::StartOfLine {
+ display_lines: false,
+ },
+ None,
+ cx,
+ );
+ if cols > 1 {
+ self.visual_motion(Motion::Right, Some(cols as usize - 1), cx)
+ }
}
- }
- RecordedSelection::VisualBlock { rows, cols } => {
- visual_motion(
- Motion::Down {
- display_lines: false,
- },
- Some(rows as usize),
- cx,
- );
- if cols > 1 {
- visual_motion(Motion::Right, Some(cols as usize - 1), cx);
+ RecordedSelection::VisualBlock { rows, cols } => {
+ self.visual_motion(
+ Motion::Down {
+ display_lines: false,
+ },
+ Some(rows as usize),
+ cx,
+ );
+ if cols > 1 {
+ self.visual_motion(Motion::Right, Some(cols as usize - 1), cx);
+ }
}
+ RecordedSelection::VisualLine { rows } => {
+ self.visual_motion(
+ Motion::Down {
+ display_lines: false,
+ },
+ Some(rows as usize),
+ cx,
+ );
+ }
+ RecordedSelection::None => {}
}
- RecordedSelection::VisualLine { rows } => {
- visual_motion(
- Motion::Down {
- display_lines: false,
- },
- Some(rows as usize),
- cx,
- );
- }
- RecordedSelection::None => {}
- }
- // insert internally uses repeat to handle counts
- // vim doesn't treat 3a1 as though you literally repeated a1
- // 3 times, instead it inserts the content thrice at the insert position.
- if let Some(to_repeat) = repeatable_insert(&actions[0]) {
- if let Some(ReplayableAction::Action(action)) = actions.last() {
- if NormalBefore.partial_eq(&**action) {
- actions.pop();
+ // insert internally uses repeat to handle counts
+ // vim doesn't treat 3a1 as though you literally repeated a1
+ // 3 times, instead it inserts the content thrice at the insert position.
+ if let Some(to_repeat) = repeatable_insert(&actions[0]) {
+ if let Some(ReplayableAction::Action(action)) = actions.last() {
+ if NormalBefore.partial_eq(&**action) {
+ actions.pop();
+ }
}
- }
- let mut new_actions = actions.clone();
- actions[0] = ReplayableAction::Action(to_repeat.boxed_clone());
+ let mut new_actions = actions.clone();
+ actions[0] = ReplayableAction::Action(to_repeat.boxed_clone());
- let mut count = Vim::read(cx).workspace_state.recorded_count.unwrap_or(1);
+ let mut count = cx.global::<VimGlobals>().recorded_count.unwrap_or(1);
- // if we came from insert mode we're just doing repetitions 2 onwards.
- if from_insert_mode {
- count -= 1;
- new_actions[0] = actions[0].clone();
- }
+ // if we came from insert mode we're just doing repetitions 2 onwards.
+ if from_insert_mode {
+ count -= 1;
+ new_actions[0] = actions[0].clone();
+ }
- for _ in 1..count {
- new_actions.append(actions.clone().as_mut());
+ for _ in 1..count {
+ new_actions.append(actions.clone().as_mut());
+ }
+ new_actions.push(ReplayableAction::Action(NormalBefore.boxed_clone()));
+ actions = new_actions;
}
- new_actions.push(ReplayableAction::Action(NormalBefore.boxed_clone()));
- actions = new_actions;
- }
-
- actions.push(ReplayableAction::Action(EndRepeat.boxed_clone()));
- Vim::update(cx, |vim, cx| {
- vim.workspace_state.dot_replaying = true;
+ actions.push(ReplayableAction::Action(EndRepeat.boxed_clone()));
- vim.workspace_state
+ let globals = Vim::globals(cx);
+ globals.dot_replaying = true;
+ let mut replayer = globals
.replayer
.get_or_insert_with(|| Replayer::new())
- .replay(actions, cx);
- })
-}
-
-pub(crate) fn observe_action(action: Box<dyn Action>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, _| {
- if vim.workspace_state.dot_recording {
- vim.workspace_state
- .recorded_actions
- .push(ReplayableAction::Action(action.boxed_clone()));
-
- if vim.workspace_state.stop_recording_after_next_action {
- vim.workspace_state.dot_recording = false;
- vim.workspace_state.stop_recording_after_next_action = false;
- }
- }
- if vim.workspace_state.replayer.is_none() {
- if let Some(recording_register) = vim.workspace_state.recording_register {
- vim.workspace_state
- .recordings
- .entry(recording_register)
- .or_default()
- .push(ReplayableAction::Action(action));
- }
- }
- })
-}
-
-pub(crate) fn observe_insertion(
- text: &Arc<str>,
- range_to_replace: Option<Range<isize>>,
- cx: &mut WindowContext,
-) {
- Vim::update(cx, |vim, _| {
- if vim.workspace_state.ignore_current_insertion {
- vim.workspace_state.ignore_current_insertion = false;
- return;
- }
- if vim.workspace_state.dot_recording {
- vim.workspace_state
- .recorded_actions
- .push(ReplayableAction::Insertion {
- text: text.clone(),
- utf16_range_to_replace: range_to_replace.clone(),
- });
- if vim.workspace_state.stop_recording_after_next_action {
- vim.workspace_state.dot_recording = false;
- vim.workspace_state.stop_recording_after_next_action = false;
- }
- }
- if let Some(recording_register) = vim.workspace_state.recording_register {
- vim.workspace_state
- .recordings
- .entry(recording_register)
- .or_default()
- .push(ReplayableAction::Insertion {
- text: text.clone(),
- utf16_range_to_replace: range_to_replace,
- });
- }
- });
+ .clone();
+ replayer.replay(actions, cx);
+ }
}
#[cfg(test)]
@@ -7,28 +7,27 @@ use editor::{
use gpui::{actions, ViewContext};
use language::Bias;
use settings::Settings;
-use workspace::Workspace;
actions!(
vim,
[LineUp, LineDown, ScrollUp, ScrollDown, PageUp, PageDown]
);
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, _: &LineDown, cx| {
- scroll(cx, false, |c| ScrollAmount::Line(c.unwrap_or(1.)))
+pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(editor, cx, |vim, _: &LineDown, cx| {
+ vim.scroll(false, cx, |c| ScrollAmount::Line(c.unwrap_or(1.)))
});
- workspace.register_action(|_: &mut Workspace, _: &LineUp, cx| {
- scroll(cx, false, |c| ScrollAmount::Line(-c.unwrap_or(1.)))
+ Vim::action(editor, cx, |vim, _: &LineUp, cx| {
+ vim.scroll(false, cx, |c| ScrollAmount::Line(-c.unwrap_or(1.)))
});
- workspace.register_action(|_: &mut Workspace, _: &PageDown, cx| {
- scroll(cx, false, |c| ScrollAmount::Page(c.unwrap_or(1.)))
+ Vim::action(editor, cx, |vim, _: &PageDown, cx| {
+ vim.scroll(false, cx, |c| ScrollAmount::Page(c.unwrap_or(1.)))
});
- workspace.register_action(|_: &mut Workspace, _: &PageUp, cx| {
- scroll(cx, false, |c| ScrollAmount::Page(-c.unwrap_or(1.)))
+ Vim::action(editor, cx, |vim, _: &PageUp, cx| {
+ vim.scroll(false, cx, |c| ScrollAmount::Page(-c.unwrap_or(1.)))
});
- workspace.register_action(|_: &mut Workspace, _: &ScrollDown, cx| {
- scroll(cx, true, |c| {
+ Vim::action(editor, cx, |vim, _: &ScrollDown, cx| {
+ vim.scroll(true, cx, |c| {
if let Some(c) = c {
ScrollAmount::Line(c)
} else {
@@ -36,8 +35,8 @@ pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
}
})
});
- workspace.register_action(|_: &mut Workspace, _: &ScrollUp, cx| {
- scroll(cx, true, |c| {
+ Vim::action(editor, cx, |vim, _: &ScrollUp, cx| {
+ vim.scroll(true, cx, |c| {
if let Some(c) = c {
ScrollAmount::Line(-c)
} else {
@@ -47,17 +46,18 @@ pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
});
}
-fn scroll(
- cx: &mut ViewContext<Workspace>,
- move_cursor: bool,
- by: fn(c: Option<f32>) -> ScrollAmount,
-) {
- Vim::update(cx, |vim, cx| {
- let amount = by(vim.take_count(cx).map(|c| c as f32));
- vim.update_active_editor(cx, |_, editor, cx| {
+impl Vim {
+ fn scroll(
+ &mut self,
+ move_cursor: bool,
+ cx: &mut ViewContext<Self>,
+ by: fn(c: Option<f32>) -> ScrollAmount,
+ ) {
+ let amount = by(self.take_count(cx).map(|c| c as f32));
+ self.update_editor(cx, |_, editor, cx| {
scroll_editor(editor, move_cursor, &amount, cx)
});
- })
+ }
}
fn scroll_editor(
@@ -1,15 +1,15 @@
use std::{iter::Peekable, str::Chars, time::Duration};
+use editor::Editor;
use gpui::{actions, impl_actions, ViewContext};
use language::Point;
use search::{buffer_search, BufferSearchBar, SearchOptions};
use serde_derive::Deserialize;
-use workspace::{notifications::NotifyResultExt, searchable::Direction, Workspace};
+use workspace::{notifications::NotifyResultExt, searchable::Direction};
use crate::{
command::CommandRange,
- motion::{search_motion, Motion},
- normal::move_cursor,
+ motion::Motion,
state::{Mode, SearchState},
Vim,
};
@@ -60,53 +60,43 @@ impl_actions!(
[FindCommand, ReplaceCommand, Search, MoveToPrev, MoveToNext]
);
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(move_to_next);
- workspace.register_action(move_to_prev);
- workspace.register_action(move_to_next_match);
- workspace.register_action(move_to_prev_match);
- workspace.register_action(search);
- workspace.register_action(search_submit);
- workspace.register_action(search_deploy);
-
- workspace.register_action(find_command);
- workspace.register_action(replace_command);
+pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(editor, cx, Vim::move_to_next);
+ Vim::action(editor, cx, Vim::move_to_prev);
+ Vim::action(editor, cx, Vim::move_to_next_match);
+ Vim::action(editor, cx, Vim::move_to_prev_match);
+ Vim::action(editor, cx, Vim::search);
+ Vim::action(editor, cx, Vim::search_deploy);
+ Vim::action(editor, cx, Vim::find_command);
+ Vim::action(editor, cx, Vim::replace_command);
}
-fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext<Workspace>) {
- move_to_internal(workspace, Direction::Next, !action.partial_word, cx)
-}
+impl Vim {
+ fn move_to_next(&mut self, action: &MoveToNext, cx: &mut ViewContext<Self>) {
+ self.move_to_internal(Direction::Next, !action.partial_word, cx)
+ }
-fn move_to_prev(workspace: &mut Workspace, action: &MoveToPrev, cx: &mut ViewContext<Workspace>) {
- move_to_internal(workspace, Direction::Prev, !action.partial_word, cx)
-}
+ fn move_to_prev(&mut self, action: &MoveToPrev, cx: &mut ViewContext<Self>) {
+ self.move_to_internal(Direction::Prev, !action.partial_word, cx)
+ }
-fn move_to_next_match(
- workspace: &mut Workspace,
- _: &MoveToNextMatch,
- cx: &mut ViewContext<Workspace>,
-) {
- move_to_match_internal(workspace, Direction::Next, cx)
-}
+ fn move_to_next_match(&mut self, _: &MoveToNextMatch, cx: &mut ViewContext<Self>) {
+ self.move_to_match_internal(Direction::Next, cx)
+ }
-fn move_to_prev_match(
- workspace: &mut Workspace,
- _: &MoveToPrevMatch,
- cx: &mut ViewContext<Workspace>,
-) {
- move_to_match_internal(workspace, Direction::Prev, cx)
-}
+ fn move_to_prev_match(&mut self, _: &MoveToPrevMatch, cx: &mut ViewContext<Self>) {
+ self.move_to_match_internal(Direction::Prev, cx)
+ }
-fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Workspace>) {
- let pane = workspace.active_pane().clone();
- let direction = if action.backwards {
- Direction::Prev
- } else {
- Direction::Next
- };
- Vim::update(cx, |vim, cx| {
- let count = vim.take_count(cx).unwrap_or(1);
- let prior_selections = vim.editor_selections(cx);
+ fn search(&mut self, action: &Search, cx: &mut ViewContext<Self>) {
+ let Some(pane) = self.pane(cx) else { return };
+ let direction = if action.backwards {
+ Direction::Prev
+ } else {
+ Direction::Next
+ };
+ let count = self.take_count(cx).unwrap_or(1);
+ let prior_selections = self.editor_selections(cx);
pane.update(cx, |pane, cx| {
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
search_bar.update(cx, |search_bar, cx| {
@@ -122,241 +112,229 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
search_bar.set_replacement(None, cx);
search_bar.set_search_options(SearchOptions::REGEX, cx);
}
- vim.update_state(|state| {
- state.search = SearchState {
- direction,
- count,
- initial_query: query.clone(),
- prior_selections,
- prior_operator: state.operator_stack.last().cloned(),
- prior_mode: state.mode,
- }
- });
+ self.search = SearchState {
+ direction,
+ count,
+ initial_query: query.clone(),
+ prior_selections,
+ prior_operator: self.operator_stack.last().cloned(),
+ prior_mode: self.mode,
+ }
});
}
})
- })
-}
-
-// hook into the existing to clear out any vim search state on cmd+f or edit -> find.
-fn search_deploy(_: &mut Workspace, _: &buffer_search::Deploy, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, _| {
- vim.update_state(|state| state.search = Default::default())
- });
- cx.propagate();
-}
-
-fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {
- let mut motion = None;
- Vim::update(cx, |vim, cx| {
- vim.store_visual_marks(cx);
- let pane = workspace.active_pane().clone();
- pane.update(cx, |pane, cx| {
- if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
- search_bar.update(cx, |search_bar, cx| {
- let (mut prior_selections, prior_mode, prior_operator) =
- vim.update_state(|state| {
- let mut count = state.search.count;
- let direction = state.search.direction;
- // in the case that the query has changed, the search bar
- // will have selected the next match already.
- if (search_bar.query(cx) != state.search.initial_query)
- && state.search.direction == Direction::Next
- {
- count = count.saturating_sub(1)
- }
- state.search.count = 1;
- search_bar.select_match(direction, count, cx);
- search_bar.focus_editor(&Default::default(), cx);
-
- let prior_selections: Vec<_> =
- state.search.prior_selections.drain(..).collect();
- let prior_mode = state.search.prior_mode;
- let prior_operator = state.search.prior_operator.take();
- (prior_selections, prior_mode, prior_operator)
- });
+ }
- vim.workspace_state
- .registers
- .insert('/', search_bar.query(cx).into());
+ // hook into the existing to clear out any vim search state on cmd+f or edit -> find.
+ fn search_deploy(&mut self, _: &buffer_search::Deploy, cx: &mut ViewContext<Self>) {
+ self.search = Default::default();
+ cx.propagate();
+ }
- let new_selections = vim.editor_selections(cx);
+ pub fn search_submit(&mut self, cx: &mut ViewContext<Self>) {
+ self.store_visual_marks(cx);
+ let Some(pane) = self.pane(cx) else { return };
+ let result = pane.update(cx, |pane, cx| {
+ let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else {
+ return None;
+ };
+ search_bar.update(cx, |search_bar, cx| {
+ let mut count = self.search.count;
+ let direction = self.search.direction;
+ // in the case that the query has changed, the search bar
+ // will have selected the next match already.
+ if (search_bar.query(cx) != self.search.initial_query)
+ && self.search.direction == Direction::Next
+ {
+ count = count.saturating_sub(1)
+ }
+ self.search.count = 1;
+ search_bar.select_match(direction, count, cx);
+ search_bar.focus_editor(&Default::default(), cx);
- // If the active editor has changed during a search, don't panic.
- if prior_selections.iter().any(|s| {
- vim.update_active_editor(cx, |_vim, editor, cx| {
- !s.start.is_valid(&editor.snapshot(cx).buffer_snapshot)
- })
- .unwrap_or(true)
- }) {
- prior_selections.clear();
- }
+ let prior_selections: Vec<_> = self.search.prior_selections.drain(..).collect();
+ let prior_mode = self.search.prior_mode;
+ let prior_operator = self.search.prior_operator.take();
- if prior_mode != vim.state().mode {
- vim.switch_mode(prior_mode, true, cx);
- }
- if let Some(operator) = prior_operator {
- vim.push_operator(operator, cx);
- };
- motion = Some(Motion::ZedSearchResult {
- prior_selections,
- new_selections,
- });
- });
- }
+ let query = search_bar.query(cx).into();
+ Vim::globals(cx).registers.insert('/', query);
+ Some((prior_selections, prior_mode, prior_operator))
+ })
});
- });
- if let Some(motion) = motion {
- search_motion(motion, cx)
- }
-}
+ let Some((mut prior_selections, prior_mode, prior_operator)) = result else {
+ return;
+ };
-pub fn move_to_match_internal(
- workspace: &mut Workspace,
- direction: Direction,
- cx: &mut ViewContext<Workspace>,
-) {
- let mut motion = None;
- Vim::update(cx, |vim, cx| {
- let pane = workspace.active_pane().clone();
- let count = vim.take_count(cx).unwrap_or(1);
- let prior_selections = vim.editor_selections(cx);
+ let new_selections = self.editor_selections(cx);
- pane.update(cx, |pane, cx| {
- if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
- search_bar.update(cx, |search_bar, cx| {
- if !search_bar.has_active_match() || !search_bar.show(cx) {
- return;
- }
- search_bar.select_match(direction, count, cx);
+ // If the active editor has changed during a search, don't panic.
+ if prior_selections.iter().any(|s| {
+ self.update_editor(cx, |_, editor, cx| {
+ !s.start.is_valid(&editor.snapshot(cx).buffer_snapshot)
+ })
+ .unwrap_or(true)
+ }) {
+ prior_selections.clear();
+ }
- let new_selections = vim.editor_selections(cx);
- motion = Some(Motion::ZedSearchResult {
- prior_selections,
- new_selections,
- });
- })
- }
- })
- });
- if let Some(motion) = motion {
- search_motion(motion, cx);
+ if prior_mode != self.mode {
+ self.switch_mode(prior_mode, true, cx);
+ }
+ if let Some(operator) = prior_operator {
+ self.push_operator(operator, cx);
+ };
+ self.search_motion(
+ Motion::ZedSearchResult {
+ prior_selections,
+ new_selections,
+ },
+ cx,
+ );
}
-}
-pub fn move_to_internal(
- workspace: &mut Workspace,
- direction: Direction,
- whole_word: bool,
- cx: &mut ViewContext<Workspace>,
-) {
- Vim::update(cx, |vim, cx| {
- let pane = workspace.active_pane().clone();
- let count = vim.take_count(cx).unwrap_or(1);
- let prior_selections = vim.editor_selections(cx);
+ pub fn move_to_match_internal(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
+ let Some(pane) = self.pane(cx) else { return };
+ let count = self.take_count(cx).unwrap_or(1);
+ let prior_selections = self.editor_selections(cx);
- pane.update(cx, |pane, cx| {
- if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
- let search = search_bar.update(cx, |search_bar, cx| {
- let options = SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX;
- if !search_bar.show(cx) {
- return None;
- }
- let Some(query) = search_bar.query_suggestion(cx) else {
- vim.clear_operator(cx);
- drop(search_bar.search("", None, cx));
- return None;
- };
- let mut query = regex::escape(&query);
- if whole_word {
- query = format!(r"\<{}\>", query);
- }
- Some(search_bar.search(&query, Some(options), cx))
- });
-
- if let Some(search) = search {
- let search_bar = search_bar.downgrade();
- cx.spawn(|_, mut cx| async move {
- search.await?;
- search_bar.update(&mut cx, |search_bar, cx| {
- search_bar.select_match(direction, count, cx);
-
- let new_selections =
- Vim::update(cx, |vim, cx| vim.editor_selections(cx));
- search_motion(
- Motion::ZedSearchResult {
- prior_selections,
- new_selections,
- },
- cx,
- )
- })?;
- anyhow::Ok(())
- })
- .detach_and_log_err(cx);
+ let success = pane.update(cx, |pane, cx| {
+ let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else {
+ return false;
+ };
+ search_bar.update(cx, |search_bar, cx| {
+ if !search_bar.has_active_match() || !search_bar.show(cx) {
+ return false;
}
- }
+ search_bar.select_match(direction, count, cx);
+ true
+ })
});
-
- if vim.state().mode.is_visual() {
- vim.switch_mode(Mode::Normal, false, cx)
+ if !success {
+ return;
}
- });
-}
-fn find_command(workspace: &mut Workspace, action: &FindCommand, cx: &mut ViewContext<Workspace>) {
- let pane = workspace.active_pane().clone();
- pane.update(cx, |pane, cx| {
- if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
+ let new_selections = self.editor_selections(cx);
+ self.search_motion(
+ Motion::ZedSearchResult {
+ prior_selections,
+ new_selections,
+ },
+ cx,
+ );
+ }
+
+ pub fn move_to_internal(
+ &mut self,
+ direction: Direction,
+ whole_word: bool,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let Some(pane) = self.pane(cx) else { return };
+ let count = self.take_count(cx).unwrap_or(1);
+ let prior_selections = self.editor_selections(cx);
+ let vim = cx.view().clone();
+
+ let searched = pane.update(cx, |pane, cx| {
+ let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else {
+ return false;
+ };
let search = search_bar.update(cx, |search_bar, cx| {
+ let options = SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX;
if !search_bar.show(cx) {
return None;
}
- let mut query = action.query.clone();
- if query == "" {
- query = search_bar.query(cx);
+ let Some(query) = search_bar.query_suggestion(cx) else {
+ drop(search_bar.search("", None, cx));
+ return None;
};
-
- Some(search_bar.search(
- &query,
- Some(SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX),
- cx,
- ))
+ let mut query = regex::escape(&query);
+ if whole_word {
+ query = format!(r"\<{}\>", query);
+ }
+ Some(search_bar.search(&query, Some(options), cx))
});
- let Some(search) = search else { return };
+
+ let Some(search) = search else { return false };
+
let search_bar = search_bar.downgrade();
- let direction = if action.backwards {
- Direction::Prev
- } else {
- Direction::Next
- };
cx.spawn(|_, mut cx| async move {
search.await?;
search_bar.update(&mut cx, |search_bar, cx| {
- search_bar.select_match(direction, 1, cx)
+ search_bar.select_match(direction, count, cx);
+
+ vim.update(cx, |vim, cx| {
+ let new_selections = vim.editor_selections(cx);
+ vim.search_motion(
+ Motion::ZedSearchResult {
+ prior_selections,
+ new_selections,
+ },
+ cx,
+ )
+ });
})?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
+ true
+ });
+ if !searched {
+ self.clear_operator(cx)
}
- })
-}
-fn replace_command(
- workspace: &mut Workspace,
- action: &ReplaceCommand,
- cx: &mut ViewContext<Workspace>,
-) {
- let replacement = action.replacement.clone();
- let pane = workspace.active_pane().clone();
- let editor = Vim::read(cx)
- .active_editor
- .as_ref()
- .and_then(|editor| editor.upgrade());
- if let Some(range) = &action.range {
- if let Some(result) = Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |vim, editor, cx| {
+ if self.mode.is_visual() {
+ self.switch_mode(Mode::Normal, false, cx)
+ }
+ }
+
+ fn find_command(&mut self, action: &FindCommand, cx: &mut ViewContext<Self>) {
+ let Some(pane) = self.pane(cx) else { return };
+ pane.update(cx, |pane, cx| {
+ if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
+ let search = search_bar.update(cx, |search_bar, cx| {
+ if !search_bar.show(cx) {
+ return None;
+ }
+ let mut query = action.query.clone();
+ if query == "" {
+ query = search_bar.query(cx);
+ };
+
+ Some(search_bar.search(
+ &query,
+ Some(SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX),
+ cx,
+ ))
+ });
+ let Some(search) = search else { return };
+ let search_bar = search_bar.downgrade();
+ let direction = if action.backwards {
+ Direction::Prev
+ } else {
+ Direction::Next
+ };
+ cx.spawn(|_, mut cx| async move {
+ search.await?;
+ search_bar.update(&mut cx, |search_bar, cx| {
+ search_bar.select_match(direction, 1, cx)
+ })?;
+ anyhow::Ok(())
+ })
+ .detach_and_log_err(cx);
+ }
+ })
+ }
+
+ fn replace_command(&mut self, action: &ReplaceCommand, cx: &mut ViewContext<Self>) {
+ let replacement = action.replacement.clone();
+ let Some(((pane, workspace), editor)) =
+ self.pane(cx).zip(self.workspace(cx)).zip(self.editor())
+ else {
+ return;
+ };
+ if let Some(range) = &action.range {
+ if let Some(result) = self.update_editor(cx, |vim, editor, cx| {
let range = range.buffer_range(vim, editor, cx)?;
let snapshot = &editor.snapshot(cx).buffer_snapshot;
let end_point = Point::new(range.end.0, snapshot.line_len(range.end));
@@ -364,42 +342,43 @@ fn replace_command(
..snapshot.anchor_after(end_point);
editor.set_search_within_ranges(&[range], cx);
anyhow::Ok(())
- })
- }) {
- result.notify_err(workspace, cx);
- }
- }
- pane.update(cx, |pane, cx| {
- let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else {
- return;
- };
- let search = search_bar.update(cx, |search_bar, cx| {
- if !search_bar.show(cx) {
- return None;
- }
-
- let mut options = SearchOptions::REGEX;
- if replacement.is_case_sensitive {
- options.set(SearchOptions::CASE_SENSITIVE, true)
+ }) {
+ workspace.update(cx, |workspace, cx| {
+ result.notify_err(workspace, cx);
+ })
}
- let search = if replacement.search == "" {
- search_bar.query(cx)
- } else {
- replacement.search
+ }
+ let vim = cx.view().clone();
+ pane.update(cx, |pane, cx| {
+ let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else {
+ return;
};
+ let search = search_bar.update(cx, |search_bar, cx| {
+ if !search_bar.show(cx) {
+ return None;
+ }
- search_bar.set_replacement(Some(&replacement.replacement), cx);
- Some(search_bar.search(&search, Some(options), cx))
- });
- let Some(search) = search else { return };
- let search_bar = search_bar.downgrade();
- cx.spawn(|_, mut cx| async move {
- search.await?;
- search_bar.update(&mut cx, |search_bar, cx| {
- if replacement.should_replace_all {
- search_bar.select_last_match(cx);
- search_bar.replace_all(&Default::default(), cx);
- if let Some(editor) = editor {
+ let mut options = SearchOptions::REGEX;
+ if replacement.is_case_sensitive {
+ options.set(SearchOptions::CASE_SENSITIVE, true)
+ }
+ let search = if replacement.search == "" {
+ search_bar.query(cx)
+ } else {
+ replacement.search
+ };
+
+ search_bar.set_replacement(Some(&replacement.replacement), cx);
+ Some(search_bar.search(&search, Some(options), cx))
+ });
+ let Some(search) = search else { return };
+ let search_bar = search_bar.downgrade();
+ cx.spawn(|_, mut cx| async move {
+ search.await?;
+ search_bar.update(&mut cx, |search_bar, cx| {
+ if replacement.should_replace_all {
+ search_bar.select_last_match(cx);
+ search_bar.replace_all(&Default::default(), cx);
cx.spawn(|_, mut cx| async move {
cx.background_executor()
.timer(Duration::from_millis(200))
@@ -409,23 +388,22 @@ fn replace_command(
.ok();
})
.detach();
+ vim.update(cx, |vim, cx| {
+ vim.move_cursor(
+ Motion::StartOfLine {
+ display_lines: false,
+ },
+ None,
+ cx,
+ )
+ });
}
- Vim::update(cx, |vim, cx| {
- move_cursor(
- vim,
- Motion::StartOfLine {
- display_lines: false,
- },
- None,
- cx,
- )
- })
- }
- })?;
- anyhow::Ok(())
+ })?;
+ anyhow::Ok(())
+ })
+ .detach_and_log_err(cx);
})
- .detach_and_log_err(cx);
- })
+ }
}
impl Replacement {
@@ -697,7 +675,7 @@ mod test {
#[gpui::test]
async fn test_non_vim_search(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, false).await;
- cx.set_state("ˇone one one one", Mode::Normal);
+ cx.cx.set_state("ˇone one one one");
cx.simulate_keystrokes("cmd-f");
cx.run_until_parked();
@@ -1,85 +1,87 @@
-use editor::movement;
-use gpui::{actions, ViewContext, WindowContext};
+use editor::{movement, Editor};
+use gpui::{actions, ViewContext};
use language::Point;
-use workspace::Workspace;
-use crate::{motion::Motion, normal::yank::copy_selections_content, Mode, Vim};
+use crate::{motion::Motion, Mode, Vim};
actions!(vim, [Substitute, SubstituteLine]);
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, _: &Substitute, cx| {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- let count = vim.take_count(cx);
- substitute(vim, count, vim.state().mode == Mode::VisualLine, cx);
- })
+pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(editor, cx, |vim, _: &Substitute, cx| {
+ vim.start_recording(cx);
+ let count = vim.take_count(cx);
+ vim.substitute(count, vim.mode == Mode::VisualLine, cx);
});
- workspace.register_action(|_: &mut Workspace, _: &SubstituteLine, cx| {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) {
- vim.switch_mode(Mode::VisualLine, false, cx)
- }
- let count = vim.take_count(cx);
- substitute(vim, count, true, cx)
- })
+ Vim::action(editor, cx, |vim, _: &SubstituteLine, cx| {
+ vim.start_recording(cx);
+ if matches!(vim.mode, Mode::VisualBlock | Mode::Visual) {
+ vim.switch_mode(Mode::VisualLine, false, cx)
+ }
+ let count = vim.take_count(cx);
+ vim.substitute(count, true, cx)
});
}
-pub fn substitute(vim: &mut Vim, count: Option<usize>, line_mode: bool, cx: &mut WindowContext) {
- vim.store_visual_marks(cx);
- vim.update_active_editor(cx, |vim, editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- editor.transact(cx, |editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- if selection.start == selection.end {
- Motion::Right.expand_selection(
- map,
- selection,
- count,
- true,
- &text_layout_details,
- );
- }
- if line_mode {
- // in Visual mode when the selection contains the newline at the end
- // of the line, we should exclude it.
- if !selection.is_empty() && selection.end.column() == 0 {
- selection.end = movement::left(map, selection.end);
+impl Vim {
+ pub fn substitute(
+ &mut self,
+ count: Option<usize>,
+ line_mode: bool,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.store_visual_marks(cx);
+ self.update_editor(cx, |vim, editor, cx| {
+ editor.set_clip_at_line_ends(false, cx);
+ editor.transact(cx, |editor, cx| {
+ let text_layout_details = editor.text_layout_details(cx);
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ if selection.start == selection.end {
+ Motion::Right.expand_selection(
+ map,
+ selection,
+ count,
+ true,
+ &text_layout_details,
+ );
}
- Motion::CurrentLine.expand_selection(
- map,
- selection,
- None,
- false,
- &text_layout_details,
- );
- if let Some((point, _)) = (Motion::FirstNonWhitespace {
- display_lines: false,
- })
- .move_point(
- map,
- selection.start,
- selection.goal,
- None,
- &text_layout_details,
- ) {
- selection.start = point;
+ if line_mode {
+ // in Visual mode when the selection contains the newline at the end
+ // of the line, we should exclude it.
+ if !selection.is_empty() && selection.end.column() == 0 {
+ selection.end = movement::left(map, selection.end);
+ }
+ Motion::CurrentLine.expand_selection(
+ map,
+ selection,
+ None,
+ false,
+ &text_layout_details,
+ );
+ if let Some((point, _)) = (Motion::FirstNonWhitespace {
+ display_lines: false,
+ })
+ .move_point(
+ map,
+ selection.start,
+ selection.goal,
+ None,
+ &text_layout_details,
+ ) {
+ selection.start = point;
+ }
}
- }
- })
+ })
+ });
+ vim.copy_selections_content(editor, line_mode, cx);
+ let selections = editor.selections.all::<Point>(cx).into_iter();
+ let edits = selections.map(|selection| (selection.start..selection.end, ""));
+ editor.edit(edits, cx);
});
- copy_selections_content(vim, editor, line_mode, cx);
- let selections = editor.selections.all::<Point>(cx).into_iter();
- let edits = selections.map(|selection| (selection.start..selection.end, ""));
- editor.edit(edits, cx);
});
- });
- vim.switch_mode(Mode::Insert, true, cx);
+ self.switch_mode(Mode::Insert, true, cx);
+ }
}
#[cfg(test)]
@@ -1,57 +1,64 @@
use crate::{motion::Motion, object::Object, Vim};
use collections::HashMap;
use editor::{display_map::ToDisplayPoint, Bias};
-use gpui::WindowContext;
use language::SelectionGoal;
+use ui::ViewContext;
-pub fn toggle_comments_motion(
- vim: &mut Vim,
- motion: Motion,
- times: Option<usize>,
- cx: &mut WindowContext,
-) {
- vim.stop_recording();
- vim.update_active_editor(cx, |_, editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.transact(cx, |editor, cx| {
- let mut selection_starts: HashMap<_, _> = Default::default();
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
- selection_starts.insert(selection.id, anchor);
- motion.expand_selection(map, selection, times, false, &text_layout_details);
+impl Vim {
+ pub fn toggle_comments_motion(
+ &mut self,
+ motion: Motion,
+ times: Option<usize>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.stop_recording(cx);
+ self.update_editor(cx, |_, editor, cx| {
+ let text_layout_details = editor.text_layout_details(cx);
+ editor.transact(cx, |editor, cx| {
+ let mut selection_starts: HashMap<_, _> = Default::default();
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
+ selection_starts.insert(selection.id, anchor);
+ motion.expand_selection(map, selection, times, false, &text_layout_details);
+ });
});
- });
- editor.toggle_comments(&Default::default(), cx);
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let anchor = selection_starts.remove(&selection.id).unwrap();
- selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ editor.toggle_comments(&Default::default(), cx);
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let anchor = selection_starts.remove(&selection.id).unwrap();
+ selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ });
});
});
});
- });
-}
+ }
-pub fn toggle_comments_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
- vim.stop_recording();
- vim.update_active_editor(cx, |_, editor, cx| {
- editor.transact(cx, |editor, cx| {
- let mut original_positions: HashMap<_, _> = Default::default();
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
- original_positions.insert(selection.id, anchor);
- object.expand_selection(map, selection, around);
+ pub fn toggle_comments_object(
+ &mut self,
+ object: Object,
+ around: bool,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.stop_recording(cx);
+ self.update_editor(cx, |_, editor, cx| {
+ editor.transact(cx, |editor, cx| {
+ let mut original_positions: HashMap<_, _> = Default::default();
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
+ original_positions.insert(selection.id, anchor);
+ object.expand_selection(map, selection, around);
+ });
});
- });
- editor.toggle_comments(&Default::default(), cx);
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let anchor = original_positions.remove(&selection.id).unwrap();
- selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ editor.toggle_comments(&Default::default(), cx);
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let anchor = original_positions.remove(&selection.id).unwrap();
+ selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
+ });
});
});
});
- });
+ }
}
@@ -8,181 +8,187 @@ use crate::{
};
use collections::HashMap;
use editor::{ClipboardSelection, Editor};
-use gpui::WindowContext;
+use gpui::ViewContext;
use language::Point;
use multi_buffer::MultiBufferRow;
-use ui::ViewContext;
-
-pub fn yank_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
- vim.update_active_editor(cx, |vim, editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.transact(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- let mut original_positions: HashMap<_, _> = Default::default();
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let original_position = (selection.head(), selection.goal);
- original_positions.insert(selection.id, original_position);
- motion.expand_selection(map, selection, times, true, &text_layout_details);
+
+struct HighlightOnYank;
+
+impl Vim {
+ pub fn yank_motion(
+ &mut self,
+ motion: Motion,
+ times: Option<usize>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.update_editor(cx, |vim, editor, cx| {
+ let text_layout_details = editor.text_layout_details(cx);
+ editor.transact(cx, |editor, cx| {
+ editor.set_clip_at_line_ends(false, cx);
+ let mut original_positions: HashMap<_, _> = Default::default();
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let original_position = (selection.head(), selection.goal);
+ original_positions.insert(selection.id, original_position);
+ motion.expand_selection(map, selection, times, true, &text_layout_details);
+ });
});
- });
- yank_selections_content(vim, editor, motion.linewise(), cx);
- editor.change_selections(None, cx, |s| {
- s.move_with(|_, selection| {
- let (head, goal) = original_positions.remove(&selection.id).unwrap();
- selection.collapse_to(head, goal);
+ vim.yank_selections_content(editor, motion.linewise(), cx);
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|_, selection| {
+ let (head, goal) = original_positions.remove(&selection.id).unwrap();
+ selection.collapse_to(head, goal);
+ });
});
});
});
- });
-}
+ }
-pub fn yank_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
- vim.update_active_editor(cx, |vim, editor, cx| {
- editor.transact(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- let mut original_positions: HashMap<_, _> = Default::default();
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let original_position = (selection.head(), selection.goal);
- object.expand_selection(map, selection, around);
- original_positions.insert(selection.id, original_position);
+ pub fn yank_object(&mut self, object: Object, around: bool, cx: &mut ViewContext<Self>) {
+ self.update_editor(cx, |vim, editor, cx| {
+ editor.transact(cx, |editor, cx| {
+ editor.set_clip_at_line_ends(false, cx);
+ let mut original_positions: HashMap<_, _> = Default::default();
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ let original_position = (selection.head(), selection.goal);
+ object.expand_selection(map, selection, around);
+ original_positions.insert(selection.id, original_position);
+ });
});
- });
- yank_selections_content(vim, editor, false, cx);
- editor.change_selections(None, cx, |s| {
- s.move_with(|_, selection| {
- let (head, goal) = original_positions.remove(&selection.id).unwrap();
- selection.collapse_to(head, goal);
+ vim.yank_selections_content(editor, false, cx);
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|_, selection| {
+ let (head, goal) = original_positions.remove(&selection.id).unwrap();
+ selection.collapse_to(head, goal);
+ });
});
});
});
- });
-}
-
-pub fn yank_selections_content(
- vim: &mut Vim,
- editor: &mut Editor,
- linewise: bool,
- cx: &mut ViewContext<Editor>,
-) {
- copy_selections_content_internal(vim, editor, linewise, true, cx);
-}
+ }
-pub fn copy_selections_content(
- vim: &mut Vim,
- editor: &mut Editor,
- linewise: bool,
- cx: &mut ViewContext<Editor>,
-) {
- copy_selections_content_internal(vim, editor, linewise, false, cx);
-}
+ pub fn yank_selections_content(
+ &mut self,
+ editor: &mut Editor,
+ linewise: bool,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ self.copy_selections_content_internal(editor, linewise, true, cx);
+ }
-struct HighlightOnYank;
+ pub fn copy_selections_content(
+ &mut self,
+ editor: &mut Editor,
+ linewise: bool,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ self.copy_selections_content_internal(editor, linewise, false, cx);
+ }
-fn copy_selections_content_internal(
- vim: &mut Vim,
- editor: &mut Editor,
- linewise: bool,
- is_yank: bool,
- cx: &mut ViewContext<Editor>,
-) {
- let selections = editor.selections.all_adjusted(cx);
- let buffer = editor.buffer().read(cx).snapshot(cx);
- let mut text = String::new();
- let mut clipboard_selections = Vec::with_capacity(selections.len());
- let mut ranges_to_highlight = Vec::new();
-
- vim.update_state(|state| {
- state.marks.insert(
+ fn copy_selections_content_internal(
+ &mut self,
+ editor: &mut Editor,
+ linewise: bool,
+ is_yank: bool,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ let selections = editor.selections.all_adjusted(cx);
+ let buffer = editor.buffer().read(cx).snapshot(cx);
+ let mut text = String::new();
+ let mut clipboard_selections = Vec::with_capacity(selections.len());
+ let mut ranges_to_highlight = Vec::new();
+
+ self.marks.insert(
"[".to_string(),
selections
.iter()
.map(|s| buffer.anchor_before(s.start))
.collect(),
);
- state.marks.insert(
+ self.marks.insert(
"]".to_string(),
selections
.iter()
.map(|s| buffer.anchor_after(s.end))
.collect(),
- )
- });
-
- {
- let mut is_first = true;
- for selection in selections.iter() {
- let mut start = selection.start;
- let end = selection.end;
- if is_first {
- is_first = false;
- } else {
- text.push_str("\n");
- }
- let initial_len = text.len();
-
- // if the file does not end with \n, and our line-mode selection ends on
- // that line, we will have expanded the start of the selection to ensure it
- // contains a newline (so that delete works as expected). We undo that change
- // here.
- let is_last_line = linewise
- && end.row == buffer.max_buffer_row().0
- && buffer.max_point().column > 0
- && start.row < buffer.max_buffer_row().0
- && start == Point::new(start.row, buffer.line_len(MultiBufferRow(start.row)));
-
- if is_last_line {
- start = Point::new(start.row + 1, 0);
- }
-
- let start_anchor = buffer.anchor_after(start);
- let end_anchor = buffer.anchor_before(end);
- ranges_to_highlight.push(start_anchor..end_anchor);
+ );
- for chunk in buffer.text_for_range(start..end) {
- text.push_str(chunk);
- }
- if is_last_line {
- text.push_str("\n");
+ {
+ let mut is_first = true;
+ for selection in selections.iter() {
+ let mut start = selection.start;
+ let end = selection.end;
+ if is_first {
+ is_first = false;
+ } else {
+ text.push_str("\n");
+ }
+ let initial_len = text.len();
+
+ // if the file does not end with \n, and our line-mode selection ends on
+ // that line, we will have expanded the start of the selection to ensure it
+ // contains a newline (so that delete works as expected). We undo that change
+ // here.
+ let is_last_line = linewise
+ && end.row == buffer.max_buffer_row().0
+ && buffer.max_point().column > 0
+ && start.row < buffer.max_buffer_row().0
+ && start == Point::new(start.row, buffer.line_len(MultiBufferRow(start.row)));
+
+ if is_last_line {
+ start = Point::new(start.row + 1, 0);
+ }
+
+ let start_anchor = buffer.anchor_after(start);
+ let end_anchor = buffer.anchor_before(end);
+ ranges_to_highlight.push(start_anchor..end_anchor);
+
+ for chunk in buffer.text_for_range(start..end) {
+ text.push_str(chunk);
+ }
+ if is_last_line {
+ text.push_str("\n");
+ }
+ clipboard_selections.push(ClipboardSelection {
+ len: text.len() - initial_len,
+ is_entire_line: linewise,
+ first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
+ });
}
- clipboard_selections.push(ClipboardSelection {
- len: text.len() - initial_len,
- is_entire_line: linewise,
- first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
- });
}
- }
- let selected_register = vim.update_state(|state| state.selected_register.take());
- vim.write_registers(
- Register {
- text: text.into(),
- clipboard_selections: Some(clipboard_selections),
- },
- selected_register,
- is_yank,
- linewise,
- cx,
- );
-
- if !is_yank || vim.state().mode == Mode::Visual {
- return;
- }
+ let selected_register = self.selected_register.take();
+ Vim::update_globals(cx, |globals, cx| {
+ globals.write_registers(
+ Register {
+ text: text.into(),
+ clipboard_selections: Some(clipboard_selections),
+ },
+ selected_register,
+ is_yank,
+ linewise,
+ cx,
+ )
+ });
- editor.highlight_background::<HighlightOnYank>(
- &ranges_to_highlight,
- |colors| colors.editor_document_highlight_read_background,
- cx,
- );
- cx.spawn(|this, mut cx| async move {
- cx.background_executor()
- .timer(Duration::from_millis(200))
- .await;
- this.update(&mut cx, |editor, cx| {
- editor.clear_background_highlights::<HighlightOnYank>(cx)
+ if !is_yank || self.mode == Mode::Visual {
+ return;
+ }
+
+ editor.highlight_background::<HighlightOnYank>(
+ &ranges_to_highlight,
+ |colors| colors.editor_document_highlight_read_background,
+ cx,
+ );
+ cx.spawn(|this, mut cx| async move {
+ cx.background_executor()
+ .timer(Duration::from_millis(200))
+ .await;
+ this.update(&mut cx, |editor, cx| {
+ editor.clear_background_highlights::<HighlightOnYank>(cx)
+ })
+ .ok();
})
- .ok();
- })
- .detach();
+ .detach();
+ }
}
@@ -2,24 +2,21 @@ use std::ops::Range;
use crate::{
motion::{coerce_punctuation, right},
- normal::normal_object,
state::Mode,
- visual::visual_object,
Vim,
};
use editor::{
display_map::{DisplaySnapshot, ToDisplayPoint},
movement::{self, FindRange},
- Bias, DisplayPoint,
+ Bias, DisplayPoint, Editor,
};
use itertools::Itertools;
-use gpui::{actions, impl_actions, ViewContext, WindowContext};
+use gpui::{actions, impl_actions, ViewContext};
use language::{char_kind, BufferSnapshot, CharKind, Point, Selection};
use multi_buffer::MultiBufferRow;
use serde::Deserialize;
-use workspace::Workspace;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
pub enum Object {
@@ -65,48 +62,58 @@ actions!(
]
);
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(
- |_: &mut Workspace, &Word { ignore_punctuation }: &Word, cx: _| {
- object(Object::Word { ignore_punctuation }, cx)
+pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(
+ editor,
+ cx,
+ |vim, &Word { ignore_punctuation }: &Word, cx| {
+ vim.object(Object::Word { ignore_punctuation }, cx)
},
);
- workspace.register_action(|_: &mut Workspace, _: &Tag, cx: _| object(Object::Tag, cx));
- workspace
- .register_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx));
- workspace
- .register_action(|_: &mut Workspace, _: &Paragraph, cx: _| object(Object::Paragraph, cx));
- workspace.register_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx));
- workspace
- .register_action(|_: &mut Workspace, _: &BackQuotes, cx: _| object(Object::BackQuotes, cx));
- workspace.register_action(|_: &mut Workspace, _: &DoubleQuotes, cx: _| {
- object(Object::DoubleQuotes, cx)
+ Vim::action(editor, cx, |vim, _: &Tag, cx| vim.object(Object::Tag, cx));
+ Vim::action(editor, cx, |vim, _: &Sentence, cx| {
+ vim.object(Object::Sentence, cx)
});
- workspace.register_action(|_: &mut Workspace, _: &Parentheses, cx: _| {
- object(Object::Parentheses, cx)
+ Vim::action(editor, cx, |vim, _: &Paragraph, cx| {
+ vim.object(Object::Paragraph, cx)
});
- workspace.register_action(|_: &mut Workspace, _: &SquareBrackets, cx: _| {
- object(Object::SquareBrackets, cx)
+ Vim::action(editor, cx, |vim, _: &Quotes, cx| {
+ vim.object(Object::Quotes, cx)
});
- workspace.register_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| {
- object(Object::CurlyBrackets, cx)
+ Vim::action(editor, cx, |vim, _: &BackQuotes, cx| {
+ vim.object(Object::BackQuotes, cx)
});
- workspace.register_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| {
- object(Object::AngleBrackets, cx)
+ Vim::action(editor, cx, |vim, _: &DoubleQuotes, cx| {
+ vim.object(Object::DoubleQuotes, cx)
});
- workspace.register_action(|_: &mut Workspace, _: &VerticalBars, cx: _| {
- object(Object::VerticalBars, cx)
+ Vim::action(editor, cx, |vim, _: &Parentheses, cx| {
+ vim.object(Object::Parentheses, cx)
+ });
+ Vim::action(editor, cx, |vim, _: &SquareBrackets, cx| {
+ vim.object(Object::SquareBrackets, cx)
+ });
+ Vim::action(editor, cx, |vim, _: &CurlyBrackets, cx| {
+ vim.object(Object::CurlyBrackets, cx)
+ });
+ Vim::action(editor, cx, |vim, _: &AngleBrackets, cx| {
+ vim.object(Object::AngleBrackets, cx)
+ });
+ Vim::action(editor, cx, |vim, _: &VerticalBars, cx| {
+ vim.object(Object::VerticalBars, cx)
+ });
+ Vim::action(editor, cx, |vim, _: &Argument, cx| {
+ vim.object(Object::Argument, cx)
});
- workspace
- .register_action(|_: &mut Workspace, _: &Argument, cx: _| object(Object::Argument, cx));
}
-fn object(object: Object, cx: &mut WindowContext) {
- match Vim::read(cx).state().mode {
- Mode::Normal => normal_object(object, cx),
- Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_object(object, cx),
- Mode::Insert | Mode::Replace => {
- // Shouldn't execute a text object in insert mode. Ignoring
+impl Vim {
+ fn object(&mut self, object: Object, cx: &mut ViewContext<Self>) {
+ match self.mode {
+ Mode::Normal => self.normal_object(object, cx),
+ Mode::Visual | Mode::VisualLine | Mode::VisualBlock => self.visual_object(object, cx),
+ Mode::Insert | Mode::Replace => {
+ // Shouldn't execute a text object in insert mode. Ignoring
+ }
}
}
}
@@ -3,38 +3,33 @@ use crate::{
state::Mode,
Vim,
};
-use editor::{display_map::ToDisplayPoint, Bias, ToPoint};
-use gpui::{actions, ViewContext, WindowContext};
+use editor::{display_map::ToDisplayPoint, Bias, Editor, ToPoint};
+use gpui::{actions, ViewContext};
use language::{AutoindentMode, Point};
use std::ops::Range;
use std::sync::Arc;
-use workspace::Workspace;
actions!(vim, [ToggleReplace, UndoReplace]);
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_, _: &ToggleReplace, cx: &mut ViewContext<Workspace>| {
- Vim::update(cx, |vim, cx| {
- vim.update_state(|state| state.replacements = vec![]);
- vim.start_recording(cx);
- vim.switch_mode(Mode::Replace, false, cx);
- });
+pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(editor, cx, |vim, _: &ToggleReplace, cx| {
+ vim.replacements = vec![];
+ vim.start_recording(cx);
+ vim.switch_mode(Mode::Replace, false, cx);
});
- workspace.register_action(|_, _: &UndoReplace, cx: &mut ViewContext<Workspace>| {
- Vim::update(cx, |vim, cx| {
- if vim.state().mode != Mode::Replace {
- return;
- }
- let count = vim.take_count(cx);
- undo_replace(vim, count, cx)
- });
+ Vim::action(editor, cx, |vim, _: &UndoReplace, cx| {
+ if vim.mode != Mode::Replace {
+ return;
+ }
+ let count = vim.take_count(cx);
+ vim.undo_replace(count, cx)
});
}
-pub(crate) fn multi_replace(text: Arc<str>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |vim, editor, cx| {
+impl Vim {
+ pub(crate) fn multi_replace(&mut self, text: Arc<str>, cx: &mut ViewContext<Self>) {
+ self.update_editor(cx, |vim, editor, cx| {
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
let map = editor.snapshot(cx);
@@ -58,11 +53,7 @@ pub(crate) fn multi_replace(text: Arc<str>, cx: &mut WindowContext) {
.buffer_snapshot
.text_for_range(replace_range.clone())
.collect();
- vim.update_state(|state| {
- state
- .replacements
- .push((replace_range.clone(), current_text))
- });
+ vim.replacements.push((replace_range.clone(), current_text));
(replace_range, text.clone())
})
.collect::<Vec<_>>();
@@ -83,58 +74,56 @@ pub(crate) fn multi_replace(text: Arc<str>, cx: &mut WindowContext) {
editor.set_clip_at_line_ends(true, cx);
});
});
- });
-}
+ }
-fn undo_replace(vim: &mut Vim, maybe_times: Option<usize>, cx: &mut WindowContext) {
- vim.update_active_editor(cx, |vim, editor, cx| {
- editor.transact(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- let map = editor.snapshot(cx);
- let selections = editor.selections.all::<Point>(cx);
- let mut new_selections = vec![];
- let edits: Vec<(Range<Point>, String)> = selections
- .into_iter()
- .filter_map(|selection| {
- let end = selection.head();
- let start = motion::backspace(
- &map,
- end.to_display_point(&map),
- maybe_times.unwrap_or(1),
- )
- .to_point(&map);
- new_selections.push(
- map.buffer_snapshot.anchor_before(start)
- ..map.buffer_snapshot.anchor_before(start),
- );
+ fn undo_replace(&mut self, maybe_times: Option<usize>, cx: &mut ViewContext<Self>) {
+ self.update_editor(cx, |vim, editor, cx| {
+ editor.transact(cx, |editor, cx| {
+ editor.set_clip_at_line_ends(false, cx);
+ let map = editor.snapshot(cx);
+ let selections = editor.selections.all::<Point>(cx);
+ let mut new_selections = vec![];
+ let edits: Vec<(Range<Point>, String)> = selections
+ .into_iter()
+ .filter_map(|selection| {
+ let end = selection.head();
+ let start = motion::backspace(
+ &map,
+ end.to_display_point(&map),
+ maybe_times.unwrap_or(1),
+ )
+ .to_point(&map);
+ new_selections.push(
+ map.buffer_snapshot.anchor_before(start)
+ ..map.buffer_snapshot.anchor_before(start),
+ );
- let mut undo = None;
- let edit_range = start..end;
- for (i, (range, inverse)) in vim.state().replacements.iter().rev().enumerate() {
- if range.start.to_point(&map.buffer_snapshot) <= edit_range.start
- && range.end.to_point(&map.buffer_snapshot) >= edit_range.end
- {
- undo = Some(inverse.clone());
- vim.update_state(|state| {
- state.replacements.remove(state.replacements.len() - i - 1);
- });
- break;
+ let mut undo = None;
+ let edit_range = start..end;
+ for (i, (range, inverse)) in vim.replacements.iter().rev().enumerate() {
+ if range.start.to_point(&map.buffer_snapshot) <= edit_range.start
+ && range.end.to_point(&map.buffer_snapshot) >= edit_range.end
+ {
+ undo = Some(inverse.clone());
+ vim.replacements.remove(vim.replacements.len() - i - 1);
+ break;
+ }
}
- }
- Some((edit_range, undo?))
- })
- .collect::<Vec<_>>();
+ Some((edit_range, undo?))
+ })
+ .collect::<Vec<_>>();
- editor.buffer().update(cx, |buffer, cx| {
- buffer.edit(edits, None, cx);
- });
+ editor.buffer().update(cx, |buffer, cx| {
+ buffer.edit(edits, None, cx);
+ });
- editor.change_selections(None, cx, |s| {
- s.select_ranges(new_selections);
+ editor.change_selections(None, cx, |s| {
+ s.select_ranges(new_selections);
+ });
+ editor.set_clip_at_line_ends(true, cx);
});
- editor.set_clip_at_line_ends(true, cx);
});
- });
+ }
}
#[cfg(test)]
@@ -1,14 +1,19 @@
+use std::borrow::BorrowMut;
use std::{fmt::Display, ops::Range, sync::Arc};
+use crate::command::command_interceptor;
use crate::normal::repeat::Replayer;
use crate::surrounds::SurroundsType;
use crate::{motion::Motion, object::Object};
+use crate::{UseSystemClipboard, Vim, VimSettings};
use collections::HashMap;
-use editor::{Anchor, ClipboardSelection};
-use gpui::{Action, ClipboardEntry, ClipboardItem, KeyContext};
-use language::{CursorShape, Selection, TransactionId};
+use command_palette_hooks::{CommandPaletteFilter, CommandPaletteInterceptor};
+use editor::{Anchor, ClipboardSelection, Editor};
+use gpui::{Action, AppContext, BorrowAppContext, ClipboardEntry, ClipboardItem, Global};
+use language::Point;
use serde::{Deserialize, Serialize};
-use ui::SharedString;
+use settings::{Settings, SettingsStore};
+use ui::{SharedString, ViewContext};
use workspace::searchable::Direction;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
@@ -75,32 +80,6 @@ pub enum Operator {
ToggleComments,
}
-#[derive(Default, Clone)]
-pub struct EditorState {
- pub mode: Mode,
- pub last_mode: Mode,
-
- /// pre_count is the number before an operator is specified (3 in 3d2d)
- pub pre_count: Option<usize>,
- /// post_count is the number after an operator is specified (2 in 3d2d)
- pub post_count: Option<usize>,
-
- pub operator_stack: Vec<Operator>,
- pub replacements: Vec<(Range<editor::Anchor>, String)>,
-
- pub marks: HashMap<String, Vec<Anchor>>,
- pub stored_visual_mode: Option<(Mode, Vec<bool>)>,
- pub change_list: Vec<Vec<Anchor>>,
- pub change_list_position: Option<usize>,
-
- pub current_tx: Option<TransactionId>,
- pub current_anchor: Option<Selection<Anchor>>,
- pub undo_modes: HashMap<TransactionId, Mode>,
-
- pub selected_register: Option<char>,
- pub search: SearchState,
-}
-
#[derive(Default, Clone, Debug)]
pub enum RecordedSelection {
#[default]
@@ -161,7 +140,7 @@ impl From<String> for Register {
}
#[derive(Default, Clone)]
-pub struct WorkspaceState {
+pub struct VimGlobals {
pub last_find: Option<Motion>,
pub dot_recording: bool,
@@ -182,6 +161,232 @@ pub struct WorkspaceState {
pub registers: HashMap<char, Register>,
pub recordings: HashMap<char, Vec<ReplayableAction>>,
}
+impl Global for VimGlobals {}
+
+impl VimGlobals {
+ pub(crate) fn register(cx: &mut AppContext) {
+ cx.set_global(VimGlobals::default());
+
+ cx.observe_keystrokes(|event, cx| {
+ let Some(action) = event.action.as_ref().map(|action| action.boxed_clone()) else {
+ return;
+ };
+ Vim::globals(cx).observe_action(action.boxed_clone())
+ })
+ .detach();
+
+ cx.observe_global::<SettingsStore>(move |cx| {
+ if Vim::enabled(cx) {
+ CommandPaletteFilter::update_global(cx, |filter, _| {
+ filter.show_namespace(Vim::NAMESPACE);
+ });
+ CommandPaletteInterceptor::update_global(cx, |interceptor, _| {
+ interceptor.set(Box::new(command_interceptor));
+ });
+ } else {
+ *Vim::globals(cx) = VimGlobals::default();
+ CommandPaletteInterceptor::update_global(cx, |interceptor, _| {
+ interceptor.clear();
+ });
+ CommandPaletteFilter::update_global(cx, |filter, _| {
+ filter.hide_namespace(Vim::NAMESPACE);
+ });
+ }
+ })
+ .detach();
+ }
+
+ pub(crate) fn write_registers(
+ &mut self,
+ content: Register,
+ register: Option<char>,
+ is_yank: bool,
+ linewise: bool,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ if let Some(register) = register {
+ let lower = register.to_lowercase().next().unwrap_or(register);
+ if lower != register {
+ let current = self.registers.entry(lower).or_default();
+ current.text = (current.text.to_string() + &content.text).into();
+ // not clear how to support appending to registers with multiple cursors
+ current.clipboard_selections.take();
+ let yanked = current.clone();
+ self.registers.insert('"', yanked);
+ } else {
+ self.registers.insert('"', content.clone());
+ match lower {
+ '_' | ':' | '.' | '%' | '#' | '=' | '/' => {}
+ '+' => {
+ cx.write_to_clipboard(content.into());
+ }
+ '*' => {
+ #[cfg(target_os = "linux")]
+ cx.write_to_primary(content.into());
+ #[cfg(not(target_os = "linux"))]
+ cx.write_to_clipboard(content.into());
+ }
+ '"' => {
+ self.registers.insert('0', content.clone());
+ self.registers.insert('"', content);
+ }
+ _ => {
+ self.registers.insert(lower, content);
+ }
+ }
+ }
+ } else {
+ let setting = VimSettings::get_global(cx).use_system_clipboard;
+ if setting == UseSystemClipboard::Always
+ || setting == UseSystemClipboard::OnYank && is_yank
+ {
+ self.last_yank.replace(content.text.clone());
+ cx.write_to_clipboard(content.clone().into());
+ } else {
+ self.last_yank = cx
+ .read_from_clipboard()
+ .and_then(|item| item.text().map(|string| string.into()));
+ }
+
+ self.registers.insert('"', content.clone());
+ if is_yank {
+ self.registers.insert('0', content);
+ } else {
+ let contains_newline = content.text.contains('\n');
+ if !contains_newline {
+ self.registers.insert('-', content.clone());
+ }
+ if linewise || contains_newline {
+ let mut content = content;
+ for i in '1'..'8' {
+ if let Some(moved) = self.registers.insert(i, content) {
+ content = moved;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ pub(crate) fn read_register(
+ &mut self,
+ register: Option<char>,
+ editor: Option<&mut Editor>,
+ cx: &ViewContext<Editor>,
+ ) -> Option<Register> {
+ let Some(register) = register.filter(|reg| *reg != '"') else {
+ let setting = VimSettings::get_global(cx).use_system_clipboard;
+ return match setting {
+ UseSystemClipboard::Always => cx.read_from_clipboard().map(|item| item.into()),
+ UseSystemClipboard::OnYank if self.system_clipboard_is_newer(cx) => {
+ cx.read_from_clipboard().map(|item| item.into())
+ }
+ _ => self.registers.get(&'"').cloned(),
+ };
+ };
+ let lower = register.to_lowercase().next().unwrap_or(register);
+ match lower {
+ '_' | ':' | '.' | '#' | '=' => None,
+ '+' => cx.read_from_clipboard().map(|item| item.into()),
+ '*' => {
+ #[cfg(target_os = "linux")]
+ {
+ cx.read_from_primary().map(|item| item.into())
+ }
+ #[cfg(not(target_os = "linux"))]
+ {
+ cx.read_from_clipboard().map(|item| item.into())
+ }
+ }
+ '%' => editor.and_then(|editor| {
+ let selection = editor.selections.newest::<Point>(cx);
+ if let Some((_, buffer, _)) = editor
+ .buffer()
+ .read(cx)
+ .excerpt_containing(selection.head(), cx)
+ {
+ buffer
+ .read(cx)
+ .file()
+ .map(|file| file.path().to_string_lossy().to_string().into())
+ } else {
+ None
+ }
+ }),
+ _ => self.registers.get(&lower).cloned(),
+ }
+ }
+
+ fn system_clipboard_is_newer(&self, cx: &ViewContext<Editor>) -> bool {
+ cx.read_from_clipboard().is_some_and(|item| {
+ if let Some(last_state) = &self.last_yank {
+ Some(last_state.as_ref()) != item.text().as_deref()
+ } else {
+ true
+ }
+ })
+ }
+
+ pub fn observe_action(&mut self, action: Box<dyn Action>) {
+ if self.dot_recording {
+ self.recorded_actions
+ .push(ReplayableAction::Action(action.boxed_clone()));
+
+ if self.stop_recording_after_next_action {
+ self.dot_recording = false;
+ self.stop_recording_after_next_action = false;
+ }
+ }
+ if self.replayer.is_none() {
+ if let Some(recording_register) = self.recording_register {
+ self.recordings
+ .entry(recording_register)
+ .or_default()
+ .push(ReplayableAction::Action(action));
+ }
+ }
+ }
+
+ pub fn observe_insertion(&mut self, text: &Arc<str>, range_to_replace: Option<Range<isize>>) {
+ if self.ignore_current_insertion {
+ self.ignore_current_insertion = false;
+ return;
+ }
+ if self.dot_recording {
+ self.recorded_actions.push(ReplayableAction::Insertion {
+ text: text.clone(),
+ utf16_range_to_replace: range_to_replace.clone(),
+ });
+ if self.stop_recording_after_next_action {
+ self.dot_recording = false;
+ self.stop_recording_after_next_action = false;
+ }
+ }
+ if let Some(recording_register) = self.recording_register {
+ self.recordings.entry(recording_register).or_default().push(
+ ReplayableAction::Insertion {
+ text: text.clone(),
+ utf16_range_to_replace: range_to_replace,
+ },
+ );
+ }
+ }
+}
+
+impl Vim {
+ pub fn globals(cx: &mut AppContext) -> &mut VimGlobals {
+ cx.global_mut::<VimGlobals>()
+ }
+
+ pub fn update_globals<C, R>(cx: &mut C, f: impl FnOnce(&mut VimGlobals, &mut C) -> R) -> R
+ where
+ C: BorrowMut<AppContext>,
+ {
+ cx.update_global(f)
+ }
+}
#[derive(Debug)]
pub enum ReplayableAction {
@@ -218,93 +423,6 @@ pub struct SearchState {
pub prior_mode: Mode,
}
-impl EditorState {
- pub fn cursor_shape(&self) -> CursorShape {
- match self.mode {
- Mode::Normal => {
- if self.operator_stack.is_empty() {
- CursorShape::Block
- } else {
- CursorShape::Underscore
- }
- }
- Mode::Replace => CursorShape::Underscore,
- Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
- Mode::Insert => CursorShape::Bar,
- }
- }
-
- pub fn editor_input_enabled(&self) -> bool {
- match self.mode {
- Mode::Insert => {
- if let Some(operator) = self.operator_stack.last() {
- !operator.is_waiting(self.mode)
- } else {
- true
- }
- }
- Mode::Normal | Mode::Replace | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
- false
- }
- }
- }
-
- pub fn should_autoindent(&self) -> bool {
- !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
- }
-
- pub fn clip_at_line_ends(&self) -> bool {
- match self.mode {
- Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::Replace => {
- false
- }
- Mode::Normal => true,
- }
- }
-
- pub fn active_operator(&self) -> Option<Operator> {
- self.operator_stack.last().cloned()
- }
-
- pub fn keymap_context_layer(&self) -> KeyContext {
- let mut context = KeyContext::new_with_defaults();
-
- let mut mode = match self.mode {
- Mode::Normal => "normal",
- Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
- Mode::Insert => "insert",
- Mode::Replace => "replace",
- }
- .to_string();
-
- let mut operator_id = "none";
-
- let active_operator = self.active_operator();
- if active_operator.is_none() && self.pre_count.is_some()
- || active_operator.is_some() && self.post_count.is_some()
- {
- context.add("VimCount");
- }
-
- if let Some(active_operator) = active_operator {
- if active_operator.is_waiting(self.mode) {
- mode = "waiting".to_string();
- } else {
- mode = "operator".to_string();
- operator_id = active_operator.id();
- }
- }
-
- if mode != "waiting" && mode != "insert" && mode != "replace" {
- context.add("VimControl");
- }
- context.set("vim_mode", mode);
- context.set("vim_operator", operator_id);
-
- context
- }
-}
-
impl Operator {
pub fn id(&self) -> &'static str {
match self {
@@ -5,10 +5,10 @@ use crate::{
Vim,
};
use editor::{movement, scroll::Autoscroll, Bias};
-use gpui::WindowContext;
use language::BracketPair;
use serde::Deserialize;
use std::sync::Arc;
+use ui::ViewContext;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SurroundsType {
@@ -27,12 +27,17 @@ impl<'de> Deserialize<'de> for SurroundsType {
}
}
-pub fn add_surrounds(text: Arc<str>, target: SurroundsType, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- vim.stop_recording();
- let count = vim.take_count(cx);
- let mode = vim.state().mode;
- vim.update_active_editor(cx, |_, editor, cx| {
+impl Vim {
+ pub fn add_surrounds(
+ &mut self,
+ text: Arc<str>,
+ target: SurroundsType,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.stop_recording(cx);
+ let count = self.take_count(cx);
+ let mode = self.mode;
+ self.update_editor(cx, |_, editor, cx| {
let text_layout_details = editor.text_layout_details(cx);
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
@@ -128,13 +133,11 @@ pub fn add_surrounds(text: Arc<str>, target: SurroundsType, cx: &mut WindowConte
});
});
});
- vim.switch_mode(Mode::Normal, false, cx);
- });
-}
+ self.switch_mode(Mode::Normal, false, cx);
+ }
-pub fn delete_surrounds(text: Arc<str>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- vim.stop_recording();
+ pub fn delete_surrounds(&mut self, text: Arc<str>, cx: &mut ViewContext<Self>) {
+ self.stop_recording(cx);
// only legitimate surrounds can be removed
let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
@@ -147,7 +150,7 @@ pub fn delete_surrounds(text: Arc<str>, cx: &mut WindowContext) {
};
let surround = pair.end != *text;
- vim.update_active_editor(cx, |_, editor, cx| {
+ self.update_editor(cx, |_, editor, cx| {
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
@@ -224,14 +227,12 @@ pub fn delete_surrounds(text: Arc<str>, cx: &mut WindowContext) {
editor.set_clip_at_line_ends(true, cx);
});
});
- });
-}
+ }
-pub fn change_surrounds(text: Arc<str>, target: Object, cx: &mut WindowContext) {
- if let Some(will_replace_pair) = object_to_bracket_pair(target) {
- Vim::update(cx, |vim, cx| {
- vim.stop_recording();
- vim.update_active_editor(cx, |_, editor, cx| {
+ pub fn change_surrounds(&mut self, text: Arc<str>, target: Object, cx: &mut ViewContext<Self>) {
+ if let Some(will_replace_pair) = object_to_bracket_pair(target) {
+ self.stop_recording(cx);
+ self.update_editor(cx, |_, editor, cx| {
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
@@ -332,65 +333,67 @@ pub fn change_surrounds(text: Arc<str>, target: Object, cx: &mut WindowContext)
});
});
});
- });
+ }
}
-}
-/// Checks if any of the current cursors are surrounded by a valid pair of brackets.
-///
-/// This method supports multiple cursors and checks each cursor for a valid pair of brackets.
-/// A pair of brackets is considered valid if it is well-formed and properly closed.
-///
-/// If a valid pair of brackets is found, the method returns `true` and the cursor is automatically moved to the start of the bracket pair.
-/// If no valid pair of brackets is found for any cursor, the method returns `false`.
-pub fn check_and_move_to_valid_bracket_pair(
- vim: &mut Vim,
- object: Object,
- cx: &mut WindowContext,
-) -> bool {
- let mut valid = false;
- if let Some(pair) = object_to_bracket_pair(object) {
- vim.update_active_editor(cx, |_, editor, cx| {
- editor.transact(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- let (display_map, selections) = editor.selections.all_adjusted_display(cx);
- let mut anchors = Vec::new();
+ /// Checks if any of the current cursors are surrounded by a valid pair of brackets.
+ ///
+ /// This method supports multiple cursors and checks each cursor for a valid pair of brackets.
+ /// A pair of brackets is considered valid if it is well-formed and properly closed.
+ ///
+ /// If a valid pair of brackets is found, the method returns `true` and the cursor is automatically moved to the start of the bracket pair.
+ /// If no valid pair of brackets is found for any cursor, the method returns `false`.
+ pub fn check_and_move_to_valid_bracket_pair(
+ &mut self,
+ object: Object,
+ cx: &mut ViewContext<Self>,
+ ) -> bool {
+ let mut valid = false;
+ if let Some(pair) = object_to_bracket_pair(object) {
+ self.update_editor(cx, |_, editor, cx| {
+ editor.transact(cx, |editor, cx| {
+ editor.set_clip_at_line_ends(false, cx);
+ let (display_map, selections) = editor.selections.all_adjusted_display(cx);
+ let mut anchors = Vec::new();
- for selection in &selections {
- let start = selection.start.to_offset(&display_map, Bias::Left);
- if let Some(range) = object.range(&display_map, selection.clone(), true) {
- // If the current parenthesis object is single-line,
- // then we need to filter whether it is the current line or not
- if object.is_multiline()
- || (!object.is_multiline()
- && selection.start.row() == range.start.row()
- && selection.end.row() == range.end.row())
- {
- valid = true;
- let mut chars_and_offset = display_map
- .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
- .peekable();
- while let Some((ch, offset)) = chars_and_offset.next() {
- if ch.to_string() == pair.start {
- anchors.push(offset..offset);
- break;
+ for selection in &selections {
+ let start = selection.start.to_offset(&display_map, Bias::Left);
+ if let Some(range) = object.range(&display_map, selection.clone(), true) {
+ // If the current parenthesis object is single-line,
+ // then we need to filter whether it is the current line or not
+ if object.is_multiline()
+ || (!object.is_multiline()
+ && selection.start.row() == range.start.row()
+ && selection.end.row() == range.end.row())
+ {
+ valid = true;
+ let mut chars_and_offset = display_map
+ .buffer_chars_at(
+ range.start.to_offset(&display_map, Bias::Left),
+ )
+ .peekable();
+ while let Some((ch, offset)) = chars_and_offset.next() {
+ if ch.to_string() == pair.start {
+ anchors.push(offset..offset);
+ break;
+ }
}
+ } else {
+ anchors.push(start..start)
}
} else {
anchors.push(start..start)
}
- } else {
- anchors.push(start..start)
}
- }
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.select_ranges(anchors);
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.select_ranges(anchors);
+ });
+ editor.set_clip_at_line_ends(true, cx);
});
- editor.set_clip_at_line_ends(true, cx);
});
- });
+ }
+ return valid;
}
- return valid;
}
fn find_surround_pair<'a>(pairs: &'a [BracketPair], ch: &str) -> Option<&'a BracketPair> {
@@ -17,7 +17,7 @@ use indoc::indoc;
use search::BufferSearchBar;
use workspace::WorkspaceSettings;
-use crate::{insert::NormalBefore, motion, state::Mode, ModeIndicator};
+use crate::{insert::NormalBefore, motion, state::Mode};
#[gpui::test]
async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
@@ -51,7 +51,7 @@ async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
cx.assert_editor_state("hjklˇ");
// Selections aren't changed if editor is blurred but vim-mode is still disabled.
- cx.set_state("«hjklˇ»", Mode::Normal);
+ cx.cx.set_state("«hjklˇ»");
cx.assert_editor_state("«hjklˇ»");
cx.update_editor(|_, cx| cx.blur());
cx.assert_editor_state("«hjklˇ»");
@@ -279,59 +279,6 @@ async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
}
-#[gpui::test]
-async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- let mode_indicator = cx.workspace(|workspace, cx| {
- let status_bar = workspace.status_bar().read(cx);
- let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
- assert!(mode_indicator.is_some());
- mode_indicator.unwrap()
- });
-
- assert_eq!(
- cx.workspace(|_, cx| mode_indicator.read(cx).mode),
- Some(Mode::Normal)
- );
-
- // shows the correct mode
- cx.simulate_keystrokes("i");
- assert_eq!(
- cx.workspace(|_, cx| mode_indicator.read(cx).mode),
- Some(Mode::Insert)
- );
- cx.simulate_keystrokes("escape shift-r");
- assert_eq!(
- cx.workspace(|_, cx| mode_indicator.read(cx).mode),
- Some(Mode::Replace)
- );
-
- // shows even in search
- cx.simulate_keystrokes("escape v /");
- assert_eq!(
- cx.workspace(|_, cx| mode_indicator.read(cx).mode),
- Some(Mode::Visual)
- );
-
- // hides if vim mode is disabled
- cx.disable_vim();
- cx.run_until_parked();
- cx.workspace(|workspace, cx| {
- let status_bar = workspace.status_bar().read(cx);
- let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
- assert!(mode_indicator.read(cx).mode.is_none());
- });
-
- cx.enable_vim();
- cx.run_until_parked();
- cx.workspace(|workspace, cx| {
- let status_bar = workspace.status_bar().read(cx);
- let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
- assert!(mode_indicator.read(cx).mode.is_some());
- });
-}
-
#[gpui::test]
async fn test_word_characters(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new_typescript(cx).await;
@@ -10,7 +10,7 @@ use language::language_settings::{AllLanguageSettings, SoftWrap};
use util::test::marked_text_offsets;
use super::{neovim_connection::NeovimConnection, VimTestContext};
-use crate::{state::Mode, Vim};
+use crate::state::{Mode, VimGlobals};
pub struct NeovimBackedTestContext {
cx: VimTestContext,
@@ -263,8 +263,7 @@ impl NeovimBackedTestContext {
state: self.shared_state().await,
neovim: self.neovim.read_register(register).await,
editor: self.update(|cx| {
- Vim::read(cx)
- .workspace_state
+ cx.global::<VimGlobals>()
.registers
.get(®ister)
.cloned()
@@ -1,7 +1,7 @@
use std::ops::{Deref, DerefMut};
use editor::test::editor_lsp_test_context::EditorLspTestContext;
-use gpui::{Context, SemanticVersion, View, VisualContext};
+use gpui::{Context, SemanticVersion, UpdateGlobal, View, VisualContext};
use search::{project_search::ProjectSearchBar, BufferSearchBar};
use crate::{state::Operator, *};
@@ -12,7 +12,7 @@ pub struct VimTestContext {
impl VimTestContext {
pub fn init(cx: &mut gpui::TestAppContext) {
- if cx.has_global::<Vim>() {
+ if cx.has_global::<VimGlobals>() {
return;
}
cx.update(|cx| {
@@ -119,23 +119,31 @@ impl VimTestContext {
}
pub fn mode(&mut self) -> Mode {
- self.cx.read(|cx| cx.global::<Vim>().state().mode)
+ self.update_editor(|editor, cx| editor.addon::<VimAddon>().unwrap().view.read(cx).mode)
}
pub fn active_operator(&mut self) -> Option<Operator> {
- self.cx
- .read(|cx| cx.global::<Vim>().state().operator_stack.last().cloned())
+ self.update_editor(|editor, cx| {
+ editor
+ .addon::<VimAddon>()
+ .unwrap()
+ .view
+ .read(cx)
+ .operator_stack
+ .last()
+ .cloned()
+ })
}
pub fn set_state(&mut self, text: &str, mode: Mode) {
- let window = self.window;
self.cx.set_state(text);
- self.update_window(window, |_, cx| {
- Vim::update(cx, |vim, cx| {
+ let vim = self.update_editor(|editor, _cx| editor.addon::<VimAddon>().cloned().unwrap());
+
+ self.update(|cx| {
+ vim.view.update(cx, |vim, cx| {
vim.switch_mode(mode, true, cx);
- })
- })
- .unwrap();
+ });
+ });
self.cx.cx.cx.run_until_parked();
}
@@ -6,7 +6,6 @@ mod test;
mod change_list;
mod command;
mod digraph;
-mod editor_events;
mod insert;
mod mode_indicator;
mod motion;
@@ -18,40 +17,33 @@ mod surrounds;
mod visual;
use anyhow::Result;
-use change_list::push_to_change_list;
use collections::HashMap;
-use command_palette_hooks::{CommandPaletteFilter, CommandPaletteInterceptor};
use editor::{
movement::{self, FindRange},
Anchor, Bias, Editor, EditorEvent, EditorMode, ToPoint,
};
use gpui::{
- actions, impl_actions, Action, AppContext, EntityId, FocusableView, Global, KeystrokeEvent,
- Subscription, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
+ actions, impl_actions, Action, AppContext, EventEmitter, KeyContext, KeystrokeEvent, Render,
+ View, ViewContext, WeakView,
};
-use language::{CursorShape, Point, SelectionGoal, TransactionId};
+use insert::NormalBefore;
+use language::{CursorShape, Point, Selection, SelectionGoal, TransactionId};
pub use mode_indicator::ModeIndicator;
use motion::Motion;
-use normal::{
- mark::create_visual_marks,
- normal_replace,
- repeat::{observe_action, observe_insertion, record_register, replay_register},
-};
-use replace::multi_replace;
+use normal::search::SearchSubmit;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
use settings::{update_settings_file, Settings, SettingsSources, SettingsStore};
-use state::{EditorState, Mode, Operator, RecordedSelection, Register, WorkspaceState};
+use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};
use std::{ops::Range, sync::Arc};
-use surrounds::{add_surrounds, change_surrounds, delete_surrounds, SurroundsType};
-use ui::BorrowAppContext;
-use visual::{visual_block_motion, visual_replace};
-use workspace::{self, Workspace};
+use surrounds::SurroundsType;
+use ui::{IntoElement, VisualContext};
+use workspace::{self, Pane, Workspace};
use crate::state::ReplayableAction;
-/// Whether or not to enable Vim mode (work in progress).
+/// Whether or not to enable Vim mode.
///
/// Default: false
pub struct VimModeSetting(pub bool);
@@ -95,316 +87,336 @@ impl_actions!(vim, [SwitchMode, PushOperator, Number, SelectRegister]);
/// Initializes the `vim` crate.
pub fn init(cx: &mut AppContext) {
- cx.set_global(Vim::default());
VimModeSetting::register(cx);
VimSettings::register(cx);
+ VimGlobals::register(cx);
- cx.observe_keystrokes(observe_keystrokes).detach();
- editor_events::init(cx);
-
- cx.observe_new_views(|workspace: &mut Workspace, cx| register(workspace, cx))
+ cx.observe_new_views(|editor: &mut Editor, cx| Vim::register(editor, cx))
.detach();
- // Any time settings change, update vim mode to match. The Vim struct
- // will be initialized as disabled by default, so we filter its commands
- // out when starting up.
- CommandPaletteFilter::update_global(cx, |filter, _| {
- filter.hide_namespace(Vim::NAMESPACE);
- });
- Vim::update_global(cx, |vim, cx| {
- vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
- });
- cx.observe_global::<SettingsStore>(|cx| {
- Vim::update_global(cx, |vim, cx| {
- vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
+ cx.observe_new_views(|workspace: &mut Workspace, _| {
+ workspace.register_action(|workspace, _: &ToggleVimMode, cx| {
+ let fs = workspace.app_state().fs.clone();
+ let currently_enabled = Vim::enabled(cx);
+ update_settings_file::<VimModeSetting>(fs, cx, move |setting, _| {
+ *setting = Some(!currently_enabled)
+ })
+ });
+
+ workspace.register_action(|_, _: &OpenDefaultKeymap, cx| {
+ cx.emit(workspace::Event::OpenBundledFile {
+ text: settings::vim_keymap(),
+ title: "Default Vim Bindings",
+ language: "JSON",
+ });
+ });
+
+ workspace.register_action(|workspace, _: &SearchSubmit, cx| {
+ let Some(vim) = workspace
+ .active_item_as::<Editor>(cx)
+ .and_then(|editor| editor.read(cx).addon::<VimAddon>().cloned())
+ else {
+ return;
+ };
+ vim.view
+ .update(cx, |_, cx| cx.defer(|vim, cx| vim.search_submit(cx)))
});
})
.detach();
}
-fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
- Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
- });
- workspace.register_action(
- |_: &mut Workspace, PushOperator(operator): &PushOperator, cx| {
- Vim::update(cx, |vim, cx| vim.push_operator(operator.clone(), cx))
- },
- );
- workspace.register_action(|_: &mut Workspace, _: &ClearOperators, cx| {
- Vim::update(cx, |vim, cx| vim.clear_operator(cx))
- });
- workspace.register_action(|_: &mut Workspace, n: &Number, cx: _| {
- Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
- });
- workspace.register_action(|_: &mut Workspace, _: &Tab, cx| {
- Vim::active_editor_input_ignored(" ".into(), cx)
- });
-
- workspace.register_action(|_: &mut Workspace, _: &Enter, cx| {
- Vim::active_editor_input_ignored("\n".into(), cx)
- });
-
- workspace.register_action(|workspace: &mut Workspace, _: &ToggleVimMode, cx| {
- let fs = workspace.app_state().fs.clone();
- let currently_enabled = VimModeSetting::get_global(cx).0;
- update_settings_file::<VimModeSetting>(fs, cx, move |setting, _| {
- *setting = Some(!currently_enabled)
- })
- });
-
- workspace.register_action(|_: &mut Workspace, _: &OpenDefaultKeymap, cx| {
- cx.emit(workspace::Event::OpenBundledFile {
- text: settings::vim_keymap(),
- title: "Default Vim Bindings",
- language: "JSON",
- });
- });
-
- normal::register(workspace, cx);
- insert::register(workspace, cx);
- motion::register(workspace, cx);
- command::register(workspace, cx);
- replace::register(workspace, cx);
- object::register(workspace, cx);
- visual::register(workspace, cx);
- change_list::register(workspace, cx);
+#[derive(Clone)]
+pub(crate) struct VimAddon {
+ pub(crate) view: View<Vim>,
}
-/// Called whenever an keystroke is typed so vim can observe all actions
-/// and keystrokes accordingly.
-fn observe_keystrokes(keystroke_event: &KeystrokeEvent, cx: &mut WindowContext) {
- if let Some(action) = keystroke_event
- .action
- .as_ref()
- .map(|action| action.boxed_clone())
- {
- observe_action(action.boxed_clone(), cx);
-
- // Keystroke is handled by the vim system, so continue forward
- if action.name().starts_with("vim::") {
- return;
- }
- } else if cx.has_pending_keystrokes() || keystroke_event.keystroke.is_ime_in_progress() {
- return;
+impl editor::Addon for VimAddon {
+ fn extend_key_context(&self, key_context: &mut KeyContext, cx: &AppContext) {
+ self.view.read(cx).extend_key_context(key_context)
}
- Vim::update(cx, |vim, cx| {
- if let Some(operator) = vim.active_operator() {
- if !operator.is_waiting(vim.state().mode) {
- vim.clear_operator(cx);
- vim.stop_recording_immediately(Box::new(ClearOperators))
- }
- }
- });
+ fn to_any(&self) -> &dyn std::any::Any {
+ self
+ }
}
/// The state pertaining to Vim mode.
-#[derive(Default)]
-struct Vim {
- active_editor: Option<WeakView<Editor>>,
- editor_subscription: Option<Subscription>,
- enabled: bool,
- editor_states: HashMap<EntityId, EditorState>,
- workspace_state: WorkspaceState,
- default_state: EditorState,
+pub(crate) struct Vim {
+ pub(crate) mode: Mode,
+ pub last_mode: Mode,
+
+ /// pre_count is the number before an operator is specified (3 in 3d2d)
+ pre_count: Option<usize>,
+ /// post_count is the number after an operator is specified (2 in 3d2d)
+ post_count: Option<usize>,
+
+ operator_stack: Vec<Operator>,
+ pub(crate) replacements: Vec<(Range<editor::Anchor>, String)>,
+
+ pub(crate) marks: HashMap<String, Vec<Anchor>>,
+ pub(crate) stored_visual_mode: Option<(Mode, Vec<bool>)>,
+ pub(crate) change_list: Vec<Vec<Anchor>>,
+ pub(crate) change_list_position: Option<usize>,
+
+ pub(crate) current_tx: Option<TransactionId>,
+ pub(crate) current_anchor: Option<Selection<Anchor>>,
+ pub(crate) undo_modes: HashMap<TransactionId, Mode>,
+
+ selected_register: Option<char>,
+ pub search: SearchState,
+
+ editor: WeakView<Editor>,
}
-impl Global for Vim {}
+// Hack: Vim intercepts events dispatched to a window and updates the view in response.
+// This means it needs a VisualContext. The easiest way to satisfy that constraint is
+// to make Vim a "View" that is just never actually rendered.
+impl Render for Vim {
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+ gpui::Empty
+ }
+}
+
+enum VimEvent {
+ Focused,
+}
+impl EventEmitter<VimEvent> for Vim {}
impl Vim {
/// The namespace for Vim actions.
const NAMESPACE: &'static str = "vim";
- fn read(cx: &mut AppContext) -> &Self {
- cx.global::<Self>()
- }
+ pub fn new(cx: &mut ViewContext<Editor>) -> View<Self> {
+ let editor = cx.view().clone();
- fn update<F, S>(cx: &mut WindowContext, update: F) -> S
- where
- F: FnOnce(&mut Self, &mut WindowContext) -> S,
- {
- cx.update_global(update)
+ cx.new_view(|cx: &mut ViewContext<Vim>| {
+ cx.subscribe(&editor, |vim, _, event, cx| {
+ vim.handle_editor_event(event, cx)
+ })
+ .detach();
+
+ let listener = cx.listener(Vim::observe_keystrokes);
+ cx.observe_keystrokes(listener).detach();
+
+ Vim {
+ mode: Mode::Normal,
+ last_mode: Mode::Normal,
+ pre_count: None,
+ post_count: None,
+ operator_stack: Vec::new(),
+ replacements: Vec::new(),
+
+ marks: HashMap::default(),
+ stored_visual_mode: None,
+ change_list: Vec::new(),
+ change_list_position: None,
+ current_tx: None,
+ current_anchor: None,
+ undo_modes: HashMap::default(),
+
+ selected_register: None,
+ search: SearchState::default(),
+
+ editor: editor.downgrade(),
+ }
+ })
}
- fn activate_editor(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
- if !editor.read(cx).use_modal_editing() {
+ fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
+ if !editor.use_modal_editing() {
return;
}
- self.active_editor = Some(editor.clone().downgrade());
- self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
- EditorEvent::SelectionsChanged { local: true } => {
- if editor.read(cx).leader_peer_id().is_none() {
- Vim::update(cx, |vim, cx| {
- vim.local_selections_changed(editor, cx);
- })
- }
- }
- EditorEvent::InputIgnored { text } => {
- Vim::active_editor_input_ignored(text.clone(), cx);
- observe_insertion(text, None, cx)
+ let mut was_enabled = Vim::enabled(cx);
+ cx.observe_global::<SettingsStore>(move |editor, cx| {
+ let enabled = Vim::enabled(cx);
+ if was_enabled == enabled {
+ return;
}
- EditorEvent::InputHandled {
- text,
- utf16_range_to_replace: range_to_replace,
- } => observe_insertion(text, range_to_replace.clone(), cx),
- EditorEvent::TransactionBegun { transaction_id } => Vim::update(cx, |vim, cx| {
- vim.transaction_begun(*transaction_id, cx);
- }),
- EditorEvent::TransactionUndone { transaction_id } => Vim::update(cx, |vim, cx| {
- vim.transaction_undone(transaction_id, cx);
- }),
- EditorEvent::Edited { .. } => {
- Vim::update(cx, |vim, cx| vim.transaction_ended(editor, cx))
+ was_enabled = enabled;
+ if enabled {
+ Self::activate(editor, cx)
+ } else {
+ Self::deactivate(editor, cx)
}
- EditorEvent::FocusedIn => Vim::update(cx, |vim, cx| vim.sync_vim_settings(cx)),
- _ => {}
- }));
+ })
+ .detach();
+ if was_enabled {
+ Self::activate(editor, cx)
+ }
+ }
- let editor = editor.read(cx);
- let editor_mode = editor.mode();
- let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
+ fn activate(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
+ let vim = Vim::new(cx);
- if editor_mode == EditorMode::Full
- && !newest_selection_empty
- && self.state().mode == Mode::Normal
- // When following someone, don't switch vim mode.
- && editor.leader_peer_id().is_none()
- {
- self.switch_mode(Mode::Visual, true, cx);
- }
+ editor.register_addon(VimAddon { view: vim.clone() });
- self.sync_vim_settings(cx);
+ vim.update(cx, |_, cx| {
+ Vim::action(editor, cx, |vim, action: &SwitchMode, cx| {
+ vim.switch_mode(action.0, false, cx)
+ });
+
+ Vim::action(editor, cx, |vim, action: &PushOperator, cx| {
+ vim.push_operator(action.0.clone(), cx)
+ });
+
+ Vim::action(editor, cx, |vim, _: &ClearOperators, cx| {
+ vim.clear_operator(cx)
+ });
+ Vim::action(editor, cx, |vim, n: &Number, cx| {
+ vim.push_count_digit(n.0, cx);
+ });
+ Vim::action(editor, cx, |vim, _: &Tab, cx| {
+ vim.input_ignored(" ".into(), cx)
+ });
+ Vim::action(editor, cx, |vim, _: &Enter, cx| {
+ vim.input_ignored("\n".into(), cx)
+ });
+
+ normal::register(editor, cx);
+ insert::register(editor, cx);
+ motion::register(editor, cx);
+ command::register(editor, cx);
+ replace::register(editor, cx);
+ object::register(editor, cx);
+ visual::register(editor, cx);
+ change_list::register(editor, cx);
+
+ cx.defer(|vim, cx| {
+ vim.focused(false, cx);
+ })
+ })
}
- fn update_active_editor<S>(
- &mut self,
- cx: &mut WindowContext,
- update: impl FnOnce(&mut Vim, &mut Editor, &mut ViewContext<Editor>) -> S,
- ) -> Option<S> {
- let editor = self.active_editor.clone()?.upgrade()?;
- Some(editor.update(cx, |editor, cx| update(self, editor, cx)))
+ fn deactivate(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
+ editor.set_cursor_shape(CursorShape::Bar, cx);
+ editor.set_clip_at_line_ends(false, cx);
+ editor.set_collapse_matches(false);
+ editor.set_input_enabled(true);
+ editor.set_autoindent(true);
+ editor.selections.line_mode = false;
+ editor.unregister_addon::<VimAddon>();
}
- fn editor_selections(&mut self, cx: &mut WindowContext) -> Vec<Range<Anchor>> {
- self.update_active_editor(cx, |_, editor, _| {
- editor
- .selections
- .disjoint_anchors()
- .iter()
- .map(|selection| selection.tail()..selection.head())
- .collect()
- })
- .unwrap_or_default()
+ /// Register an action on the editor.
+ pub fn action<A: Action>(
+ editor: &mut Editor,
+ cx: &mut ViewContext<Vim>,
+ f: impl Fn(&mut Vim, &A, &mut ViewContext<Vim>) + 'static,
+ ) {
+ let subscription = editor.register_action(cx.listener(f));
+ cx.on_release(|_, _, _| drop(subscription)).detach();
}
- /// When doing an action that modifies the buffer, we start recording so that `.`
- /// will replay the action.
- pub fn start_recording(&mut self, cx: &mut WindowContext) {
- if !self.workspace_state.dot_replaying {
- self.workspace_state.dot_recording = true;
- self.workspace_state.recorded_actions = Default::default();
- self.workspace_state.recorded_count = None;
-
- let selections = self
- .active_editor
- .as_ref()
- .and_then(|editor| editor.upgrade())
- .map(|editor| {
- let editor = editor.read(cx);
- (
- editor.selections.oldest::<Point>(cx),
- editor.selections.newest::<Point>(cx),
- )
- });
+ pub fn editor(&self) -> Option<View<Editor>> {
+ self.editor.upgrade()
+ }
- if let Some((oldest, newest)) = selections {
- self.workspace_state.recorded_selection = match self.state().mode {
- Mode::Visual if newest.end.row == newest.start.row => {
- RecordedSelection::SingleLine {
- cols: newest.end.column - newest.start.column,
- }
- }
- Mode::Visual => RecordedSelection::Visual {
- rows: newest.end.row - newest.start.row,
- cols: newest.end.column,
- },
- Mode::VisualLine => RecordedSelection::VisualLine {
- rows: newest.end.row - newest.start.row,
- },
- Mode::VisualBlock => RecordedSelection::VisualBlock {
- rows: newest.end.row.abs_diff(oldest.start.row),
- cols: newest.end.column.abs_diff(oldest.start.column),
- },
- _ => RecordedSelection::None,
- }
- } else {
- self.workspace_state.recorded_selection = RecordedSelection::None;
- }
- }
+ pub fn workspace(&self, cx: &ViewContext<Self>) -> Option<View<Workspace>> {
+ self.editor().and_then(|editor| editor.read(cx).workspace())
}
- pub fn stop_replaying(&mut self, _: &mut WindowContext) {
- self.workspace_state.dot_replaying = false;
- if let Some(replayer) = self.workspace_state.replayer.take() {
- replayer.stop();
- }
+ pub fn pane(&self, cx: &ViewContext<Self>) -> Option<View<Pane>> {
+ self.workspace(cx)
+ .and_then(|workspace| workspace.read(cx).pane_for(&self.editor()?))
}
- /// When finishing an action that modifies the buffer, stop recording.
- /// as you usually call this within a keystroke handler we also ensure that
- /// the current action is recorded.
- pub fn stop_recording(&mut self) {
- if self.workspace_state.dot_recording {
- self.workspace_state.stop_recording_after_next_action = true;
- }
+ pub fn enabled(cx: &mut AppContext) -> bool {
+ VimModeSetting::get_global(cx).0
}
- /// Stops recording actions immediately rather than waiting until after the
- /// next action to stop recording.
- ///
- /// This doesn't include the current action.
- pub fn stop_recording_immediately(&mut self, action: Box<dyn Action>) {
- if self.workspace_state.dot_recording {
- self.workspace_state
- .recorded_actions
- .push(ReplayableAction::Action(action.boxed_clone()));
- self.workspace_state.dot_recording = false;
- self.workspace_state.stop_recording_after_next_action = false;
+ /// Called whenever an keystroke is typed so vim can observe all actions
+ /// and keystrokes accordingly.
+ fn observe_keystrokes(&mut self, keystroke_event: &KeystrokeEvent, cx: &mut ViewContext<Self>) {
+ if let Some(action) = keystroke_event.action.as_ref() {
+ // Keystroke is handled by the vim system, so continue forward
+ if action.name().starts_with("vim::") {
+ return;
+ }
+ } else if cx.has_pending_keystrokes() || keystroke_event.keystroke.is_ime_in_progress() {
+ return;
}
- }
- /// Explicitly record one action (equivalents to start_recording and stop_recording)
- pub fn record_current_action(&mut self, cx: &mut WindowContext) {
- self.start_recording(cx);
- self.stop_recording();
+ if let Some(operator) = self.active_operator() {
+ if !operator.is_waiting(self.mode) {
+ self.clear_operator(cx);
+ self.stop_recording_immediately(Box::new(ClearOperators), cx)
+ }
+ }
}
- // When handling an action, you must create visual marks if you will switch to normal
- // mode without the default selection behavior.
- fn store_visual_marks(&mut self, cx: &mut WindowContext) {
- let mode = self.state().mode;
- if mode.is_visual() {
- create_visual_marks(self, mode, cx);
+ fn handle_editor_event(&mut self, event: &EditorEvent, cx: &mut ViewContext<Self>) {
+ match event {
+ EditorEvent::Focused => self.focused(true, cx),
+ EditorEvent::Blurred => self.blurred(cx),
+ EditorEvent::SelectionsChanged { local: true } => {
+ self.local_selections_changed(cx);
+ }
+ EditorEvent::InputIgnored { text } => {
+ self.input_ignored(text.clone(), cx);
+ Vim::globals(cx).observe_insertion(text, None)
+ }
+ EditorEvent::InputHandled {
+ text,
+ utf16_range_to_replace: range_to_replace,
+ } => Vim::globals(cx).observe_insertion(text, range_to_replace.clone()),
+ EditorEvent::TransactionBegun { transaction_id } => {
+ self.transaction_begun(*transaction_id, cx)
+ }
+ EditorEvent::TransactionUndone { transaction_id } => {
+ self.transaction_undone(transaction_id, cx)
+ }
+ EditorEvent::Edited { .. } => self.push_to_change_list(cx),
+ EditorEvent::FocusedIn => self.sync_vim_settings(cx),
+ _ => {}
}
}
- fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
- let state = self.state();
- let last_mode = state.mode;
- let prior_mode = state.last_mode;
- let prior_tx = state.current_tx;
- self.update_state(|state| {
- state.last_mode = last_mode;
- state.mode = mode;
- state.operator_stack.clear();
- state.selected_register.take();
- if mode == Mode::Normal || mode != last_mode {
- state.current_tx.take();
- state.current_anchor.take();
+ fn push_operator(&mut self, operator: Operator, cx: &mut ViewContext<Self>) {
+ if matches!(
+ operator,
+ Operator::Change
+ | Operator::Delete
+ | Operator::Replace
+ | Operator::Indent
+ | Operator::Outdent
+ | Operator::Lowercase
+ | Operator::Uppercase
+ | Operator::OppositeCase
+ | Operator::ToggleComments
+ ) {
+ self.start_recording(cx)
+ };
+ // Since these operations can only be entered with pre-operators,
+ // we need to clear the previous operators when pushing,
+ // so that the current stack is the most correct
+ if matches!(
+ operator,
+ Operator::AddSurrounds { .. }
+ | Operator::ChangeSurrounds { .. }
+ | Operator::DeleteSurrounds
+ ) {
+ self.operator_stack.clear();
+ if let Operator::AddSurrounds { target: None } = operator {
+ self.start_recording(cx);
}
- });
+ };
+ self.operator_stack.push(operator);
+ self.sync_vim_settings(cx);
+ }
+
+ pub fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut ViewContext<Self>) {
+ let last_mode = self.mode;
+ let prior_mode = self.last_mode;
+ let prior_tx = self.current_tx;
+ self.last_mode = last_mode;
+ self.mode = mode;
+ self.operator_stack.clear();
+ self.selected_register.take();
+ if mode == Mode::Normal || mode != last_mode {
+ self.current_tx.take();
+ self.current_anchor.take();
+ }
if mode != Mode::Insert && mode != Mode::Replace {
self.take_count(cx);
}
@@ -417,14 +429,14 @@ impl Vim {
}
if !mode.is_visual() && last_mode.is_visual() {
- create_visual_marks(self, last_mode, cx);
+ self.create_visual_marks(last_mode, cx);
}
// Adjust selections
- self.update_active_editor(cx, |_, editor, cx| {
+ self.update_editor(cx, |vim, editor, cx| {
if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
{
- visual_block_motion(true, editor, cx, |_, point, goal| Some((point, goal)))
+ vim.visual_block_motion(true, editor, cx, |_, point, goal| Some((point, goal)))
}
if last_mode == Mode::Insert || last_mode == Mode::Replace {
if let Some(prior_tx) = prior_tx {
@@ -478,278 +490,327 @@ impl Vim {
});
}
- fn push_count_digit(&mut self, number: usize, cx: &mut WindowContext) {
- if self.active_operator().is_some() {
- self.update_state(|state| {
- let post_count = state.post_count.unwrap_or(0);
-
- state.post_count = Some(
- post_count
- .checked_mul(10)
- .and_then(|post_count| post_count.checked_add(number))
- .unwrap_or(post_count),
- )
- })
- } else {
- self.update_state(|state| {
- let pre_count = state.pre_count.unwrap_or(0);
-
- state.pre_count = Some(
- pre_count
- .checked_mul(10)
- .and_then(|pre_count| pre_count.checked_add(number))
- .unwrap_or(pre_count),
- )
- })
- }
- // update the keymap so that 0 works
- self.sync_vim_settings(cx)
- }
-
- fn take_count(&mut self, cx: &mut WindowContext) -> Option<usize> {
- if self.workspace_state.dot_replaying {
- return self.workspace_state.recorded_count;
+ fn take_count(&mut self, cx: &mut ViewContext<Self>) -> Option<usize> {
+ let global_state = cx.global_mut::<VimGlobals>();
+ if global_state.dot_replaying {
+ return global_state.recorded_count;
}
- let count = if self.state().post_count == None && self.state().pre_count == None {
+ let count = if self.post_count == None && self.pre_count == None {
return None;
} else {
- Some(self.update_state(|state| {
- state.post_count.take().unwrap_or(1) * state.pre_count.take().unwrap_or(1)
- }))
+ Some(self.post_count.take().unwrap_or(1) * self.pre_count.take().unwrap_or(1))
};
- if self.workspace_state.dot_recording {
- self.workspace_state.recorded_count = count;
+
+ if global_state.dot_recording {
+ global_state.recorded_count = count;
}
self.sync_vim_settings(cx);
count
}
- fn select_register(&mut self, register: Arc<str>, cx: &mut WindowContext) {
- self.update_state(|state| {
- if register.chars().count() == 1 {
- state
- .selected_register
- .replace(register.chars().next().unwrap());
+ pub fn cursor_shape(&self) -> CursorShape {
+ match self.mode {
+ Mode::Normal => {
+ if self.operator_stack.is_empty() {
+ CursorShape::Block
+ } else {
+ CursorShape::Underscore
+ }
}
- state.operator_stack.clear();
- });
- self.sync_vim_settings(cx);
+ Mode::Replace => CursorShape::Underscore,
+ Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
+ Mode::Insert => CursorShape::Bar,
+ }
}
- fn write_registers(
- &mut self,
- content: Register,
- register: Option<char>,
- is_yank: bool,
- linewise: bool,
- cx: &mut ViewContext<Editor>,
- ) {
- if let Some(register) = register {
- let lower = register.to_lowercase().next().unwrap_or(register);
- if lower != register {
- let current = self.workspace_state.registers.entry(lower).or_default();
- current.text = (current.text.to_string() + &content.text).into();
- // not clear how to support appending to registers with multiple cursors
- current.clipboard_selections.take();
- let yanked = current.clone();
- self.workspace_state.registers.insert('"', yanked);
- } else {
- self.workspace_state.registers.insert('"', content.clone());
- match lower {
- '_' | ':' | '.' | '%' | '#' | '=' | '/' => {}
- '+' => {
- cx.write_to_clipboard(content.into());
- }
- '*' => {
- #[cfg(target_os = "linux")]
- cx.write_to_primary(content.into());
- #[cfg(not(target_os = "linux"))]
- cx.write_to_clipboard(content.into());
- }
- '"' => {
- self.workspace_state.registers.insert('0', content.clone());
- self.workspace_state.registers.insert('"', content);
- }
- _ => {
- self.workspace_state.registers.insert(lower, content);
- }
+ pub fn editor_input_enabled(&self) -> bool {
+ match self.mode {
+ Mode::Insert => {
+ if let Some(operator) = self.operator_stack.last() {
+ !operator.is_waiting(self.mode)
+ } else {
+ true
}
}
- } else {
- let setting = VimSettings::get_global(cx).use_system_clipboard;
- if setting == UseSystemClipboard::Always
- || setting == UseSystemClipboard::OnYank && is_yank
- {
- self.workspace_state.last_yank.replace(content.text.clone());
- cx.write_to_clipboard(content.clone().into());
+ Mode::Normal | Mode::Replace | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
+ false
+ }
+ }
+ }
+
+ pub fn should_autoindent(&self) -> bool {
+ !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
+ }
+
+ pub fn clip_at_line_ends(&self) -> bool {
+ match self.mode {
+ Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::Replace => {
+ false
+ }
+ Mode::Normal => true,
+ }
+ }
+
+ pub fn extend_key_context(&self, context: &mut KeyContext) {
+ let mut mode = match self.mode {
+ Mode::Normal => "normal",
+ Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
+ Mode::Insert => "insert",
+ Mode::Replace => "replace",
+ }
+ .to_string();
+
+ let mut operator_id = "none";
+
+ let active_operator = self.active_operator();
+ if active_operator.is_none() && self.pre_count.is_some()
+ || active_operator.is_some() && self.post_count.is_some()
+ {
+ context.add("VimCount");
+ }
+
+ if let Some(active_operator) = active_operator {
+ if active_operator.is_waiting(self.mode) {
+ mode = "waiting".to_string();
} else {
- self.workspace_state.last_yank = cx
- .read_from_clipboard()
- .and_then(|item| item.text().map(|string| string.into()))
+ mode = "operator".to_string();
+ operator_id = active_operator.id();
}
+ }
+
+ if mode != "waiting" && mode != "insert" && mode != "replace" {
+ context.add("VimControl");
+ }
+ context.set("vim_mode", mode);
+ context.set("vim_operator", operator_id);
+ }
+
+ fn focused(&mut self, preserve_selection: bool, cx: &mut ViewContext<Self>) {
+ let Some(editor) = self.editor() else {
+ return;
+ };
+ let editor = editor.read(cx);
+ let editor_mode = editor.mode();
+ let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
- self.workspace_state.registers.insert('"', content.clone());
- if is_yank {
- self.workspace_state.registers.insert('0', content);
+ if editor_mode == EditorMode::Full
+ && !newest_selection_empty
+ && self.mode == Mode::Normal
+ // When following someone, don't switch vim mode.
+ && editor.leader_peer_id().is_none()
+ {
+ if preserve_selection {
+ self.switch_mode(Mode::Visual, true, cx);
} else {
- let contains_newline = content.text.contains('\n');
- if !contains_newline {
- self.workspace_state.registers.insert('-', content.clone());
- }
- if linewise || contains_newline {
- let mut content = content;
- for i in '1'..'8' {
- if let Some(moved) = self.workspace_state.registers.insert(i, content) {
- content = moved;
- } else {
- break;
+ self.update_editor(cx, |_, editor, cx| {
+ editor.set_clip_at_line_ends(false, cx);
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|_, selection| {
+ selection.collapse_to(selection.start, selection.goal)
+ })
+ });
+ });
+ }
+ }
+
+ cx.emit(VimEvent::Focused);
+ self.sync_vim_settings(cx);
+ }
+
+ fn blurred(&mut self, cx: &mut ViewContext<Self>) {
+ self.stop_recording_immediately(NormalBefore.boxed_clone(), cx);
+ self.store_visual_marks(cx);
+ self.clear_operator(cx);
+ self.update_editor(cx, |_, editor, cx| {
+ editor.set_cursor_shape(language::CursorShape::Hollow, cx);
+ });
+ }
+
+ fn update_editor<S>(
+ &mut self,
+ cx: &mut ViewContext<Self>,
+ update: impl FnOnce(&mut Self, &mut Editor, &mut ViewContext<Editor>) -> S,
+ ) -> Option<S> {
+ let editor = self.editor.upgrade()?;
+ Some(editor.update(cx, |editor, cx| update(self, editor, cx)))
+ }
+
+ fn editor_selections(&mut self, cx: &mut ViewContext<Self>) -> Vec<Range<Anchor>> {
+ self.update_editor(cx, |_, editor, _| {
+ editor
+ .selections
+ .disjoint_anchors()
+ .iter()
+ .map(|selection| selection.tail()..selection.head())
+ .collect()
+ })
+ .unwrap_or_default()
+ }
+
+ /// When doing an action that modifies the buffer, we start recording so that `.`
+ /// will replay the action.
+ pub fn start_recording(&mut self, cx: &mut ViewContext<Self>) {
+ Vim::update_globals(cx, |globals, cx| {
+ if !globals.dot_replaying {
+ globals.dot_recording = true;
+ globals.recorded_actions = Default::default();
+ globals.recorded_count = None;
+
+ let selections = self.editor().map(|editor| {
+ let editor = editor.read(cx);
+ (
+ editor.selections.oldest::<Point>(cx),
+ editor.selections.newest::<Point>(cx),
+ )
+ });
+
+ if let Some((oldest, newest)) = selections {
+ globals.recorded_selection = match self.mode {
+ Mode::Visual if newest.end.row == newest.start.row => {
+ RecordedSelection::SingleLine {
+ cols: newest.end.column - newest.start.column,
+ }
}
+ Mode::Visual => RecordedSelection::Visual {
+ rows: newest.end.row - newest.start.row,
+ cols: newest.end.column,
+ },
+ Mode::VisualLine => RecordedSelection::VisualLine {
+ rows: newest.end.row - newest.start.row,
+ },
+ Mode::VisualBlock => RecordedSelection::VisualBlock {
+ rows: newest.end.row.abs_diff(oldest.start.row),
+ cols: newest.end.column.abs_diff(oldest.start.column),
+ },
+ _ => RecordedSelection::None,
}
+ } else {
+ globals.recorded_selection = RecordedSelection::None;
}
}
+ })
+ }
+
+ pub fn stop_replaying(&mut self, cx: &mut ViewContext<Self>) {
+ let globals = Vim::globals(cx);
+ globals.dot_replaying = false;
+ if let Some(replayer) = globals.replayer.take() {
+ replayer.stop();
}
}
- fn read_register(
+ /// When finishing an action that modifies the buffer, stop recording.
+ /// as you usually call this within a keystroke handler we also ensure that
+ /// the current action is recorded.
+ pub fn stop_recording(&mut self, cx: &mut ViewContext<Self>) {
+ let globals = Vim::globals(cx);
+ if globals.dot_recording {
+ globals.stop_recording_after_next_action = true;
+ }
+ }
+
+ /// Stops recording actions immediately rather than waiting until after the
+ /// next action to stop recording.
+ ///
+ /// This doesn't include the current action.
+ pub fn stop_recording_immediately(
&mut self,
- register: Option<char>,
- editor: Option<&mut Editor>,
- cx: &mut WindowContext,
- ) -> Option<Register> {
- let Some(register) = register.filter(|reg| *reg != '"') else {
- let setting = VimSettings::get_global(cx).use_system_clipboard;
- return match setting {
- UseSystemClipboard::Always => cx.read_from_clipboard().map(|item| item.into()),
- UseSystemClipboard::OnYank if self.system_clipboard_is_newer(cx) => {
- cx.read_from_clipboard().map(|item| item.into())
- }
- _ => self.workspace_state.registers.get(&'"').cloned(),
- };
- };
- let lower = register.to_lowercase().next().unwrap_or(register);
- match lower {
- '_' | ':' | '.' | '#' | '=' => None,
- '+' => cx.read_from_clipboard().map(|item| item.into()),
- '*' => {
- #[cfg(target_os = "linux")]
- {
- cx.read_from_primary().map(|item| item.into())
- }
- #[cfg(not(target_os = "linux"))]
- {
- cx.read_from_clipboard().map(|item| item.into())
- }
- }
- '%' => editor.and_then(|editor| {
- let selection = editor.selections.newest::<Point>(cx);
- if let Some((_, buffer, _)) = editor
- .buffer()
- .read(cx)
- .excerpt_containing(selection.head(), cx)
- {
- buffer
- .read(cx)
- .file()
- .map(|file| file.path().to_string_lossy().to_string().into())
- } else {
- None
- }
- }),
- _ => self.workspace_state.registers.get(&lower).cloned(),
+ action: Box<dyn Action>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let globals = Vim::globals(cx);
+ if globals.dot_recording {
+ globals
+ .recorded_actions
+ .push(ReplayableAction::Action(action.boxed_clone()));
+ globals.dot_recording = false;
+ globals.stop_recording_after_next_action = false;
}
}
- fn system_clipboard_is_newer(&self, cx: &mut AppContext) -> bool {
- cx.read_from_clipboard().is_some_and(|item| {
- if let Some(last_state) = &self.workspace_state.last_yank {
- Some(last_state.as_ref()) != item.text().as_deref()
- } else {
- true
- }
- })
+ /// Explicitly record one action (equivalents to start_recording and stop_recording)
+ pub fn record_current_action(&mut self, cx: &mut ViewContext<Self>) {
+ self.start_recording(cx);
+ self.stop_recording(cx);
}
- fn push_operator(&mut self, operator: Operator, cx: &mut WindowContext) {
- if matches!(
- operator,
- Operator::Change
- | Operator::Delete
- | Operator::Replace
- | Operator::Indent
- | Operator::Outdent
- | Operator::Lowercase
- | Operator::Uppercase
- | Operator::OppositeCase
- | Operator::ToggleComments
- ) {
- self.start_recording(cx)
- };
- // Since these operations can only be entered with pre-operators,
- // we need to clear the previous operators when pushing,
- // so that the current stack is the most correct
- if matches!(
- operator,
- Operator::AddSurrounds { .. }
- | Operator::ChangeSurrounds { .. }
- | Operator::DeleteSurrounds
- ) {
- self.update_state(|state| state.operator_stack.clear());
- if let Operator::AddSurrounds { target: None } = operator {
- self.start_recording(cx);
- }
- };
- self.update_state(|state| state.operator_stack.push(operator));
+ fn push_count_digit(&mut self, number: usize, cx: &mut ViewContext<Self>) {
+ if self.active_operator().is_some() {
+ let post_count = self.post_count.unwrap_or(0);
+
+ self.post_count = Some(
+ post_count
+ .checked_mul(10)
+ .and_then(|post_count| post_count.checked_add(number))
+ .unwrap_or(post_count),
+ )
+ } else {
+ let pre_count = self.pre_count.unwrap_or(0);
+
+ self.pre_count = Some(
+ pre_count
+ .checked_mul(10)
+ .and_then(|pre_count| pre_count.checked_add(number))
+ .unwrap_or(pre_count),
+ )
+ }
+ // update the keymap so that 0 works
+ self.sync_vim_settings(cx)
+ }
+
+ fn select_register(&mut self, register: Arc<str>, cx: &mut ViewContext<Self>) {
+ if register.chars().count() == 1 {
+ self.selected_register
+ .replace(register.chars().next().unwrap());
+ }
+ self.operator_stack.clear();
self.sync_vim_settings(cx);
}
fn maybe_pop_operator(&mut self) -> Option<Operator> {
- self.update_state(|state| state.operator_stack.pop())
+ self.operator_stack.pop()
}
- fn pop_operator(&mut self, cx: &mut WindowContext) -> Operator {
- let popped_operator = self.update_state(|state| state.operator_stack.pop())
+ fn pop_operator(&mut self, cx: &mut ViewContext<Self>) -> Operator {
+ let popped_operator = self.operator_stack.pop()
.expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
self.sync_vim_settings(cx);
popped_operator
}
- fn clear_operator(&mut self, cx: &mut WindowContext) {
+ fn clear_operator(&mut self, cx: &mut ViewContext<Self>) {
self.take_count(cx);
- self.update_state(|state| {
- state.selected_register.take();
- state.operator_stack.clear()
- });
+ self.selected_register.take();
+ self.operator_stack.clear();
self.sync_vim_settings(cx);
}
fn active_operator(&self) -> Option<Operator> {
- self.state().operator_stack.last().cloned()
+ self.operator_stack.last().cloned()
}
- fn transaction_begun(&mut self, transaction_id: TransactionId, _: &mut WindowContext) {
- self.update_state(|state| {
- let mode = if (state.mode == Mode::Insert
- || state.mode == Mode::Replace
- || state.mode == Mode::Normal)
- && state.current_tx.is_none()
- {
- state.current_tx = Some(transaction_id);
- state.last_mode
- } else {
- state.mode
- };
- if mode == Mode::VisualLine || mode == Mode::VisualBlock {
- state.undo_modes.insert(transaction_id, mode);
- }
- });
+ fn transaction_begun(&mut self, transaction_id: TransactionId, _: &mut ViewContext<Self>) {
+ let mode = if (self.mode == Mode::Insert
+ || self.mode == Mode::Replace
+ || self.mode == Mode::Normal)
+ && self.current_tx.is_none()
+ {
+ self.current_tx = Some(transaction_id);
+ self.last_mode
+ } else {
+ self.mode
+ };
+ if mode == Mode::VisualLine || mode == Mode::VisualBlock {
+ self.undo_modes.insert(transaction_id, mode);
+ }
}
- fn transaction_undone(&mut self, transaction_id: &TransactionId, cx: &mut WindowContext) {
- match self.state().mode {
+ fn transaction_undone(&mut self, transaction_id: &TransactionId, cx: &mut ViewContext<Self>) {
+ match self.mode {
Mode::VisualLine | Mode::VisualBlock | Mode::Visual => {
- self.update_active_editor(cx, |vim, editor, cx| {
- let original_mode = vim.state().undo_modes.get(transaction_id);
+ self.update_editor(cx, |vim, editor, cx| {
+ let original_mode = vim.undo_modes.get(transaction_id);
editor.change_selections(None, cx, |s| match original_mode {
Some(Mode::VisualLine) => {
s.move_with(|map, selection| {
@@ -7,17 +7,15 @@ use editor::{
scroll::Autoscroll,
Bias, DisplayPoint, Editor, ToOffset,
};
-use gpui::{actions, ViewContext, WindowContext};
+use gpui::{actions, ViewContext};
use language::{Point, Selection, SelectionGoal};
use multi_buffer::MultiBufferRow;
use search::BufferSearchBar;
use util::ResultExt;
-use workspace::{searchable::Direction, Workspace};
+use workspace::searchable::Direction;
use crate::{
motion::{start_of_line, Motion},
- normal::yank::{copy_selections_content, yank_selections_content},
- normal::{mark::create_visual_marks, substitute::substitute},
object::Object,
state::{Mode, Operator},
Vim,
@@ -41,102 +39,87 @@ actions!(
]
);
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_, _: &ToggleVisual, cx: &mut ViewContext<Workspace>| {
- toggle_mode(Mode::Visual, cx)
+pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
+ Vim::action(editor, cx, |vim, _: &ToggleVisual, cx| {
+ vim.toggle_mode(Mode::Visual, cx)
});
- workspace.register_action(|_, _: &ToggleVisualLine, cx: &mut ViewContext<Workspace>| {
- toggle_mode(Mode::VisualLine, cx)
+ Vim::action(editor, cx, |vim, _: &ToggleVisualLine, cx| {
+ vim.toggle_mode(Mode::VisualLine, cx)
});
- workspace.register_action(
- |_, _: &ToggleVisualBlock, cx: &mut ViewContext<Workspace>| {
- toggle_mode(Mode::VisualBlock, cx)
- },
- );
- workspace.register_action(other_end);
- workspace.register_action(|_, _: &VisualDelete, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- delete(vim, false, cx);
- });
+ Vim::action(editor, cx, |vim, _: &ToggleVisualBlock, cx| {
+ vim.toggle_mode(Mode::VisualBlock, cx)
});
- workspace.register_action(|_, _: &VisualDeleteLine, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- delete(vim, true, cx);
- });
+ Vim::action(editor, cx, Vim::other_end);
+ Vim::action(editor, cx, |vim, _: &VisualDelete, cx| {
+ vim.record_current_action(cx);
+ vim.visual_delete(false, cx);
});
- workspace.register_action(|_, _: &VisualYank, cx| {
- Vim::update(cx, |vim, cx| {
- yank(vim, cx);
- });
+ Vim::action(editor, cx, |vim, _: &VisualDeleteLine, cx| {
+ vim.record_current_action(cx);
+ vim.visual_delete(true, cx);
});
+ Vim::action(editor, cx, |vim, _: &VisualYank, cx| vim.visual_yank(cx));
- workspace.register_action(select_next);
- workspace.register_action(select_previous);
- workspace.register_action(|workspace, _: &SelectNextMatch, cx| {
- Vim::update(cx, |vim, cx| {
- select_match(workspace, vim, Direction::Next, cx);
- });
+ Vim::action(editor, cx, Vim::select_next);
+ Vim::action(editor, cx, Vim::select_previous);
+ Vim::action(editor, cx, |vim, _: &SelectNextMatch, cx| {
+ vim.select_match(Direction::Next, cx);
});
- workspace.register_action(|workspace, _: &SelectPreviousMatch, cx| {
- Vim::update(cx, |vim, cx| {
- select_match(workspace, vim, Direction::Prev, cx);
- });
+ Vim::action(editor, cx, |vim, _: &SelectPreviousMatch, cx| {
+ vim.select_match(Direction::Prev, cx);
});
- workspace.register_action(|_, _: &RestoreVisualSelection, cx| {
- Vim::update(cx, |vim, cx| {
- let Some((stored_mode, reversed)) =
- vim.update_state(|state| state.stored_visual_mode.take())
- else {
- return;
- };
- let Some((start, end)) = vim.state().marks.get("<").zip(vim.state().marks.get(">"))
- else {
- return;
- };
- let ranges = start
- .into_iter()
- .zip(end)
- .zip(reversed)
- .map(|((start, end), reversed)| (*start, *end, reversed))
- .collect::<Vec<_>>();
-
- if vim.state().mode.is_visual() {
- create_visual_marks(vim, vim.state().mode, cx);
- }
+ Vim::action(editor, cx, |vim, _: &RestoreVisualSelection, cx| {
+ let Some((stored_mode, reversed)) = vim.stored_visual_mode.take() else {
+ return;
+ };
+ let Some((start, end)) = vim.marks.get("<").zip(vim.marks.get(">")) else {
+ return;
+ };
+ let ranges = start
+ .into_iter()
+ .zip(end)
+ .zip(reversed)
+ .map(|((start, end), reversed)| (*start, *end, reversed))
+ .collect::<Vec<_>>();
+
+ if vim.mode.is_visual() {
+ vim.create_visual_marks(vim.mode, cx);
+ }
- vim.update_active_editor(cx, |_, editor, cx| {
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- let map = s.display_map();
- let ranges = ranges
- .into_iter()
- .map(|(start, end, reversed)| {
- let new_end =
- movement::saturating_right(&map, end.to_display_point(&map));
- Selection {
- id: s.new_selection_id(),
- start: start.to_offset(&map.buffer_snapshot),
- end: new_end.to_offset(&map, Bias::Left),
- reversed,
- goal: SelectionGoal::None,
- }
- })
- .collect();
- s.select(ranges);
- })
- });
- vim.switch_mode(stored_mode, true, cx)
+ vim.update_editor(cx, |_, editor, cx| {
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ let map = s.display_map();
+ let ranges = ranges
+ .into_iter()
+ .map(|(start, end, reversed)| {
+ let new_end = movement::saturating_right(&map, end.to_display_point(&map));
+ Selection {
+ id: s.new_selection_id(),
+ start: start.to_offset(&map.buffer_snapshot),
+ end: new_end.to_offset(&map, Bias::Left),
+ reversed,
+ goal: SelectionGoal::None,
+ }
+ })
+ .collect();
+ s.select(ranges);
+ })
});
+ vim.switch_mode(stored_mode, true, cx)
});
}
-pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |vim, editor, cx| {
+impl Vim {
+ pub fn visual_motion(
+ &mut self,
+ motion: Motion,
+ times: Option<usize>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.update_editor(cx, |vim, editor, cx| {
let text_layout_details = editor.text_layout_details(cx);
- if vim.state().mode == Mode::VisualBlock
+ if vim.mode == Mode::VisualBlock
&& !matches!(
motion,
Motion::EndOfLine {
@@ -145,7 +128,7 @@ pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContex
)
{
let is_up_or_down = matches!(motion, Motion::Up { .. } | Motion::Down { .. });
- visual_block_motion(is_up_or_down, editor, cx, |map, point, goal| {
+ vim.visual_block_motion(is_up_or_down, editor, cx, |map, point, goal| {
motion.move_point(map, point, goal, times, &text_layout_details)
})
} else {
@@ -183,7 +166,7 @@ pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContex
// ensure the current character is included in the selection.
if !selection.reversed {
- let next_point = if vim.state().mode == Mode::VisualBlock {
+ let next_point = if vim.mode == Mode::VisualBlock {
movement::saturating_right(map, selection.end)
} else {
movement::right(map, selection.end)
@@ -206,127 +189,126 @@ pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContex
});
}
});
- });
-}
-
-pub fn visual_block_motion(
- preserve_goal: bool,
- editor: &mut Editor,
- cx: &mut ViewContext<Editor>,
- mut move_selection: impl FnMut(
- &DisplaySnapshot,
- DisplayPoint,
- SelectionGoal,
- ) -> Option<(DisplayPoint, SelectionGoal)>,
-) {
- let text_layout_details = editor.text_layout_details(cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- let map = &s.display_map();
- let mut head = s.newest_anchor().head().to_display_point(map);
- let mut tail = s.oldest_anchor().tail().to_display_point(map);
-
- let mut head_x = map.x_for_display_point(head, &text_layout_details);
- let mut tail_x = map.x_for_display_point(tail, &text_layout_details);
-
- let (start, end) = match s.newest_anchor().goal {
- SelectionGoal::HorizontalRange { start, end } if preserve_goal => (start, end),
- SelectionGoal::HorizontalPosition(start) if preserve_goal => (start, start),
- _ => (tail_x.0, head_x.0),
- };
- let mut goal = SelectionGoal::HorizontalRange { start, end };
-
- let was_reversed = tail_x > head_x;
- if !was_reversed && !preserve_goal {
- head = movement::saturating_left(map, head);
- }
+ }
- let Some((new_head, _)) = move_selection(&map, head, goal) else {
- return;
- };
- head = new_head;
- head_x = map.x_for_display_point(head, &text_layout_details);
-
- let is_reversed = tail_x > head_x;
- if was_reversed && !is_reversed {
- tail = movement::saturating_left(map, tail);
- tail_x = map.x_for_display_point(tail, &text_layout_details);
- } else if !was_reversed && is_reversed {
- tail = movement::saturating_right(map, tail);
- tail_x = map.x_for_display_point(tail, &text_layout_details);
- }
- if !is_reversed && !preserve_goal {
- head = movement::saturating_right(map, head);
- head_x = map.x_for_display_point(head, &text_layout_details);
- }
+ pub fn visual_block_motion(
+ &mut self,
+ preserve_goal: bool,
+ editor: &mut Editor,
+ cx: &mut ViewContext<Editor>,
+ mut move_selection: impl FnMut(
+ &DisplaySnapshot,
+ DisplayPoint,
+ SelectionGoal,
+ ) -> Option<(DisplayPoint, SelectionGoal)>,
+ ) {
+ let text_layout_details = editor.text_layout_details(cx);
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ let map = &s.display_map();
+ let mut head = s.newest_anchor().head().to_display_point(map);
+ let mut tail = s.oldest_anchor().tail().to_display_point(map);
- let positions = if is_reversed {
- head_x..tail_x
- } else {
- tail_x..head_x
- };
+ let mut head_x = map.x_for_display_point(head, &text_layout_details);
+ let mut tail_x = map.x_for_display_point(tail, &text_layout_details);
- if !preserve_goal {
- goal = SelectionGoal::HorizontalRange {
- start: positions.start.0,
- end: positions.end.0,
+ let (start, end) = match s.newest_anchor().goal {
+ SelectionGoal::HorizontalRange { start, end } if preserve_goal => (start, end),
+ SelectionGoal::HorizontalPosition(start) if preserve_goal => (start, start),
+ _ => (tail_x.0, head_x.0),
};
- }
+ let mut goal = SelectionGoal::HorizontalRange { start, end };
- let mut selections = Vec::new();
- let mut row = tail.row();
-
- loop {
- let laid_out_line = map.layout_row(row, &text_layout_details);
- let start = DisplayPoint::new(
- row,
- laid_out_line.closest_index_for_x(positions.start) as u32,
- );
- let mut end =
- DisplayPoint::new(row, laid_out_line.closest_index_for_x(positions.end) as u32);
- if end <= start {
- if start.column() == map.line_len(start.row()) {
- end = start;
- } else {
- end = movement::saturating_right(map, start);
- }
+ let was_reversed = tail_x > head_x;
+ if !was_reversed && !preserve_goal {
+ head = movement::saturating_left(map, head);
}
- if positions.start <= laid_out_line.width {
- let selection = Selection {
- id: s.new_selection_id(),
- start: start.to_point(map),
- end: end.to_point(map),
- reversed: is_reversed,
- goal,
- };
+ let Some((new_head, _)) = move_selection(&map, head, goal) else {
+ return;
+ };
+ head = new_head;
+ head_x = map.x_for_display_point(head, &text_layout_details);
- selections.push(selection);
+ let is_reversed = tail_x > head_x;
+ if was_reversed && !is_reversed {
+ tail = movement::saturating_left(map, tail);
+ tail_x = map.x_for_display_point(tail, &text_layout_details);
+ } else if !was_reversed && is_reversed {
+ tail = movement::saturating_right(map, tail);
+ tail_x = map.x_for_display_point(tail, &text_layout_details);
}
- if row == head.row() {
- break;
+ if !is_reversed && !preserve_goal {
+ head = movement::saturating_right(map, head);
+ head_x = map.x_for_display_point(head, &text_layout_details);
}
- if tail.row() > head.row() {
- row.0 -= 1
+
+ let positions = if is_reversed {
+ head_x..tail_x
} else {
- row.0 += 1
+ tail_x..head_x
+ };
+
+ if !preserve_goal {
+ goal = SelectionGoal::HorizontalRange {
+ start: positions.start.0,
+ end: positions.end.0,
+ };
}
- }
- s.select(selections);
- })
-}
+ let mut selections = Vec::new();
+ let mut row = tail.row();
+
+ loop {
+ let laid_out_line = map.layout_row(row, &text_layout_details);
+ let start = DisplayPoint::new(
+ row,
+ laid_out_line.closest_index_for_x(positions.start) as u32,
+ );
+ let mut end =
+ DisplayPoint::new(row, laid_out_line.closest_index_for_x(positions.end) as u32);
+ if end <= start {
+ if start.column() == map.line_len(start.row()) {
+ end = start;
+ } else {
+ end = movement::saturating_right(map, start);
+ }
+ }
+
+ if positions.start <= laid_out_line.width {
+ let selection = Selection {
+ id: s.new_selection_id(),
+ start: start.to_point(map),
+ end: end.to_point(map),
+ reversed: is_reversed,
+ goal,
+ };
+
+ selections.push(selection);
+ }
+ if row == head.row() {
+ break;
+ }
+ if tail.row() > head.row() {
+ row.0 -= 1
+ } else {
+ row.0 += 1
+ }
+ }
+
+ s.select(selections);
+ })
+ }
-pub fn visual_object(object: Object, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- if let Some(Operator::Object { around }) = vim.active_operator() {
- vim.pop_operator(cx);
- let current_mode = vim.state().mode;
+ pub fn visual_object(&mut self, object: Object, cx: &mut ViewContext<Vim>) {
+ if let Some(Operator::Object { around }) = self.active_operator() {
+ self.pop_operator(cx);
+ let current_mode = self.mode;
let target_mode = object.target_visual_mode(current_mode);
if target_mode != current_mode {
- vim.switch_mode(target_mode, true, cx);
+ self.switch_mode(target_mode, true, cx);
}
- vim.update_active_editor(cx, |_, editor, cx| {
+ self.update_editor(cx, |_, editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_with(|map, selection| {
let mut mut_selection = selection.clone();
@@ -384,109 +366,103 @@ pub fn visual_object(object: Object, cx: &mut WindowContext) {
});
});
}
- });
-}
+ }
-fn toggle_mode(mode: Mode, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- if vim.state().mode == mode {
- vim.switch_mode(Mode::Normal, false, cx);
+ fn toggle_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
+ if self.mode == mode {
+ self.switch_mode(Mode::Normal, false, cx);
} else {
- vim.switch_mode(mode, false, cx);
+ self.switch_mode(mode, false, cx);
}
- })
-}
+ }
-pub fn other_end(_: &mut Workspace, _: &OtherEnd, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |_, editor, cx| {
+ pub fn other_end(&mut self, _: &OtherEnd, cx: &mut ViewContext<Self>) {
+ self.update_editor(cx, |_, editor, cx| {
editor.change_selections(None, cx, |s| {
s.move_with(|_, selection| {
selection.reversed = !selection.reversed;
})
})
- })
- });
-}
+ });
+ }
-pub fn delete(vim: &mut Vim, line_mode: bool, cx: &mut WindowContext) {
- vim.store_visual_marks(cx);
- vim.update_active_editor(cx, |vim, editor, cx| {
- let mut original_columns: HashMap<_, _> = Default::default();
- let line_mode = line_mode || editor.selections.line_mode;
+ pub fn visual_delete(&mut self, line_mode: bool, cx: &mut ViewContext<Self>) {
+ self.store_visual_marks(cx);
+ self.update_editor(cx, |vim, editor, cx| {
+ let mut original_columns: HashMap<_, _> = Default::default();
+ let line_mode = line_mode || editor.selections.line_mode;
- editor.transact(cx, |editor, cx| {
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- if line_mode {
- let mut position = selection.head();
- if !selection.reversed {
- position = movement::left(map, position);
- }
- original_columns.insert(selection.id, position.to_point(map).column);
- if vim.state().mode == Mode::VisualBlock {
- *selection.end.column_mut() = map.line_len(selection.end.row())
- } else if vim.state().mode != Mode::VisualLine {
- selection.start = DisplayPoint::new(selection.start.row(), 0);
- if selection.end.row() == map.max_point().row() {
- selection.end = map.max_point()
- } else {
- *selection.end.row_mut() += 1;
- *selection.end.column_mut() = 0;
+ editor.transact(cx, |editor, cx| {
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.move_with(|map, selection| {
+ if line_mode {
+ let mut position = selection.head();
+ if !selection.reversed {
+ position = movement::left(map, position);
+ }
+ original_columns.insert(selection.id, position.to_point(map).column);
+ if vim.mode == Mode::VisualBlock {
+ *selection.end.column_mut() = map.line_len(selection.end.row())
+ } else if vim.mode != Mode::VisualLine {
+ selection.start = DisplayPoint::new(selection.start.row(), 0);
+ if selection.end.row() == map.max_point().row() {
+ selection.end = map.max_point()
+ } else {
+ *selection.end.row_mut() += 1;
+ *selection.end.column_mut() = 0;
+ }
}
}
- }
- selection.goal = SelectionGoal::None;
+ selection.goal = SelectionGoal::None;
+ });
});
- });
- copy_selections_content(vim, editor, line_mode, cx);
- editor.insert("", cx);
+ vim.copy_selections_content(editor, line_mode, cx);
+ editor.insert("", cx);
- // Fixup cursor position after the deletion
- editor.set_clip_at_line_ends(true, cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- let mut cursor = selection.head().to_point(map);
+ // Fixup cursor position after the deletion
+ editor.set_clip_at_line_ends(true, cx);
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.move_with(|map, selection| {
+ let mut cursor = selection.head().to_point(map);
- if let Some(column) = original_columns.get(&selection.id) {
- cursor.column = *column
+ if let Some(column) = original_columns.get(&selection.id) {
+ cursor.column = *column
+ }
+ let cursor = map.clip_point(cursor.to_display_point(map), Bias::Left);
+ selection.collapse_to(cursor, selection.goal)
+ });
+ if vim.mode == Mode::VisualBlock {
+ s.select_anchors(vec![s.first_anchor()])
}
- let cursor = map.clip_point(cursor.to_display_point(map), Bias::Left);
- selection.collapse_to(cursor, selection.goal)
});
- if vim.state().mode == Mode::VisualBlock {
+ })
+ });
+ self.switch_mode(Mode::Normal, true, cx);
+ }
+
+ pub fn visual_yank(&mut self, cx: &mut ViewContext<Self>) {
+ self.store_visual_marks(cx);
+ self.update_editor(cx, |vim, editor, cx| {
+ let line_mode = editor.selections.line_mode;
+ vim.yank_selections_content(editor, line_mode, cx);
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ if line_mode {
+ selection.start = start_of_line(map, false, selection.start);
+ };
+ selection.collapse_to(selection.start, SelectionGoal::None)
+ });
+ if vim.mode == Mode::VisualBlock {
s.select_anchors(vec![s.first_anchor()])
}
});
- })
- });
- vim.switch_mode(Mode::Normal, true, cx);
-}
-
-pub fn yank(vim: &mut Vim, cx: &mut WindowContext) {
- vim.store_visual_marks(cx);
- vim.update_active_editor(cx, |vim, editor, cx| {
- let line_mode = editor.selections.line_mode;
- yank_selections_content(vim, editor, line_mode, cx);
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- if line_mode {
- selection.start = start_of_line(map, false, selection.start);
- };
- selection.collapse_to(selection.start, SelectionGoal::None)
- });
- if vim.state().mode == Mode::VisualBlock {
- s.select_anchors(vec![s.first_anchor()])
- }
});
- });
- vim.switch_mode(Mode::Normal, true, cx);
-}
+ self.switch_mode(Mode::Normal, true, cx);
+ }
-pub(crate) fn visual_replace(text: Arc<str>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- vim.stop_recording();
- vim.update_active_editor(cx, |_, editor, cx| {
+ pub(crate) fn visual_replace(&mut self, text: Arc<str>, cx: &mut ViewContext<Self>) {
+ self.stop_recording(cx);
+ self.update_editor(cx, |_, editor, cx| {
editor.transact(cx, |editor, cx| {
let (display_map, selections) = editor.selections.all_adjusted_display(cx);
@@ -522,16 +498,14 @@ pub(crate) fn visual_replace(text: Arc<str>, cx: &mut WindowContext) {
editor.change_selections(None, cx, |s| s.select_ranges(stable_anchors));
});
});
- vim.switch_mode(Mode::Normal, false, cx);
- });
-}
+ self.switch_mode(Mode::Normal, false, cx);
+ }
-pub fn select_next(_: &mut Workspace, _: &SelectNext, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- let count =
- vim.take_count(cx)
- .unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
- vim.update_active_editor(cx, |_, editor, cx| {
+ pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
+ let count = self
+ .take_count(cx)
+ .unwrap_or_else(|| if self.mode.is_visual() { 1 } else { 2 });
+ self.update_editor(cx, |_, editor, cx| {
editor.set_clip_at_line_ends(false, cx);
for _ in 0..count {
if editor
@@ -542,16 +516,14 @@ pub fn select_next(_: &mut Workspace, _: &SelectNext, cx: &mut ViewContext<Works
break;
}
}
- })
- });
-}
+ });
+ }
-pub fn select_previous(_: &mut Workspace, _: &SelectPrevious, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- let count =
- vim.take_count(cx)
- .unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
- vim.update_active_editor(cx, |_, editor, cx| {
+ pub fn select_previous(&mut self, _: &SelectPrevious, cx: &mut ViewContext<Self>) {
+ let count = self
+ .take_count(cx)
+ .unwrap_or_else(|| if self.mode.is_visual() { 1 } else { 2 });
+ self.update_editor(cx, |_, editor, cx| {
for _ in 0..count {
if editor
.select_previous(&Default::default(), cx)
@@ -561,89 +533,91 @@ pub fn select_previous(_: &mut Workspace, _: &SelectPrevious, cx: &mut ViewConte
break;
}
}
- })
- });
-}
+ });
+ }
-pub fn select_match(
- workspace: &mut Workspace,
- vim: &mut Vim,
- direction: Direction,
- cx: &mut WindowContext,
-) {
- let count = vim.take_count(cx).unwrap_or(1);
- let pane = workspace.active_pane().clone();
- let vim_is_normal = vim.state().mode == Mode::Normal;
- let mut start_selection = 0usize;
- let mut end_selection = 0usize;
-
- vim.update_active_editor(cx, |_, editor, _| {
- editor.set_collapse_matches(false);
- });
- if vim_is_normal {
+ pub fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
+ let count = self.take_count(cx).unwrap_or(1);
+ let Some(workspace) = self
+ .editor
+ .upgrade()
+ .and_then(|editor| editor.read(cx).workspace())
+ else {
+ return;
+ };
+ let pane = workspace.read(cx).active_pane().clone();
+ let vim_is_normal = self.mode == Mode::Normal;
+ let mut start_selection = 0usize;
+ let mut end_selection = 0usize;
+
+ self.update_editor(cx, |_, editor, _| {
+ editor.set_collapse_matches(false);
+ });
+ if vim_is_normal {
+ pane.update(cx, |pane, cx| {
+ if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>()
+ {
+ search_bar.update(cx, |search_bar, cx| {
+ if !search_bar.has_active_match() || !search_bar.show(cx) {
+ return;
+ }
+ // without update_match_index there is a bug when the cursor is before the first match
+ search_bar.update_match_index(cx);
+ search_bar.select_match(direction.opposite(), 1, cx);
+ });
+ }
+ });
+ }
+ self.update_editor(cx, |_, editor, cx| {
+ let latest = editor.selections.newest::<usize>(cx);
+ start_selection = latest.start;
+ end_selection = latest.end;
+ });
+
+ let mut match_exists = false;
pane.update(cx, |pane, cx| {
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
search_bar.update(cx, |search_bar, cx| {
- if !search_bar.has_active_match() || !search_bar.show(cx) {
- return;
- }
- // without update_match_index there is a bug when the cursor is before the first match
search_bar.update_match_index(cx);
- search_bar.select_match(direction.opposite(), 1, cx);
+ search_bar.select_match(direction, count, cx);
+ match_exists = search_bar.match_exists(cx);
});
}
});
- }
- vim.update_active_editor(cx, |_, editor, cx| {
- let latest = editor.selections.newest::<usize>(cx);
- start_selection = latest.start;
- end_selection = latest.end;
- });
-
- let mut match_exists = false;
- pane.update(cx, |pane, cx| {
- if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
- search_bar.update(cx, |search_bar, cx| {
- search_bar.update_match_index(cx);
- search_bar.select_match(direction, count, cx);
- match_exists = search_bar.match_exists(cx);
- });
- }
- });
- if !match_exists {
- vim.clear_operator(cx);
- vim.stop_replaying(cx);
- return;
- }
- vim.update_active_editor(cx, |_, editor, cx| {
- let latest = editor.selections.newest::<usize>(cx);
- if vim_is_normal {
- start_selection = latest.start;
- end_selection = latest.end;
- } else {
- start_selection = start_selection.min(latest.start);
- end_selection = end_selection.max(latest.end);
- }
- if direction == Direction::Prev {
- std::mem::swap(&mut start_selection, &mut end_selection);
+ if !match_exists {
+ self.clear_operator(cx);
+ self.stop_replaying(cx);
+ return;
}
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.select_ranges([start_selection..end_selection]);
+ self.update_editor(cx, |_, editor, cx| {
+ let latest = editor.selections.newest::<usize>(cx);
+ if vim_is_normal {
+ start_selection = latest.start;
+ end_selection = latest.end;
+ } else {
+ start_selection = start_selection.min(latest.start);
+ end_selection = end_selection.max(latest.end);
+ }
+ if direction == Direction::Prev {
+ std::mem::swap(&mut start_selection, &mut end_selection);
+ }
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.select_ranges([start_selection..end_selection]);
+ });
+ editor.set_collapse_matches(true);
});
- editor.set_collapse_matches(true);
- });
- match vim.maybe_pop_operator() {
- Some(Operator::Change) => substitute(vim, None, false, cx),
- Some(Operator::Delete) => {
- vim.stop_recording();
- delete(vim, false, cx)
+ match self.maybe_pop_operator() {
+ Some(Operator::Change) => self.substitute(None, false, cx),
+ Some(Operator::Delete) => {
+ self.stop_recording(cx);
+ self.visual_delete(false, cx)
+ }
+ Some(Operator::Yank) => self.visual_yank(cx),
+ _ => {} // Ignoring other operators
}
- Some(Operator::Yank) => yank(vim, cx),
- _ => {} // Ignoring other operators
}
}
-
#[cfg(test)]
mod test {
use indoc::indoc;