state.rs

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