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