vim.rs

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