vim.rs

   1//! Vim support for Zed.
   2
   3#[cfg(test)]
   4mod test;
   5
   6mod change_list;
   7mod command;
   8mod digraph;
   9mod helix;
  10mod indent;
  11mod insert;
  12mod mode_indicator;
  13mod motion;
  14mod normal;
  15mod object;
  16mod replace;
  17mod rewrap;
  18mod state;
  19mod surrounds;
  20mod visual;
  21
  22use anyhow::Result;
  23use collections::HashMap;
  24use editor::{
  25    movement::{self, FindRange},
  26    Anchor, Bias, Editor, EditorEvent, EditorMode, ToPoint,
  27};
  28use gpui::{
  29    actions, impl_actions, Action, AppContext, Axis, Entity, EventEmitter, KeyContext,
  30    KeystrokeEvent, Render, Subscription, View, ViewContext, WeakView,
  31};
  32use insert::{NormalBefore, TemporaryNormal};
  33use language::{CursorShape, Point, Selection, SelectionGoal, TransactionId};
  34pub use mode_indicator::ModeIndicator;
  35use motion::Motion;
  36use normal::search::SearchSubmit;
  37use schemars::JsonSchema;
  38use serde::Deserialize;
  39use serde_derive::Serialize;
  40use settings::{update_settings_file, Settings, SettingsSources, SettingsStore};
  41use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};
  42use std::{mem, ops::Range, sync::Arc};
  43use surrounds::SurroundsType;
  44use theme::ThemeSettings;
  45use ui::{px, IntoElement, VisualContext};
  46use vim_mode_setting::VimModeSetting;
  47use workspace::{self, Pane, ResizeIntent, Workspace};
  48
  49use crate::state::ReplayableAction;
  50
  51/// Used to resize the current pane
  52#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
  53pub struct ResizePane(pub ResizeIntent);
  54
  55/// An Action to Switch between modes
  56#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
  57pub struct SwitchMode(pub Mode);
  58
  59/// PushOperator is used to put vim into a "minor" mode,
  60/// where it's waiting for a specific next set of keystrokes.
  61/// For example 'd' needs a motion to complete.
  62#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
  63pub struct PushOperator(pub Operator);
  64
  65/// Number is used to manage vim's count. Pushing a digit
  66/// multiplies the current value by 10 and adds the digit.
  67#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
  68struct Number(usize);
  69
  70#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
  71struct SelectRegister(String);
  72
  73actions!(
  74    vim,
  75    [
  76        ClearOperators,
  77        Tab,
  78        Enter,
  79        Object,
  80        InnerObject,
  81        FindForward,
  82        FindBackward,
  83        MaximizePane,
  84        OpenDefaultKeymap,
  85        ResetPaneSizes,
  86        Sneak,
  87        SneakBackward,
  88    ]
  89);
  90
  91// in the workspace namespace so it's not filtered out when vim is disabled.
  92actions!(workspace, [ToggleVimMode]);
  93
  94impl_actions!(
  95    vim,
  96    [ResizePane, SwitchMode, PushOperator, Number, SelectRegister]
  97);
  98
  99/// Initializes the `vim` crate.
 100pub fn init(cx: &mut AppContext) {
 101    vim_mode_setting::init(cx);
 102    VimSettings::register(cx);
 103    VimGlobals::register(cx);
 104
 105    cx.observe_new_views(|editor: &mut Editor, cx| Vim::register(editor, cx))
 106        .detach();
 107
 108    cx.observe_new_views(|workspace: &mut Workspace, _| {
 109        workspace.register_action(|workspace, _: &ToggleVimMode, cx| {
 110            let fs = workspace.app_state().fs.clone();
 111            let currently_enabled = Vim::enabled(cx);
 112            update_settings_file::<VimModeSetting>(fs, cx, move |setting, _| {
 113                *setting = Some(!currently_enabled)
 114            })
 115        });
 116
 117        workspace.register_action(|_, _: &OpenDefaultKeymap, cx| {
 118            cx.emit(workspace::Event::OpenBundledFile {
 119                text: settings::vim_keymap(),
 120                title: "Default Vim Bindings",
 121                language: "JSON",
 122            });
 123        });
 124
 125        workspace.register_action(|workspace, _: &ResetPaneSizes, cx| {
 126            workspace.reset_pane_sizes(cx);
 127        });
 128
 129        workspace.register_action(|workspace, _: &MaximizePane, cx| {
 130            let pane = workspace.active_pane();
 131            let Some(size) = workspace.bounding_box_for_pane(&pane) else {
 132                return;
 133            };
 134
 135            let theme = ThemeSettings::get_global(cx);
 136            let height = theme.buffer_font_size() * theme.buffer_line_height.value();
 137
 138            let desired_size = if let Some(count) = Vim::take_count(cx) {
 139                height * count
 140            } else {
 141                px(10000.)
 142            };
 143            workspace.resize_pane(Axis::Vertical, desired_size - size.size.height, cx)
 144        });
 145
 146        workspace.register_action(|workspace, action: &ResizePane, cx| {
 147            let count = Vim::take_count(cx).unwrap_or(1) as f32;
 148            let theme = ThemeSettings::get_global(cx);
 149            let Ok(font_id) = cx.text_system().font_id(&theme.buffer_font) else {
 150                return;
 151            };
 152            let Ok(width) = cx
 153                .text_system()
 154                .advance(font_id, theme.buffer_font_size(), 'm')
 155            else {
 156                return;
 157            };
 158            let height = theme.buffer_font_size() * theme.buffer_line_height.value();
 159
 160            let (axis, amount) = match action.0 {
 161                ResizeIntent::Lengthen => (Axis::Vertical, height),
 162                ResizeIntent::Shorten => (Axis::Vertical, height * -1.),
 163                ResizeIntent::Widen => (Axis::Horizontal, width.width),
 164                ResizeIntent::Narrow => (Axis::Horizontal, width.width * -1.),
 165            };
 166
 167            workspace.resize_pane(axis, amount * count, cx);
 168        });
 169
 170        workspace.register_action(|workspace, _: &SearchSubmit, cx| {
 171            let vim = workspace
 172                .focused_pane(cx)
 173                .read(cx)
 174                .active_item()
 175                .and_then(|item| item.act_as::<Editor>(cx))
 176                .and_then(|editor| editor.read(cx).addon::<VimAddon>().cloned());
 177            let Some(vim) = vim else { return };
 178            vim.view
 179                .update(cx, |_, cx| cx.defer(|vim, cx| vim.search_submit(cx)))
 180        });
 181    })
 182    .detach();
 183}
 184
 185#[derive(Clone)]
 186pub(crate) struct VimAddon {
 187    pub(crate) view: View<Vim>,
 188}
 189
 190impl editor::Addon for VimAddon {
 191    fn extend_key_context(&self, key_context: &mut KeyContext, cx: &AppContext) {
 192        self.view.read(cx).extend_key_context(key_context, cx)
 193    }
 194
 195    fn to_any(&self) -> &dyn std::any::Any {
 196        self
 197    }
 198}
 199
 200/// The state pertaining to Vim mode.
 201pub(crate) struct Vim {
 202    pub(crate) mode: Mode,
 203    pub last_mode: Mode,
 204    pub temp_mode: bool,
 205    pub exit_temporary_mode: bool,
 206
 207    operator_stack: Vec<Operator>,
 208    pub(crate) replacements: Vec<(Range<editor::Anchor>, String)>,
 209
 210    pub(crate) marks: HashMap<String, Vec<Anchor>>,
 211    pub(crate) stored_visual_mode: Option<(Mode, Vec<bool>)>,
 212    pub(crate) change_list: Vec<Vec<Anchor>>,
 213    pub(crate) change_list_position: Option<usize>,
 214
 215    pub(crate) current_tx: Option<TransactionId>,
 216    pub(crate) current_anchor: Option<Selection<Anchor>>,
 217    pub(crate) undo_modes: HashMap<TransactionId, Mode>,
 218
 219    selected_register: Option<char>,
 220    pub search: SearchState,
 221
 222    editor: WeakView<Editor>,
 223
 224    _subscriptions: Vec<Subscription>,
 225}
 226
 227// Hack: Vim intercepts events dispatched to a window and updates the view in response.
 228// This means it needs a VisualContext. The easiest way to satisfy that constraint is
 229// to make Vim a "View" that is just never actually rendered.
 230impl Render for Vim {
 231    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
 232        gpui::Empty
 233    }
 234}
 235
 236enum VimEvent {
 237    Focused,
 238}
 239impl EventEmitter<VimEvent> for Vim {}
 240
 241impl Vim {
 242    /// The namespace for Vim actions.
 243    const NAMESPACE: &'static str = "vim";
 244
 245    pub fn new(cx: &mut ViewContext<Editor>) -> View<Self> {
 246        let editor = cx.view().clone();
 247
 248        cx.new_view(|cx| Vim {
 249            mode: Mode::Normal,
 250            last_mode: Mode::Normal,
 251            temp_mode: false,
 252            exit_temporary_mode: false,
 253            operator_stack: Vec::new(),
 254            replacements: Vec::new(),
 255
 256            marks: HashMap::default(),
 257            stored_visual_mode: None,
 258            change_list: Vec::new(),
 259            change_list_position: None,
 260            current_tx: None,
 261            current_anchor: None,
 262            undo_modes: HashMap::default(),
 263
 264            selected_register: None,
 265            search: SearchState::default(),
 266
 267            editor: editor.downgrade(),
 268            _subscriptions: vec![
 269                cx.observe_keystrokes(Self::observe_keystrokes),
 270                cx.subscribe(&editor, |this, _, event, cx| {
 271                    this.handle_editor_event(event, cx)
 272                }),
 273            ],
 274        })
 275    }
 276
 277    fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
 278        if !editor.use_modal_editing() {
 279            return;
 280        }
 281
 282        let mut was_enabled = Vim::enabled(cx);
 283        let mut was_toggle = VimSettings::get_global(cx).toggle_relative_line_numbers;
 284        cx.observe_global::<SettingsStore>(move |editor, cx| {
 285            let enabled = Vim::enabled(cx);
 286            let toggle = VimSettings::get_global(cx).toggle_relative_line_numbers;
 287            if enabled && was_enabled && (toggle != was_toggle) {
 288                if toggle {
 289                    let is_relative = editor
 290                        .addon::<VimAddon>()
 291                        .map(|vim| vim.view.read(cx).mode != Mode::Insert);
 292                    editor.set_relative_line_number(is_relative, cx)
 293                } else {
 294                    editor.set_relative_line_number(None, cx)
 295                }
 296            }
 297            was_toggle = VimSettings::get_global(cx).toggle_relative_line_numbers;
 298            if was_enabled == enabled {
 299                return;
 300            }
 301            was_enabled = enabled;
 302            if enabled {
 303                Self::activate(editor, cx)
 304            } else {
 305                Self::deactivate(editor, cx)
 306            }
 307        })
 308        .detach();
 309        if was_enabled {
 310            Self::activate(editor, cx)
 311        }
 312    }
 313
 314    fn activate(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
 315        let vim = Vim::new(cx);
 316
 317        editor.register_addon(VimAddon { view: vim.clone() });
 318
 319        vim.update(cx, |_, cx| {
 320            Vim::action(editor, cx, |vim, action: &SwitchMode, cx| {
 321                vim.switch_mode(action.0, false, cx)
 322            });
 323
 324            Vim::action(editor, cx, |vim, action: &PushOperator, cx| {
 325                vim.push_operator(action.0.clone(), cx)
 326            });
 327
 328            Vim::action(editor, cx, |vim, _: &ClearOperators, cx| {
 329                vim.clear_operator(cx)
 330            });
 331            Vim::action(editor, cx, |vim, n: &Number, cx| {
 332                vim.push_count_digit(n.0, cx);
 333            });
 334            Vim::action(editor, cx, |vim, _: &Tab, cx| {
 335                vim.input_ignored(" ".into(), cx)
 336            });
 337            Vim::action(editor, cx, |vim, _: &Enter, cx| {
 338                vim.input_ignored("\n".into(), cx)
 339            });
 340
 341            normal::register(editor, cx);
 342            insert::register(editor, cx);
 343            helix::register(editor, cx);
 344            motion::register(editor, cx);
 345            command::register(editor, cx);
 346            replace::register(editor, cx);
 347            indent::register(editor, cx);
 348            rewrap::register(editor, cx);
 349            object::register(editor, cx);
 350            visual::register(editor, cx);
 351            change_list::register(editor, cx);
 352            digraph::register(editor, cx);
 353
 354            cx.defer(|vim, cx| {
 355                vim.focused(false, cx);
 356            })
 357        })
 358    }
 359
 360    fn deactivate(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
 361        editor.set_cursor_shape(CursorShape::Bar, cx);
 362        editor.set_clip_at_line_ends(false, cx);
 363        editor.set_collapse_matches(false);
 364        editor.set_input_enabled(true);
 365        editor.set_autoindent(true);
 366        editor.selections.line_mode = false;
 367        editor.unregister_addon::<VimAddon>();
 368        editor.set_relative_line_number(None, cx);
 369        if let Some(vim) = Vim::globals(cx).focused_vim() {
 370            if vim.entity_id() == cx.view().entity_id() {
 371                Vim::globals(cx).focused_vim = None;
 372            }
 373        }
 374    }
 375
 376    /// Register an action on the editor.
 377    pub fn action<A: Action>(
 378        editor: &mut Editor,
 379        cx: &mut ViewContext<Vim>,
 380        f: impl Fn(&mut Vim, &A, &mut ViewContext<Vim>) + 'static,
 381    ) {
 382        let subscription = editor.register_action(cx.listener(f));
 383        cx.on_release(|_, _, _| drop(subscription)).detach();
 384    }
 385
 386    pub fn editor(&self) -> Option<View<Editor>> {
 387        self.editor.upgrade()
 388    }
 389
 390    pub fn workspace(&self, cx: &mut ViewContext<Self>) -> Option<View<Workspace>> {
 391        cx.window_handle()
 392            .downcast::<Workspace>()
 393            .and_then(|handle| handle.root(cx).ok())
 394    }
 395
 396    pub fn pane(&self, cx: &mut ViewContext<Self>) -> Option<View<Pane>> {
 397        self.workspace(cx)
 398            .map(|workspace| workspace.read(cx).focused_pane(cx))
 399    }
 400
 401    pub fn enabled(cx: &mut AppContext) -> bool {
 402        VimModeSetting::get_global(cx).0
 403    }
 404
 405    /// Called whenever an keystroke is typed so vim can observe all actions
 406    /// and keystrokes accordingly.
 407    fn observe_keystrokes(&mut self, keystroke_event: &KeystrokeEvent, cx: &mut ViewContext<Self>) {
 408        if self.exit_temporary_mode {
 409            self.exit_temporary_mode = false;
 410            // Don't switch to insert mode if the action is temporary_normal.
 411            if let Some(action) = keystroke_event.action.as_ref() {
 412                if action.as_any().downcast_ref::<TemporaryNormal>().is_some() {
 413                    return;
 414                }
 415            }
 416            self.switch_mode(Mode::Insert, false, cx)
 417        }
 418        if let Some(action) = keystroke_event.action.as_ref() {
 419            // Keystroke is handled by the vim system, so continue forward
 420            if action.name().starts_with("vim::") {
 421                return;
 422            }
 423        } else if cx.has_pending_keystrokes() || keystroke_event.keystroke.is_ime_in_progress() {
 424            return;
 425        }
 426
 427        if let Some(operator) = self.active_operator() {
 428            match operator {
 429                Operator::Literal { prefix } => {
 430                    self.handle_literal_keystroke(keystroke_event, prefix.unwrap_or_default(), cx);
 431                }
 432                _ if !operator.is_waiting(self.mode) => {
 433                    self.clear_operator(cx);
 434                    self.stop_recording_immediately(Box::new(ClearOperators), cx)
 435                }
 436                _ => {}
 437            }
 438        }
 439    }
 440
 441    fn handle_editor_event(&mut self, event: &EditorEvent, cx: &mut ViewContext<Self>) {
 442        match event {
 443            EditorEvent::Focused => self.focused(true, cx),
 444            EditorEvent::Blurred => self.blurred(cx),
 445            EditorEvent::SelectionsChanged { local: true } => {
 446                self.local_selections_changed(cx);
 447            }
 448            EditorEvent::InputIgnored { text } => {
 449                self.input_ignored(text.clone(), cx);
 450                Vim::globals(cx).observe_insertion(text, None)
 451            }
 452            EditorEvent::InputHandled {
 453                text,
 454                utf16_range_to_replace: range_to_replace,
 455            } => Vim::globals(cx).observe_insertion(text, range_to_replace.clone()),
 456            EditorEvent::TransactionBegun { transaction_id } => {
 457                self.transaction_begun(*transaction_id, cx)
 458            }
 459            EditorEvent::TransactionUndone { transaction_id } => {
 460                self.transaction_undone(transaction_id, cx)
 461            }
 462            EditorEvent::Edited { .. } => self.push_to_change_list(cx),
 463            EditorEvent::FocusedIn => self.sync_vim_settings(cx),
 464            EditorEvent::CursorShapeChanged => self.cursor_shape_changed(cx),
 465            _ => {}
 466        }
 467    }
 468
 469    fn push_operator(&mut self, operator: Operator, cx: &mut ViewContext<Self>) {
 470        if matches!(
 471            operator,
 472            Operator::Change
 473                | Operator::Delete
 474                | Operator::Replace
 475                | Operator::Indent
 476                | Operator::Outdent
 477                | Operator::AutoIndent
 478                | Operator::Lowercase
 479                | Operator::Uppercase
 480                | Operator::OppositeCase
 481                | Operator::ToggleComments
 482        ) {
 483            self.start_recording(cx)
 484        };
 485        // Since these operations can only be entered with pre-operators,
 486        // we need to clear the previous operators when pushing,
 487        // so that the current stack is the most correct
 488        if matches!(
 489            operator,
 490            Operator::AddSurrounds { .. }
 491                | Operator::ChangeSurrounds { .. }
 492                | Operator::DeleteSurrounds
 493        ) {
 494            self.operator_stack.clear();
 495            if let Operator::AddSurrounds { target: None } = operator {
 496                self.start_recording(cx);
 497            }
 498        };
 499        self.operator_stack.push(operator);
 500        self.sync_vim_settings(cx);
 501    }
 502
 503    pub fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut ViewContext<Self>) {
 504        if self.temp_mode && mode == Mode::Normal {
 505            self.temp_mode = false;
 506            self.switch_mode(Mode::Normal, leave_selections, cx);
 507            self.switch_mode(Mode::Insert, false, cx);
 508            return;
 509        } else if self.temp_mode
 510            && !matches!(mode, Mode::Visual | Mode::VisualLine | Mode::VisualBlock)
 511        {
 512            self.temp_mode = false;
 513        }
 514
 515        let last_mode = self.mode;
 516        let prior_mode = self.last_mode;
 517        let prior_tx = self.current_tx;
 518        self.last_mode = last_mode;
 519        self.mode = mode;
 520        self.operator_stack.clear();
 521        self.selected_register.take();
 522        if mode == Mode::Normal || mode != last_mode {
 523            self.current_tx.take();
 524            self.current_anchor.take();
 525        }
 526        if mode != Mode::Insert && mode != Mode::Replace {
 527            Vim::take_count(cx);
 528        }
 529
 530        // Sync editor settings like clip mode
 531        self.sync_vim_settings(cx);
 532
 533        if VimSettings::get_global(cx).toggle_relative_line_numbers
 534            && self.mode != self.last_mode
 535            && (self.mode == Mode::Insert || self.last_mode == Mode::Insert)
 536        {
 537            self.update_editor(cx, |vim, editor, cx| {
 538                let is_relative = vim.mode != Mode::Insert;
 539                editor.set_relative_line_number(Some(is_relative), cx)
 540            });
 541        }
 542
 543        if leave_selections {
 544            return;
 545        }
 546
 547        if !mode.is_visual() && last_mode.is_visual() {
 548            self.create_visual_marks(last_mode, cx);
 549        }
 550
 551        // Adjust selections
 552        self.update_editor(cx, |vim, editor, cx| {
 553            if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
 554            {
 555                vim.visual_block_motion(true, editor, cx, |_, point, goal| Some((point, goal)))
 556            }
 557            if last_mode == Mode::Insert || last_mode == Mode::Replace {
 558                if let Some(prior_tx) = prior_tx {
 559                    editor.group_until_transaction(prior_tx, cx)
 560                }
 561            }
 562
 563            editor.change_selections(None, cx, |s| {
 564                // we cheat with visual block mode and use multiple cursors.
 565                // the cost of this cheat is we need to convert back to a single
 566                // cursor whenever vim would.
 567                if last_mode == Mode::VisualBlock
 568                    && (mode != Mode::VisualBlock && mode != Mode::Insert)
 569                {
 570                    let tail = s.oldest_anchor().tail();
 571                    let head = s.newest_anchor().head();
 572                    s.select_anchor_ranges(vec![tail..head]);
 573                } else if last_mode == Mode::Insert
 574                    && prior_mode == Mode::VisualBlock
 575                    && mode != Mode::VisualBlock
 576                {
 577                    let pos = s.first_anchor().head();
 578                    s.select_anchor_ranges(vec![pos..pos])
 579                }
 580
 581                let snapshot = s.display_map();
 582                if let Some(pending) = s.pending.as_mut() {
 583                    if pending.selection.reversed && mode.is_visual() && !last_mode.is_visual() {
 584                        let mut end = pending.selection.end.to_point(&snapshot.buffer_snapshot);
 585                        end = snapshot
 586                            .buffer_snapshot
 587                            .clip_point(end + Point::new(0, 1), Bias::Right);
 588                        pending.selection.end = snapshot.buffer_snapshot.anchor_before(end);
 589                    }
 590                }
 591
 592                s.move_with(|map, selection| {
 593                    if last_mode.is_visual() && !mode.is_visual() {
 594                        let mut point = selection.head();
 595                        if !selection.reversed && !selection.is_empty() {
 596                            point = movement::left(map, selection.head());
 597                        }
 598                        selection.collapse_to(point, selection.goal)
 599                    } else if !last_mode.is_visual() && mode.is_visual() && selection.is_empty() {
 600                        selection.end = movement::right(map, selection.start);
 601                    }
 602                });
 603            })
 604        });
 605    }
 606
 607    pub fn take_count(cx: &mut AppContext) -> Option<usize> {
 608        let global_state = cx.global_mut::<VimGlobals>();
 609        if global_state.dot_replaying {
 610            return global_state.recorded_count;
 611        }
 612
 613        let count = if global_state.post_count.is_none() && global_state.pre_count.is_none() {
 614            return None;
 615        } else {
 616            Some(
 617                global_state.post_count.take().unwrap_or(1)
 618                    * global_state.pre_count.take().unwrap_or(1),
 619            )
 620        };
 621
 622        if global_state.dot_recording {
 623            global_state.recorded_count = count;
 624        }
 625        count
 626    }
 627
 628    pub fn cursor_shape(&self) -> CursorShape {
 629        match self.mode {
 630            Mode::Normal => {
 631                if let Some(operator) = self.operator_stack.last() {
 632                    match operator {
 633                        // Navigation operators -> Block cursor
 634                        Operator::FindForward { .. }
 635                        | Operator::FindBackward { .. }
 636                        | Operator::Mark
 637                        | Operator::Jump { .. }
 638                        | Operator::Register
 639                        | Operator::RecordRegister
 640                        | Operator::ReplayRegister => CursorShape::Block,
 641
 642                        // All other operators -> Underline cursor
 643                        _ => CursorShape::Underline,
 644                    }
 645                } else {
 646                    // No operator active -> Block cursor
 647                    CursorShape::Block
 648                }
 649            }
 650            Mode::Replace => CursorShape::Underline,
 651            Mode::HelixNormal | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
 652                CursorShape::Block
 653            }
 654            Mode::Insert => CursorShape::Bar,
 655        }
 656    }
 657
 658    pub fn editor_input_enabled(&self) -> bool {
 659        match self.mode {
 660            Mode::Insert => {
 661                if let Some(operator) = self.operator_stack.last() {
 662                    !operator.is_waiting(self.mode)
 663                } else {
 664                    true
 665                }
 666            }
 667            Mode::Normal
 668            | Mode::HelixNormal
 669            | Mode::Replace
 670            | Mode::Visual
 671            | Mode::VisualLine
 672            | Mode::VisualBlock => false,
 673        }
 674    }
 675
 676    pub fn should_autoindent(&self) -> bool {
 677        !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
 678    }
 679
 680    pub fn clip_at_line_ends(&self) -> bool {
 681        match self.mode {
 682            Mode::Insert
 683            | Mode::Visual
 684            | Mode::VisualLine
 685            | Mode::VisualBlock
 686            | Mode::Replace
 687            | Mode::HelixNormal => false,
 688            Mode::Normal => true,
 689        }
 690    }
 691
 692    pub fn extend_key_context(&self, context: &mut KeyContext, cx: &AppContext) {
 693        let mut mode = match self.mode {
 694            Mode::Normal => "normal",
 695            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
 696            Mode::Insert => "insert",
 697            Mode::Replace => "replace",
 698            Mode::HelixNormal => "helix_normal",
 699        }
 700        .to_string();
 701
 702        let mut operator_id = "none";
 703
 704        let active_operator = self.active_operator();
 705        if active_operator.is_none() && cx.global::<VimGlobals>().pre_count.is_some()
 706            || active_operator.is_some() && cx.global::<VimGlobals>().post_count.is_some()
 707        {
 708            context.add("VimCount");
 709        }
 710
 711        if let Some(active_operator) = active_operator {
 712            if active_operator.is_waiting(self.mode) {
 713                if matches!(active_operator, Operator::Literal { .. }) {
 714                    mode = "literal".to_string();
 715                } else {
 716                    mode = "waiting".to_string();
 717                }
 718            } else {
 719                operator_id = active_operator.id();
 720                mode = "operator".to_string();
 721            }
 722        }
 723
 724        if mode == "normal" || mode == "visual" || mode == "operator" {
 725            context.add("VimControl");
 726        }
 727        context.set("vim_mode", mode);
 728        context.set("vim_operator", operator_id);
 729    }
 730
 731    fn focused(&mut self, preserve_selection: bool, cx: &mut ViewContext<Self>) {
 732        let Some(editor) = self.editor() else {
 733            return;
 734        };
 735        let newest_selection_empty = editor.update(cx, |editor, cx| {
 736            editor.selections.newest::<usize>(cx).is_empty()
 737        });
 738        let editor = editor.read(cx);
 739        let editor_mode = editor.mode();
 740
 741        if editor_mode == EditorMode::Full
 742                && !newest_selection_empty
 743                && self.mode == Mode::Normal
 744                // When following someone, don't switch vim mode.
 745                && editor.leader_peer_id().is_none()
 746        {
 747            if preserve_selection {
 748                self.switch_mode(Mode::Visual, true, cx);
 749            } else {
 750                self.update_editor(cx, |_, editor, cx| {
 751                    editor.set_clip_at_line_ends(false, cx);
 752                    editor.change_selections(None, cx, |s| {
 753                        s.move_with(|_, selection| {
 754                            selection.collapse_to(selection.start, selection.goal)
 755                        })
 756                    });
 757                });
 758            }
 759        }
 760
 761        cx.emit(VimEvent::Focused);
 762        self.sync_vim_settings(cx);
 763
 764        if VimSettings::get_global(cx).toggle_relative_line_numbers {
 765            if let Some(old_vim) = Vim::globals(cx).focused_vim() {
 766                if old_vim.entity_id() != cx.view().entity_id() {
 767                    old_vim.update(cx, |vim, cx| {
 768                        vim.update_editor(cx, |_, editor, cx| {
 769                            editor.set_relative_line_number(None, cx)
 770                        });
 771                    });
 772
 773                    self.update_editor(cx, |vim, editor, cx| {
 774                        let is_relative = vim.mode != Mode::Insert;
 775                        editor.set_relative_line_number(Some(is_relative), cx)
 776                    });
 777                }
 778            } else {
 779                self.update_editor(cx, |vim, editor, cx| {
 780                    let is_relative = vim.mode != Mode::Insert;
 781                    editor.set_relative_line_number(Some(is_relative), cx)
 782                });
 783            }
 784        }
 785        Vim::globals(cx).focused_vim = Some(cx.view().downgrade());
 786    }
 787
 788    fn blurred(&mut self, cx: &mut ViewContext<Self>) {
 789        self.stop_recording_immediately(NormalBefore.boxed_clone(), cx);
 790        self.store_visual_marks(cx);
 791        self.clear_operator(cx);
 792        self.update_editor(cx, |_, editor, cx| {
 793            editor.set_cursor_shape(language::CursorShape::Hollow, cx);
 794        });
 795    }
 796
 797    fn cursor_shape_changed(&mut self, cx: &mut ViewContext<Self>) {
 798        self.update_editor(cx, |vim, editor, cx| {
 799            editor.set_cursor_shape(vim.cursor_shape(), cx);
 800        });
 801    }
 802
 803    fn update_editor<S>(
 804        &mut self,
 805        cx: &mut ViewContext<Self>,
 806        update: impl FnOnce(&mut Self, &mut Editor, &mut ViewContext<Editor>) -> S,
 807    ) -> Option<S> {
 808        let editor = self.editor.upgrade()?;
 809        Some(editor.update(cx, |editor, cx| update(self, editor, cx)))
 810    }
 811
 812    fn editor_selections(&mut self, cx: &mut ViewContext<Self>) -> Vec<Range<Anchor>> {
 813        self.update_editor(cx, |_, editor, _| {
 814            editor
 815                .selections
 816                .disjoint_anchors()
 817                .iter()
 818                .map(|selection| selection.tail()..selection.head())
 819                .collect()
 820        })
 821        .unwrap_or_default()
 822    }
 823
 824    /// When doing an action that modifies the buffer, we start recording so that `.`
 825    /// will replay the action.
 826    pub fn start_recording(&mut self, cx: &mut ViewContext<Self>) {
 827        Vim::update_globals(cx, |globals, cx| {
 828            if !globals.dot_replaying {
 829                globals.dot_recording = true;
 830                globals.recording_actions = Default::default();
 831                globals.recorded_count = None;
 832
 833                let selections = self.editor().map(|editor| {
 834                    editor.update(cx, |editor, cx| {
 835                        (
 836                            editor.selections.oldest::<Point>(cx),
 837                            editor.selections.newest::<Point>(cx),
 838                        )
 839                    })
 840                });
 841
 842                if let Some((oldest, newest)) = selections {
 843                    globals.recorded_selection = match self.mode {
 844                        Mode::Visual if newest.end.row == newest.start.row => {
 845                            RecordedSelection::SingleLine {
 846                                cols: newest.end.column - newest.start.column,
 847                            }
 848                        }
 849                        Mode::Visual => RecordedSelection::Visual {
 850                            rows: newest.end.row - newest.start.row,
 851                            cols: newest.end.column,
 852                        },
 853                        Mode::VisualLine => RecordedSelection::VisualLine {
 854                            rows: newest.end.row - newest.start.row,
 855                        },
 856                        Mode::VisualBlock => RecordedSelection::VisualBlock {
 857                            rows: newest.end.row.abs_diff(oldest.start.row),
 858                            cols: newest.end.column.abs_diff(oldest.start.column),
 859                        },
 860                        _ => RecordedSelection::None,
 861                    }
 862                } else {
 863                    globals.recorded_selection = RecordedSelection::None;
 864                }
 865            }
 866        })
 867    }
 868
 869    pub fn stop_replaying(&mut self, cx: &mut ViewContext<Self>) {
 870        let globals = Vim::globals(cx);
 871        globals.dot_replaying = false;
 872        if let Some(replayer) = globals.replayer.take() {
 873            replayer.stop();
 874        }
 875    }
 876
 877    /// When finishing an action that modifies the buffer, stop recording.
 878    /// as you usually call this within a keystroke handler we also ensure that
 879    /// the current action is recorded.
 880    pub fn stop_recording(&mut self, cx: &mut ViewContext<Self>) {
 881        let globals = Vim::globals(cx);
 882        if globals.dot_recording {
 883            globals.stop_recording_after_next_action = true;
 884        }
 885        self.exit_temporary_mode = self.temp_mode;
 886    }
 887
 888    /// Stops recording actions immediately rather than waiting until after the
 889    /// next action to stop recording.
 890    ///
 891    /// This doesn't include the current action.
 892    pub fn stop_recording_immediately(
 893        &mut self,
 894        action: Box<dyn Action>,
 895        cx: &mut ViewContext<Self>,
 896    ) {
 897        let globals = Vim::globals(cx);
 898        if globals.dot_recording {
 899            globals
 900                .recording_actions
 901                .push(ReplayableAction::Action(action.boxed_clone()));
 902            globals.recorded_actions = mem::take(&mut globals.recording_actions);
 903            globals.dot_recording = false;
 904            globals.stop_recording_after_next_action = false;
 905        }
 906        self.exit_temporary_mode = self.temp_mode;
 907    }
 908
 909    /// Explicitly record one action (equivalents to start_recording and stop_recording)
 910    pub fn record_current_action(&mut self, cx: &mut ViewContext<Self>) {
 911        self.start_recording(cx);
 912        self.stop_recording(cx);
 913    }
 914
 915    fn push_count_digit(&mut self, number: usize, cx: &mut ViewContext<Self>) {
 916        if self.active_operator().is_some() {
 917            let post_count = Vim::globals(cx).post_count.unwrap_or(0);
 918
 919            Vim::globals(cx).post_count = Some(
 920                post_count
 921                    .checked_mul(10)
 922                    .and_then(|post_count| post_count.checked_add(number))
 923                    .unwrap_or(post_count),
 924            )
 925        } else {
 926            let pre_count = Vim::globals(cx).pre_count.unwrap_or(0);
 927
 928            Vim::globals(cx).pre_count = Some(
 929                pre_count
 930                    .checked_mul(10)
 931                    .and_then(|pre_count| pre_count.checked_add(number))
 932                    .unwrap_or(pre_count),
 933            )
 934        }
 935        // update the keymap so that 0 works
 936        self.sync_vim_settings(cx)
 937    }
 938
 939    fn select_register(&mut self, register: Arc<str>, cx: &mut ViewContext<Self>) {
 940        if register.chars().count() == 1 {
 941            self.selected_register
 942                .replace(register.chars().next().unwrap());
 943        }
 944        self.operator_stack.clear();
 945        self.sync_vim_settings(cx);
 946    }
 947
 948    fn maybe_pop_operator(&mut self) -> Option<Operator> {
 949        self.operator_stack.pop()
 950    }
 951
 952    fn pop_operator(&mut self, cx: &mut ViewContext<Self>) -> Operator {
 953        let popped_operator = self.operator_stack.pop()
 954            .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
 955        self.sync_vim_settings(cx);
 956        popped_operator
 957    }
 958
 959    fn clear_operator(&mut self, cx: &mut ViewContext<Self>) {
 960        Vim::take_count(cx);
 961        self.selected_register.take();
 962        self.operator_stack.clear();
 963        self.sync_vim_settings(cx);
 964    }
 965
 966    fn active_operator(&self) -> Option<Operator> {
 967        self.operator_stack.last().cloned()
 968    }
 969
 970    fn transaction_begun(&mut self, transaction_id: TransactionId, _: &mut ViewContext<Self>) {
 971        let mode = if (self.mode == Mode::Insert
 972            || self.mode == Mode::Replace
 973            || self.mode == Mode::Normal)
 974            && self.current_tx.is_none()
 975        {
 976            self.current_tx = Some(transaction_id);
 977            self.last_mode
 978        } else {
 979            self.mode
 980        };
 981        if mode == Mode::VisualLine || mode == Mode::VisualBlock {
 982            self.undo_modes.insert(transaction_id, mode);
 983        }
 984    }
 985
 986    fn transaction_undone(&mut self, transaction_id: &TransactionId, cx: &mut ViewContext<Self>) {
 987        match self.mode {
 988            Mode::VisualLine | Mode::VisualBlock | Mode::Visual => {
 989                self.update_editor(cx, |vim, editor, cx| {
 990                    let original_mode = vim.undo_modes.get(transaction_id);
 991                    editor.change_selections(None, cx, |s| match original_mode {
 992                        Some(Mode::VisualLine) => {
 993                            s.move_with(|map, selection| {
 994                                selection.collapse_to(
 995                                    map.prev_line_boundary(selection.start.to_point(map)).1,
 996                                    SelectionGoal::None,
 997                                )
 998                            });
 999                        }
1000                        Some(Mode::VisualBlock) => {
1001                            let mut first = s.first_anchor();
1002                            first.collapse_to(first.start, first.goal);
1003                            s.select_anchors(vec![first]);
1004                        }
1005                        _ => {
1006                            s.move_with(|map, selection| {
1007                                selection.collapse_to(
1008                                    map.clip_at_line_end(selection.start),
1009                                    selection.goal,
1010                                );
1011                            });
1012                        }
1013                    });
1014                });
1015                self.switch_mode(Mode::Normal, true, cx)
1016            }
1017            Mode::Normal => {
1018                self.update_editor(cx, |_, editor, cx| {
1019                    editor.change_selections(None, cx, |s| {
1020                        s.move_with(|map, selection| {
1021                            selection
1022                                .collapse_to(map.clip_at_line_end(selection.end), selection.goal)
1023                        })
1024                    })
1025                });
1026            }
1027            Mode::Insert | Mode::Replace | Mode::HelixNormal => {}
1028        }
1029    }
1030
1031    fn local_selections_changed(&mut self, cx: &mut ViewContext<Self>) {
1032        let Some(editor) = self.editor() else { return };
1033
1034        if editor.read(cx).leader_peer_id().is_some() {
1035            return;
1036        }
1037
1038        let newest = editor.read(cx).selections.newest_anchor().clone();
1039        let is_multicursor = editor.read(cx).selections.count() > 1;
1040        if self.mode == Mode::Insert && self.current_tx.is_some() {
1041            if self.current_anchor.is_none() {
1042                self.current_anchor = Some(newest);
1043            } else if self.current_anchor.as_ref().unwrap() != &newest {
1044                if let Some(tx_id) = self.current_tx.take() {
1045                    self.update_editor(cx, |_, editor, cx| {
1046                        editor.group_until_transaction(tx_id, cx)
1047                    });
1048                }
1049            }
1050        } else if self.mode == Mode::Normal && newest.start != newest.end {
1051            if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
1052                self.switch_mode(Mode::VisualBlock, false, cx);
1053            } else {
1054                self.switch_mode(Mode::Visual, false, cx)
1055            }
1056        } else if newest.start == newest.end
1057            && !is_multicursor
1058            && [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&self.mode)
1059        {
1060            self.switch_mode(Mode::Normal, true, cx);
1061        }
1062    }
1063
1064    fn input_ignored(&mut self, text: Arc<str>, cx: &mut ViewContext<Self>) {
1065        if text.is_empty() {
1066            return;
1067        }
1068
1069        match self.active_operator() {
1070            Some(Operator::FindForward { before }) => {
1071                let find = Motion::FindForward {
1072                    before,
1073                    char: text.chars().next().unwrap(),
1074                    mode: if VimSettings::get_global(cx).use_multiline_find {
1075                        FindRange::MultiLine
1076                    } else {
1077                        FindRange::SingleLine
1078                    },
1079                    smartcase: VimSettings::get_global(cx).use_smartcase_find,
1080                };
1081                Vim::globals(cx).last_find = Some(find.clone());
1082                self.motion(find, cx)
1083            }
1084            Some(Operator::FindBackward { after }) => {
1085                let find = Motion::FindBackward {
1086                    after,
1087                    char: text.chars().next().unwrap(),
1088                    mode: if VimSettings::get_global(cx).use_multiline_find {
1089                        FindRange::MultiLine
1090                    } else {
1091                        FindRange::SingleLine
1092                    },
1093                    smartcase: VimSettings::get_global(cx).use_smartcase_find,
1094                };
1095                Vim::globals(cx).last_find = Some(find.clone());
1096                self.motion(find, cx)
1097            }
1098            Some(Operator::Sneak { first_char }) => {
1099                if let Some(first_char) = first_char {
1100                    if let Some(second_char) = text.chars().next() {
1101                        let sneak = Motion::Sneak {
1102                            first_char,
1103                            second_char,
1104                            smartcase: VimSettings::get_global(cx).use_smartcase_find,
1105                        };
1106                        Vim::globals(cx).last_find = Some((&sneak).clone());
1107                        self.motion(sneak, cx)
1108                    }
1109                } else {
1110                    let first_char = text.chars().next();
1111                    self.pop_operator(cx);
1112                    self.push_operator(Operator::Sneak { first_char }, cx);
1113                }
1114            }
1115            Some(Operator::SneakBackward { first_char }) => {
1116                if let Some(first_char) = first_char {
1117                    if let Some(second_char) = text.chars().next() {
1118                        let sneak = Motion::SneakBackward {
1119                            first_char,
1120                            second_char,
1121                            smartcase: VimSettings::get_global(cx).use_smartcase_find,
1122                        };
1123                        Vim::globals(cx).last_find = Some((&sneak).clone());
1124                        self.motion(sneak, cx)
1125                    }
1126                } else {
1127                    let first_char = text.chars().next();
1128                    self.pop_operator(cx);
1129                    self.push_operator(Operator::SneakBackward { first_char }, cx);
1130                }
1131            }
1132            Some(Operator::Replace) => match self.mode {
1133                Mode::Normal => self.normal_replace(text, cx),
1134                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
1135                    self.visual_replace(text, cx)
1136                }
1137                _ => self.clear_operator(cx),
1138            },
1139            Some(Operator::Digraph { first_char }) => {
1140                if let Some(first_char) = first_char {
1141                    if let Some(second_char) = text.chars().next() {
1142                        self.insert_digraph(first_char, second_char, cx);
1143                    }
1144                } else {
1145                    let first_char = text.chars().next();
1146                    self.pop_operator(cx);
1147                    self.push_operator(Operator::Digraph { first_char }, cx);
1148                }
1149            }
1150            Some(Operator::Literal { prefix }) => {
1151                self.handle_literal_input(prefix.unwrap_or_default(), &text, cx)
1152            }
1153            Some(Operator::AddSurrounds { target }) => match self.mode {
1154                Mode::Normal => {
1155                    if let Some(target) = target {
1156                        self.add_surrounds(text, target, cx);
1157                        self.clear_operator(cx);
1158                    }
1159                }
1160                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
1161                    self.add_surrounds(text, SurroundsType::Selection, cx);
1162                    self.clear_operator(cx);
1163                }
1164                _ => self.clear_operator(cx),
1165            },
1166            Some(Operator::ChangeSurrounds { target }) => match self.mode {
1167                Mode::Normal => {
1168                    if let Some(target) = target {
1169                        self.change_surrounds(text, target, cx);
1170                        self.clear_operator(cx);
1171                    }
1172                }
1173                _ => self.clear_operator(cx),
1174            },
1175            Some(Operator::DeleteSurrounds) => match self.mode {
1176                Mode::Normal => {
1177                    self.delete_surrounds(text, cx);
1178                    self.clear_operator(cx);
1179                }
1180                _ => self.clear_operator(cx),
1181            },
1182            Some(Operator::Mark) => self.create_mark(text, false, cx),
1183            Some(Operator::RecordRegister) => {
1184                self.record_register(text.chars().next().unwrap(), cx)
1185            }
1186            Some(Operator::ReplayRegister) => {
1187                self.replay_register(text.chars().next().unwrap(), cx)
1188            }
1189            Some(Operator::Register) => match self.mode {
1190                Mode::Insert => {
1191                    self.update_editor(cx, |_, editor, cx| {
1192                        if let Some(register) = Vim::update_globals(cx, |globals, cx| {
1193                            globals.read_register(text.chars().next(), Some(editor), cx)
1194                        }) {
1195                            editor.do_paste(
1196                                &register.text.to_string(),
1197                                register.clipboard_selections.clone(),
1198                                false,
1199                                cx,
1200                            )
1201                        }
1202                    });
1203                    self.clear_operator(cx);
1204                }
1205                _ => {
1206                    self.select_register(text, cx);
1207                }
1208            },
1209            Some(Operator::Jump { line }) => self.jump(text, line, cx),
1210            _ => {
1211                if self.mode == Mode::Replace {
1212                    self.multi_replace(text, cx)
1213                }
1214
1215                if self.mode == Mode::Normal {
1216                    self.update_editor(cx, |_, editor, cx| {
1217                        editor.accept_inline_completion(
1218                            &editor::actions::AcceptInlineCompletion {},
1219                            cx,
1220                        );
1221                    });
1222                }
1223            }
1224        }
1225    }
1226
1227    fn sync_vim_settings(&mut self, cx: &mut ViewContext<Self>) {
1228        self.update_editor(cx, |vim, editor, cx| {
1229            editor.set_cursor_shape(vim.cursor_shape(), cx);
1230            editor.set_clip_at_line_ends(vim.clip_at_line_ends(), cx);
1231            editor.set_collapse_matches(true);
1232            editor.set_input_enabled(vim.editor_input_enabled());
1233            editor.set_autoindent(vim.should_autoindent());
1234            editor.selections.line_mode = matches!(vim.mode, Mode::VisualLine);
1235
1236            let enable_inline_completions = match vim.mode {
1237                Mode::Insert | Mode::Replace => true,
1238                Mode::Normal => editor
1239                    .inline_completion_provider()
1240                    .map_or(false, |provider| provider.show_completions_in_normal_mode()),
1241                _ => false,
1242            };
1243            editor.set_inline_completions_enabled(enable_inline_completions, cx);
1244        });
1245        cx.notify()
1246    }
1247}
1248
1249/// Controls when to use system clipboard.
1250#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
1251#[serde(rename_all = "snake_case")]
1252pub enum UseSystemClipboard {
1253    /// Don't use system clipboard.
1254    Never,
1255    /// Use system clipboard.
1256    Always,
1257    /// Use system clipboard for yank operations.
1258    OnYank,
1259}
1260
1261#[derive(Deserialize)]
1262struct VimSettings {
1263    pub toggle_relative_line_numbers: bool,
1264    pub use_system_clipboard: UseSystemClipboard,
1265    pub use_multiline_find: bool,
1266    pub use_smartcase_find: bool,
1267    pub custom_digraphs: HashMap<String, Arc<str>>,
1268    pub highlight_on_yank_duration: u64,
1269}
1270
1271#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
1272struct VimSettingsContent {
1273    pub toggle_relative_line_numbers: Option<bool>,
1274    pub use_system_clipboard: Option<UseSystemClipboard>,
1275    pub use_multiline_find: Option<bool>,
1276    pub use_smartcase_find: Option<bool>,
1277    pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
1278    pub highlight_on_yank_duration: Option<u64>,
1279}
1280
1281impl Settings for VimSettings {
1282    const KEY: Option<&'static str> = Some("vim");
1283
1284    type FileContent = VimSettingsContent;
1285
1286    fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
1287        sources.json_merge()
1288    }
1289}