use std::{ops::Range, sync::Arc}; use gpui::{Action, KeyContext}; use language::CursorShape; use serde::{Deserialize, Serialize}; use workspace::searchable::Direction; use crate::motion::Motion; #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum Mode { Normal, Insert, Visual, VisualLine, VisualBlock, } impl Mode { pub fn is_visual(&self) -> bool { match self { Mode::Normal | Mode::Insert => false, Mode::Visual | Mode::VisualLine | Mode::VisualBlock => true, } } } impl Default for Mode { fn default() -> Self { Self::Normal } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] pub enum Operator { Change, Delete, Yank, Replace, Object { around: bool }, FindForward { before: bool }, FindBackward { after: bool }, } #[derive(Default, Clone)] pub struct EditorState { pub mode: Mode, pub last_mode: Mode, /// pre_count is the number before an operator is specified (3 in 3d2d) pub pre_count: Option, /// post_count is the number after an operator is specified (2 in 3d2d) pub post_count: Option, pub operator_stack: Vec, } #[derive(Default, Clone, Debug)] pub enum RecordedSelection { #[default] None, Visual { rows: u32, cols: u32, }, SingleLine { cols: u32, }, VisualBlock { rows: u32, cols: u32, }, VisualLine { rows: u32, }, } #[derive(Default, Clone)] pub struct WorkspaceState { pub search: SearchState, pub last_find: Option, pub recording: bool, pub stop_recording_after_next_action: bool, pub replaying: bool, pub recorded_count: Option, pub recorded_actions: Vec, pub recorded_selection: RecordedSelection, } #[derive(Debug)] pub enum ReplayableAction { Action(Box), Insertion { text: Arc, utf16_range_to_replace: Option>, }, } impl Clone for ReplayableAction { fn clone(&self) -> Self { match self { Self::Action(action) => Self::Action(action.boxed_clone()), Self::Insertion { text, utf16_range_to_replace, } => Self::Insertion { text: text.clone(), utf16_range_to_replace: utf16_range_to_replace.clone(), }, } } } #[derive(Clone)] pub struct SearchState { pub direction: Direction, pub count: usize, pub initial_query: String, } impl Default for SearchState { fn default() -> Self { Self { direction: Direction::Next, count: 1, initial_query: "".to_string(), } } } impl EditorState { pub fn cursor_shape(&self) -> CursorShape { match self.mode { Mode::Normal => { if self.operator_stack.is_empty() { CursorShape::Block } else { CursorShape::Underscore } } Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block, Mode::Insert => CursorShape::Bar, } } pub fn vim_controlled(&self) -> bool { !matches!(self.mode, Mode::Insert) || matches!( self.operator_stack.last(), Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) ) } pub fn should_autoindent(&self) -> bool { !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock) } pub fn clip_at_line_ends(&self) -> bool { match self.mode { Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => false, Mode::Normal => true, } } pub fn active_operator(&self) -> Option { self.operator_stack.last().copied() } pub fn keymap_context_layer(&self) -> KeyContext { let mut context = KeyContext::default(); context.add("VimEnabled"); context.set( "vim_mode", match self.mode { Mode::Normal => "normal", Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual", Mode::Insert => "insert", }, ); if self.vim_controlled() { context.add("VimControl"); } if self.active_operator().is_none() && self.pre_count.is_some() || self.active_operator().is_some() && self.post_count.is_some() { context.add("VimCount"); } let active_operator = self.active_operator(); if let Some(active_operator) = active_operator { for context_flag in active_operator.context_flags().into_iter() { context.add(*context_flag); } } context.set( "vim_operator", active_operator.map(|op| op.id()).unwrap_or_else(|| "none"), ); context } } impl Operator { pub fn id(&self) -> &'static str { match self { Operator::Object { around: false } => "i", Operator::Object { around: true } => "a", Operator::Change => "c", Operator::Delete => "d", Operator::Yank => "y", Operator::Replace => "r", Operator::FindForward { before: false } => "f", Operator::FindForward { before: true } => "t", Operator::FindBackward { after: false } => "F", Operator::FindBackward { after: true } => "T", } } pub fn context_flags(&self) -> &'static [&'static str] { match self { Operator::Object { .. } => &["VimObject"], Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => { &["VimWaiting"] } _ => &[], } } }