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::Lowercase
 474                | Operator::Uppercase
 475                | Operator::OppositeCase
 476                | Operator::ToggleComments
 477        ) {
 478            self.start_recording(cx)
 479        };
 480        // Since these operations can only be entered with pre-operators,
 481        // we need to clear the previous operators when pushing,
 482        // so that the current stack is the most correct
 483        if matches!(
 484            operator,
 485            Operator::AddSurrounds { .. }
 486                | Operator::ChangeSurrounds { .. }
 487                | Operator::DeleteSurrounds
 488        ) {
 489            self.operator_stack.clear();
 490            if let Operator::AddSurrounds { target: None } = operator {
 491                self.start_recording(cx);
 492            }
 493        };
 494        self.operator_stack.push(operator);
 495        self.sync_vim_settings(cx);
 496    }
 497
 498    pub fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut ViewContext<Self>) {
 499        if self.temp_mode && mode == Mode::Normal {
 500            self.temp_mode = false;
 501            self.switch_mode(Mode::Normal, leave_selections, cx);
 502            self.switch_mode(Mode::Insert, false, cx);
 503            return;
 504        } else if self.temp_mode
 505            && !matches!(mode, Mode::Visual | Mode::VisualLine | Mode::VisualBlock)
 506        {
 507            self.temp_mode = false;
 508        }
 509
 510        let last_mode = self.mode;
 511        let prior_mode = self.last_mode;
 512        let prior_tx = self.current_tx;
 513        self.last_mode = last_mode;
 514        self.mode = mode;
 515        self.operator_stack.clear();
 516        self.selected_register.take();
 517        if mode == Mode::Normal || mode != last_mode {
 518            self.current_tx.take();
 519            self.current_anchor.take();
 520        }
 521        if mode != Mode::Insert && mode != Mode::Replace {
 522            Vim::take_count(cx);
 523        }
 524
 525        // Sync editor settings like clip mode
 526        self.sync_vim_settings(cx);
 527
 528        if VimSettings::get_global(cx).toggle_relative_line_numbers
 529            && self.mode != self.last_mode
 530            && (self.mode == Mode::Insert || self.last_mode == Mode::Insert)
 531        {
 532            self.update_editor(cx, |vim, editor, cx| {
 533                let is_relative = vim.mode != Mode::Insert;
 534                editor.set_relative_line_number(Some(is_relative), cx)
 535            });
 536        }
 537
 538        if leave_selections {
 539            return;
 540        }
 541
 542        if !mode.is_visual() && last_mode.is_visual() {
 543            self.create_visual_marks(last_mode, cx);
 544        }
 545
 546        // Adjust selections
 547        self.update_editor(cx, |vim, editor, cx| {
 548            if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
 549            {
 550                vim.visual_block_motion(true, editor, cx, |_, point, goal| Some((point, goal)))
 551            }
 552            if last_mode == Mode::Insert || last_mode == Mode::Replace {
 553                if let Some(prior_tx) = prior_tx {
 554                    editor.group_until_transaction(prior_tx, cx)
 555                }
 556            }
 557
 558            editor.change_selections(None, cx, |s| {
 559                // we cheat with visual block mode and use multiple cursors.
 560                // the cost of this cheat is we need to convert back to a single
 561                // cursor whenever vim would.
 562                if last_mode == Mode::VisualBlock
 563                    && (mode != Mode::VisualBlock && mode != Mode::Insert)
 564                {
 565                    let tail = s.oldest_anchor().tail();
 566                    let head = s.newest_anchor().head();
 567                    s.select_anchor_ranges(vec![tail..head]);
 568                } else if last_mode == Mode::Insert
 569                    && prior_mode == Mode::VisualBlock
 570                    && mode != Mode::VisualBlock
 571                {
 572                    let pos = s.first_anchor().head();
 573                    s.select_anchor_ranges(vec![pos..pos])
 574                }
 575
 576                let snapshot = s.display_map();
 577                if let Some(pending) = s.pending.as_mut() {
 578                    if pending.selection.reversed && mode.is_visual() && !last_mode.is_visual() {
 579                        let mut end = pending.selection.end.to_point(&snapshot.buffer_snapshot);
 580                        end = snapshot
 581                            .buffer_snapshot
 582                            .clip_point(end + Point::new(0, 1), Bias::Right);
 583                        pending.selection.end = snapshot.buffer_snapshot.anchor_before(end);
 584                    }
 585                }
 586
 587                s.move_with(|map, selection| {
 588                    if last_mode.is_visual() && !mode.is_visual() {
 589                        let mut point = selection.head();
 590                        if !selection.reversed && !selection.is_empty() {
 591                            point = movement::left(map, selection.head());
 592                        }
 593                        selection.collapse_to(point, selection.goal)
 594                    } else if !last_mode.is_visual() && mode.is_visual() && selection.is_empty() {
 595                        selection.end = movement::right(map, selection.start);
 596                    }
 597                });
 598            })
 599        });
 600    }
 601
 602    pub fn take_count(cx: &mut AppContext) -> Option<usize> {
 603        let global_state = cx.global_mut::<VimGlobals>();
 604        if global_state.dot_replaying {
 605            return global_state.recorded_count;
 606        }
 607
 608        let count = if global_state.post_count.is_none() && global_state.pre_count.is_none() {
 609            return None;
 610        } else {
 611            Some(
 612                global_state.post_count.take().unwrap_or(1)
 613                    * global_state.pre_count.take().unwrap_or(1),
 614            )
 615        };
 616
 617        if global_state.dot_recording {
 618            global_state.recorded_count = count;
 619        }
 620        count
 621    }
 622
 623    pub fn cursor_shape(&self) -> CursorShape {
 624        match self.mode {
 625            Mode::Normal => {
 626                if self.operator_stack.is_empty() {
 627                    CursorShape::Block
 628                } else {
 629                    CursorShape::Underline
 630                }
 631            }
 632            Mode::Replace => CursorShape::Underline,
 633            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
 634            Mode::Insert => CursorShape::Bar,
 635        }
 636    }
 637
 638    pub fn editor_input_enabled(&self) -> bool {
 639        match self.mode {
 640            Mode::Insert => {
 641                if let Some(operator) = self.operator_stack.last() {
 642                    !operator.is_waiting(self.mode)
 643                } else {
 644                    true
 645                }
 646            }
 647            Mode::Normal | Mode::Replace | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
 648                false
 649            }
 650        }
 651    }
 652
 653    pub fn should_autoindent(&self) -> bool {
 654        !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
 655    }
 656
 657    pub fn clip_at_line_ends(&self) -> bool {
 658        match self.mode {
 659            Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::Replace => {
 660                false
 661            }
 662            Mode::Normal => true,
 663        }
 664    }
 665
 666    pub fn extend_key_context(&self, context: &mut KeyContext, cx: &AppContext) {
 667        let mut mode = match self.mode {
 668            Mode::Normal => "normal",
 669            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
 670            Mode::Insert => "insert",
 671            Mode::Replace => "replace",
 672        }
 673        .to_string();
 674
 675        let mut operator_id = "none";
 676
 677        let active_operator = self.active_operator();
 678        if active_operator.is_none() && cx.global::<VimGlobals>().pre_count.is_some()
 679            || active_operator.is_some() && cx.global::<VimGlobals>().post_count.is_some()
 680        {
 681            context.add("VimCount");
 682        }
 683
 684        if let Some(active_operator) = active_operator {
 685            if active_operator.is_waiting(self.mode) {
 686                if matches!(active_operator, Operator::Literal { .. }) {
 687                    mode = "literal".to_string();
 688                } else {
 689                    mode = "waiting".to_string();
 690                }
 691            } else {
 692                operator_id = active_operator.id();
 693                mode = "operator".to_string();
 694            }
 695        }
 696
 697        if mode == "normal" || mode == "visual" || mode == "operator" {
 698            context.add("VimControl");
 699        }
 700        context.set("vim_mode", mode);
 701        context.set("vim_operator", operator_id);
 702    }
 703
 704    fn focused(&mut self, preserve_selection: bool, cx: &mut ViewContext<Self>) {
 705        let Some(editor) = self.editor() else {
 706            return;
 707        };
 708        let newest_selection_empty = editor.update(cx, |editor, cx| {
 709            editor.selections.newest::<usize>(cx).is_empty()
 710        });
 711        let editor = editor.read(cx);
 712        let editor_mode = editor.mode();
 713
 714        if editor_mode == EditorMode::Full
 715                && !newest_selection_empty
 716                && self.mode == Mode::Normal
 717                // When following someone, don't switch vim mode.
 718                && editor.leader_peer_id().is_none()
 719        {
 720            if preserve_selection {
 721                self.switch_mode(Mode::Visual, true, cx);
 722            } else {
 723                self.update_editor(cx, |_, editor, cx| {
 724                    editor.set_clip_at_line_ends(false, cx);
 725                    editor.change_selections(None, cx, |s| {
 726                        s.move_with(|_, selection| {
 727                            selection.collapse_to(selection.start, selection.goal)
 728                        })
 729                    });
 730                });
 731            }
 732        }
 733
 734        cx.emit(VimEvent::Focused);
 735        self.sync_vim_settings(cx);
 736
 737        if VimSettings::get_global(cx).toggle_relative_line_numbers {
 738            if let Some(old_vim) = Vim::globals(cx).focused_vim() {
 739                if old_vim.entity_id() != cx.view().entity_id() {
 740                    old_vim.update(cx, |vim, cx| {
 741                        vim.update_editor(cx, |_, editor, cx| {
 742                            editor.set_relative_line_number(None, cx)
 743                        });
 744                    });
 745
 746                    self.update_editor(cx, |vim, editor, cx| {
 747                        let is_relative = vim.mode != Mode::Insert;
 748                        editor.set_relative_line_number(Some(is_relative), cx)
 749                    });
 750                }
 751            } else {
 752                self.update_editor(cx, |vim, editor, cx| {
 753                    let is_relative = vim.mode != Mode::Insert;
 754                    editor.set_relative_line_number(Some(is_relative), cx)
 755                });
 756            }
 757        }
 758        Vim::globals(cx).focused_vim = Some(cx.view().downgrade());
 759    }
 760
 761    fn blurred(&mut self, cx: &mut ViewContext<Self>) {
 762        self.stop_recording_immediately(NormalBefore.boxed_clone(), cx);
 763        self.store_visual_marks(cx);
 764        self.clear_operator(cx);
 765        self.update_editor(cx, |_, editor, cx| {
 766            editor.set_cursor_shape(language::CursorShape::Hollow, cx);
 767        });
 768    }
 769
 770    fn cursor_shape_changed(&mut self, cx: &mut ViewContext<Self>) {
 771        self.update_editor(cx, |vim, editor, cx| {
 772            editor.set_cursor_shape(vim.cursor_shape(), cx);
 773        });
 774    }
 775
 776    fn update_editor<S>(
 777        &mut self,
 778        cx: &mut ViewContext<Self>,
 779        update: impl FnOnce(&mut Self, &mut Editor, &mut ViewContext<Editor>) -> S,
 780    ) -> Option<S> {
 781        let editor = self.editor.upgrade()?;
 782        Some(editor.update(cx, |editor, cx| update(self, editor, cx)))
 783    }
 784
 785    fn editor_selections(&mut self, cx: &mut ViewContext<Self>) -> Vec<Range<Anchor>> {
 786        self.update_editor(cx, |_, editor, _| {
 787            editor
 788                .selections
 789                .disjoint_anchors()
 790                .iter()
 791                .map(|selection| selection.tail()..selection.head())
 792                .collect()
 793        })
 794        .unwrap_or_default()
 795    }
 796
 797    /// When doing an action that modifies the buffer, we start recording so that `.`
 798    /// will replay the action.
 799    pub fn start_recording(&mut self, cx: &mut ViewContext<Self>) {
 800        Vim::update_globals(cx, |globals, cx| {
 801            if !globals.dot_replaying {
 802                globals.dot_recording = true;
 803                globals.recording_actions = Default::default();
 804                globals.recorded_count = None;
 805
 806                let selections = self.editor().map(|editor| {
 807                    editor.update(cx, |editor, cx| {
 808                        (
 809                            editor.selections.oldest::<Point>(cx),
 810                            editor.selections.newest::<Point>(cx),
 811                        )
 812                    })
 813                });
 814
 815                if let Some((oldest, newest)) = selections {
 816                    globals.recorded_selection = match self.mode {
 817                        Mode::Visual if newest.end.row == newest.start.row => {
 818                            RecordedSelection::SingleLine {
 819                                cols: newest.end.column - newest.start.column,
 820                            }
 821                        }
 822                        Mode::Visual => RecordedSelection::Visual {
 823                            rows: newest.end.row - newest.start.row,
 824                            cols: newest.end.column,
 825                        },
 826                        Mode::VisualLine => RecordedSelection::VisualLine {
 827                            rows: newest.end.row - newest.start.row,
 828                        },
 829                        Mode::VisualBlock => RecordedSelection::VisualBlock {
 830                            rows: newest.end.row.abs_diff(oldest.start.row),
 831                            cols: newest.end.column.abs_diff(oldest.start.column),
 832                        },
 833                        _ => RecordedSelection::None,
 834                    }
 835                } else {
 836                    globals.recorded_selection = RecordedSelection::None;
 837                }
 838            }
 839        })
 840    }
 841
 842    pub fn stop_replaying(&mut self, cx: &mut ViewContext<Self>) {
 843        let globals = Vim::globals(cx);
 844        globals.dot_replaying = false;
 845        if let Some(replayer) = globals.replayer.take() {
 846            replayer.stop();
 847        }
 848    }
 849
 850    /// When finishing an action that modifies the buffer, stop recording.
 851    /// as you usually call this within a keystroke handler we also ensure that
 852    /// the current action is recorded.
 853    pub fn stop_recording(&mut self, cx: &mut ViewContext<Self>) {
 854        let globals = Vim::globals(cx);
 855        if globals.dot_recording {
 856            globals.stop_recording_after_next_action = true;
 857        }
 858        self.exit_temporary_mode = self.temp_mode;
 859    }
 860
 861    /// Stops recording actions immediately rather than waiting until after the
 862    /// next action to stop recording.
 863    ///
 864    /// This doesn't include the current action.
 865    pub fn stop_recording_immediately(
 866        &mut self,
 867        action: Box<dyn Action>,
 868        cx: &mut ViewContext<Self>,
 869    ) {
 870        let globals = Vim::globals(cx);
 871        if globals.dot_recording {
 872            globals
 873                .recording_actions
 874                .push(ReplayableAction::Action(action.boxed_clone()));
 875            globals.recorded_actions = mem::take(&mut globals.recording_actions);
 876            globals.dot_recording = false;
 877            globals.stop_recording_after_next_action = false;
 878        }
 879        self.exit_temporary_mode = self.temp_mode;
 880    }
 881
 882    /// Explicitly record one action (equivalents to start_recording and stop_recording)
 883    pub fn record_current_action(&mut self, cx: &mut ViewContext<Self>) {
 884        self.start_recording(cx);
 885        self.stop_recording(cx);
 886    }
 887
 888    fn push_count_digit(&mut self, number: usize, cx: &mut ViewContext<Self>) {
 889        if self.active_operator().is_some() {
 890            let post_count = Vim::globals(cx).post_count.unwrap_or(0);
 891
 892            Vim::globals(cx).post_count = Some(
 893                post_count
 894                    .checked_mul(10)
 895                    .and_then(|post_count| post_count.checked_add(number))
 896                    .unwrap_or(post_count),
 897            )
 898        } else {
 899            let pre_count = Vim::globals(cx).pre_count.unwrap_or(0);
 900
 901            Vim::globals(cx).pre_count = Some(
 902                pre_count
 903                    .checked_mul(10)
 904                    .and_then(|pre_count| pre_count.checked_add(number))
 905                    .unwrap_or(pre_count),
 906            )
 907        }
 908        // update the keymap so that 0 works
 909        self.sync_vim_settings(cx)
 910    }
 911
 912    fn select_register(&mut self, register: Arc<str>, cx: &mut ViewContext<Self>) {
 913        if register.chars().count() == 1 {
 914            self.selected_register
 915                .replace(register.chars().next().unwrap());
 916        }
 917        self.operator_stack.clear();
 918        self.sync_vim_settings(cx);
 919    }
 920
 921    fn maybe_pop_operator(&mut self) -> Option<Operator> {
 922        self.operator_stack.pop()
 923    }
 924
 925    fn pop_operator(&mut self, cx: &mut ViewContext<Self>) -> Operator {
 926        let popped_operator = self.operator_stack.pop()
 927            .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
 928        self.sync_vim_settings(cx);
 929        popped_operator
 930    }
 931
 932    fn clear_operator(&mut self, cx: &mut ViewContext<Self>) {
 933        Vim::take_count(cx);
 934        self.selected_register.take();
 935        self.operator_stack.clear();
 936        self.sync_vim_settings(cx);
 937    }
 938
 939    fn active_operator(&self) -> Option<Operator> {
 940        self.operator_stack.last().cloned()
 941    }
 942
 943    fn transaction_begun(&mut self, transaction_id: TransactionId, _: &mut ViewContext<Self>) {
 944        let mode = if (self.mode == Mode::Insert
 945            || self.mode == Mode::Replace
 946            || self.mode == Mode::Normal)
 947            && self.current_tx.is_none()
 948        {
 949            self.current_tx = Some(transaction_id);
 950            self.last_mode
 951        } else {
 952            self.mode
 953        };
 954        if mode == Mode::VisualLine || mode == Mode::VisualBlock {
 955            self.undo_modes.insert(transaction_id, mode);
 956        }
 957    }
 958
 959    fn transaction_undone(&mut self, transaction_id: &TransactionId, cx: &mut ViewContext<Self>) {
 960        match self.mode {
 961            Mode::VisualLine | Mode::VisualBlock | Mode::Visual => {
 962                self.update_editor(cx, |vim, editor, cx| {
 963                    let original_mode = vim.undo_modes.get(transaction_id);
 964                    editor.change_selections(None, cx, |s| match original_mode {
 965                        Some(Mode::VisualLine) => {
 966                            s.move_with(|map, selection| {
 967                                selection.collapse_to(
 968                                    map.prev_line_boundary(selection.start.to_point(map)).1,
 969                                    SelectionGoal::None,
 970                                )
 971                            });
 972                        }
 973                        Some(Mode::VisualBlock) => {
 974                            let mut first = s.first_anchor();
 975                            first.collapse_to(first.start, first.goal);
 976                            s.select_anchors(vec![first]);
 977                        }
 978                        _ => {
 979                            s.move_with(|map, selection| {
 980                                selection.collapse_to(
 981                                    map.clip_at_line_end(selection.start),
 982                                    selection.goal,
 983                                );
 984                            });
 985                        }
 986                    });
 987                });
 988                self.switch_mode(Mode::Normal, true, cx)
 989            }
 990            Mode::Normal => {
 991                self.update_editor(cx, |_, editor, cx| {
 992                    editor.change_selections(None, cx, |s| {
 993                        s.move_with(|map, selection| {
 994                            selection
 995                                .collapse_to(map.clip_at_line_end(selection.end), selection.goal)
 996                        })
 997                    })
 998                });
 999            }
1000            Mode::Insert | Mode::Replace => {}
1001        }
1002    }
1003
1004    fn local_selections_changed(&mut self, cx: &mut ViewContext<Self>) {
1005        let Some(editor) = self.editor() else { return };
1006
1007        if editor.read(cx).leader_peer_id().is_some() {
1008            return;
1009        }
1010
1011        let newest = editor.read(cx).selections.newest_anchor().clone();
1012        let is_multicursor = editor.read(cx).selections.count() > 1;
1013        if self.mode == Mode::Insert && self.current_tx.is_some() {
1014            if self.current_anchor.is_none() {
1015                self.current_anchor = Some(newest);
1016            } else if self.current_anchor.as_ref().unwrap() != &newest {
1017                if let Some(tx_id) = self.current_tx.take() {
1018                    self.update_editor(cx, |_, editor, cx| {
1019                        editor.group_until_transaction(tx_id, cx)
1020                    });
1021                }
1022            }
1023        } else if self.mode == Mode::Normal && newest.start != newest.end {
1024            if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
1025                self.switch_mode(Mode::VisualBlock, false, cx);
1026            } else {
1027                self.switch_mode(Mode::Visual, false, cx)
1028            }
1029        } else if newest.start == newest.end
1030            && !is_multicursor
1031            && [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&self.mode)
1032        {
1033            self.switch_mode(Mode::Normal, true, cx);
1034        }
1035    }
1036
1037    fn input_ignored(&mut self, text: Arc<str>, cx: &mut ViewContext<Self>) {
1038        if text.is_empty() {
1039            return;
1040        }
1041
1042        match self.active_operator() {
1043            Some(Operator::FindForward { before }) => {
1044                let find = Motion::FindForward {
1045                    before,
1046                    char: text.chars().next().unwrap(),
1047                    mode: if VimSettings::get_global(cx).use_multiline_find {
1048                        FindRange::MultiLine
1049                    } else {
1050                        FindRange::SingleLine
1051                    },
1052                    smartcase: VimSettings::get_global(cx).use_smartcase_find,
1053                };
1054                Vim::globals(cx).last_find = Some(find.clone());
1055                self.motion(find, cx)
1056            }
1057            Some(Operator::FindBackward { after }) => {
1058                let find = Motion::FindBackward {
1059                    after,
1060                    char: text.chars().next().unwrap(),
1061                    mode: if VimSettings::get_global(cx).use_multiline_find {
1062                        FindRange::MultiLine
1063                    } else {
1064                        FindRange::SingleLine
1065                    },
1066                    smartcase: VimSettings::get_global(cx).use_smartcase_find,
1067                };
1068                Vim::globals(cx).last_find = Some(find.clone());
1069                self.motion(find, cx)
1070            }
1071            Some(Operator::Replace) => match self.mode {
1072                Mode::Normal => self.normal_replace(text, cx),
1073                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
1074                    self.visual_replace(text, cx)
1075                }
1076                _ => self.clear_operator(cx),
1077            },
1078            Some(Operator::Digraph { first_char }) => {
1079                if let Some(first_char) = first_char {
1080                    if let Some(second_char) = text.chars().next() {
1081                        self.insert_digraph(first_char, second_char, cx);
1082                    }
1083                } else {
1084                    let first_char = text.chars().next();
1085                    self.pop_operator(cx);
1086                    self.push_operator(Operator::Digraph { first_char }, cx);
1087                }
1088            }
1089            Some(Operator::Literal { prefix }) => {
1090                self.handle_literal_input(prefix.unwrap_or_default(), &text, cx)
1091            }
1092            Some(Operator::AddSurrounds { target }) => match self.mode {
1093                Mode::Normal => {
1094                    if let Some(target) = target {
1095                        self.add_surrounds(text, target, cx);
1096                        self.clear_operator(cx);
1097                    }
1098                }
1099                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
1100                    self.add_surrounds(text, SurroundsType::Selection, cx);
1101                    self.clear_operator(cx);
1102                }
1103                _ => self.clear_operator(cx),
1104            },
1105            Some(Operator::ChangeSurrounds { target }) => match self.mode {
1106                Mode::Normal => {
1107                    if let Some(target) = target {
1108                        self.change_surrounds(text, target, cx);
1109                        self.clear_operator(cx);
1110                    }
1111                }
1112                _ => self.clear_operator(cx),
1113            },
1114            Some(Operator::DeleteSurrounds) => match self.mode {
1115                Mode::Normal => {
1116                    self.delete_surrounds(text, cx);
1117                    self.clear_operator(cx);
1118                }
1119                _ => self.clear_operator(cx),
1120            },
1121            Some(Operator::Mark) => self.create_mark(text, false, cx),
1122            Some(Operator::RecordRegister) => {
1123                self.record_register(text.chars().next().unwrap(), cx)
1124            }
1125            Some(Operator::ReplayRegister) => {
1126                self.replay_register(text.chars().next().unwrap(), cx)
1127            }
1128            Some(Operator::Register) => match self.mode {
1129                Mode::Insert => {
1130                    self.update_editor(cx, |_, editor, cx| {
1131                        if let Some(register) = Vim::update_globals(cx, |globals, cx| {
1132                            globals.read_register(text.chars().next(), Some(editor), cx)
1133                        }) {
1134                            editor.do_paste(
1135                                &register.text.to_string(),
1136                                register.clipboard_selections.clone(),
1137                                false,
1138                                cx,
1139                            )
1140                        }
1141                    });
1142                    self.clear_operator(cx);
1143                }
1144                _ => {
1145                    self.select_register(text, cx);
1146                }
1147            },
1148            Some(Operator::Jump { line }) => self.jump(text, line, cx),
1149            _ => {
1150                if self.mode == Mode::Replace {
1151                    self.multi_replace(text, cx)
1152                }
1153            }
1154        }
1155    }
1156
1157    fn sync_vim_settings(&mut self, cx: &mut ViewContext<Self>) {
1158        self.update_editor(cx, |vim, editor, cx| {
1159            editor.set_cursor_shape(vim.cursor_shape(), cx);
1160            editor.set_clip_at_line_ends(vim.clip_at_line_ends(), cx);
1161            editor.set_collapse_matches(true);
1162            editor.set_input_enabled(vim.editor_input_enabled());
1163            editor.set_autoindent(vim.should_autoindent());
1164            editor.selections.line_mode = matches!(vim.mode, Mode::VisualLine);
1165            editor.set_inline_completions_enabled(matches!(vim.mode, Mode::Insert | Mode::Replace));
1166        });
1167        cx.notify()
1168    }
1169}
1170
1171/// Controls when to use system clipboard.
1172#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
1173#[serde(rename_all = "snake_case")]
1174pub enum UseSystemClipboard {
1175    /// Don't use system clipboard.
1176    Never,
1177    /// Use system clipboard.
1178    Always,
1179    /// Use system clipboard for yank operations.
1180    OnYank,
1181}
1182
1183#[derive(Deserialize)]
1184struct VimSettings {
1185    pub toggle_relative_line_numbers: bool,
1186    pub use_system_clipboard: UseSystemClipboard,
1187    pub use_multiline_find: bool,
1188    pub use_smartcase_find: bool,
1189    pub custom_digraphs: HashMap<String, Arc<str>>,
1190}
1191
1192#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
1193struct VimSettingsContent {
1194    pub toggle_relative_line_numbers: Option<bool>,
1195    pub use_system_clipboard: Option<UseSystemClipboard>,
1196    pub use_multiline_find: Option<bool>,
1197    pub use_smartcase_find: Option<bool>,
1198    pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
1199}
1200
1201impl Settings for VimSettings {
1202    const KEY: Option<&'static str> = Some("vim");
1203
1204    type FileContent = VimSettingsContent;
1205
1206    fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
1207        sources.json_merge()
1208    }
1209}