state.rs

  1use editor::display_map::Clip;
  2use gpui::keymap_matcher::KeymapContext;
  3use language::CursorShape;
  4use serde::{Deserialize, Serialize};
  5use workspace::searchable::Direction;
  6
  7use crate::motion::Motion;
  8
  9#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
 10pub enum Mode {
 11    Normal,
 12    Insert,
 13    Visual { line: bool },
 14}
 15
 16impl Mode {
 17    pub fn is_visual(&self) -> bool {
 18        match self {
 19            Mode::Normal | Mode::Insert => false,
 20            Mode::Visual { .. } => true,
 21        }
 22    }
 23}
 24
 25impl Default for Mode {
 26    fn default() -> Self {
 27        Self::Normal
 28    }
 29}
 30
 31#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
 32pub enum Operator {
 33    Number(usize),
 34    Change,
 35    Delete,
 36    Yank,
 37    Replace,
 38    Object { around: bool },
 39    FindForward { before: bool },
 40    FindBackward { after: bool },
 41}
 42
 43#[derive(Default)]
 44pub struct VimState {
 45    pub mode: Mode,
 46    pub operator_stack: Vec<Operator>,
 47    pub search: SearchState,
 48
 49    pub last_find: Option<Motion>,
 50}
 51
 52pub struct SearchState {
 53    pub direction: Direction,
 54    pub count: usize,
 55    pub initial_query: String,
 56}
 57
 58impl Default for SearchState {
 59    fn default() -> Self {
 60        Self {
 61            direction: Direction::Next,
 62            count: 1,
 63            initial_query: "".to_string(),
 64        }
 65    }
 66}
 67
 68impl VimState {
 69    pub fn cursor_shape(&self) -> CursorShape {
 70        match self.mode {
 71            Mode::Normal => {
 72                if self.operator_stack.is_empty() {
 73                    CursorShape::Block
 74                } else {
 75                    CursorShape::Underscore
 76                }
 77            }
 78            Mode::Visual { .. } => CursorShape::Block,
 79            Mode::Insert => CursorShape::Bar,
 80        }
 81    }
 82
 83    pub fn vim_controlled(&self) -> bool {
 84        !matches!(self.mode, Mode::Insert)
 85            || matches!(
 86                self.operator_stack.last(),
 87                Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. })
 88            )
 89    }
 90
 91    pub fn default_clip(&self) -> Clip {
 92        match self.mode {
 93            Mode::Insert | Mode::Visual { .. } => Clip::None,
 94            Mode::Normal => Clip::EndOfLine,
 95        }
 96    }
 97
 98    pub fn keymap_context_layer(&self) -> KeymapContext {
 99        let mut context = KeymapContext::default();
100        context.add_identifier("VimEnabled");
101        context.add_key(
102            "vim_mode",
103            match self.mode {
104                Mode::Normal => "normal",
105                Mode::Visual { .. } => "visual",
106                Mode::Insert => "insert",
107            },
108        );
109
110        if self.vim_controlled() {
111            context.add_identifier("VimControl");
112        }
113
114        let active_operator = self.operator_stack.last();
115
116        if let Some(active_operator) = active_operator {
117            for context_flag in active_operator.context_flags().into_iter() {
118                context.add_identifier(*context_flag);
119            }
120        }
121
122        context.add_key(
123            "vim_operator",
124            active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
125        );
126
127        context
128    }
129}
130
131impl Operator {
132    pub fn id(&self) -> &'static str {
133        match self {
134            Operator::Number(_) => "n",
135            Operator::Object { around: false } => "i",
136            Operator::Object { around: true } => "a",
137            Operator::Change => "c",
138            Operator::Delete => "d",
139            Operator::Yank => "y",
140            Operator::Replace => "r",
141            Operator::FindForward { before: false } => "f",
142            Operator::FindForward { before: true } => "t",
143            Operator::FindBackward { after: false } => "F",
144            Operator::FindBackward { after: true } => "T",
145        }
146    }
147
148    pub fn context_flags(&self) -> &'static [&'static str] {
149        match self {
150            Operator::Object { .. } => &["VimObject"],
151            Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => {
152                &["VimWaiting"]
153            }
154            _ => &[],
155        }
156    }
157}