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