state.rs

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