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