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_identifier("VimEnabled");
 95        context.add_key(
 96            "vim_mode",
 97            match self.mode {
 98                Mode::Normal => "normal",
 99                Mode::Visual { .. } => "visual",
100                Mode::Insert => "insert",
101            },
102        );
103
104        if self.vim_controlled() {
105            context.add_identifier("VimControl");
106        }
107
108        let active_operator = self.operator_stack.last();
109
110        if let Some(active_operator) = active_operator {
111            for context_flag in active_operator.context_flags().into_iter() {
112                context.add_identifier(*context_flag);
113            }
114        }
115
116        context.add_key(
117            "vim_operator",
118            active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
119        );
120
121        context
122    }
123}
124
125impl Operator {
126    pub fn id(&self) -> &'static str {
127        match self {
128            Operator::Number(_) => "n",
129            Operator::Namespace(Namespace::G) => "g",
130            Operator::Namespace(Namespace::Z) => "z",
131            Operator::Object { around: false } => "i",
132            Operator::Object { around: true } => "a",
133            Operator::Change => "c",
134            Operator::Delete => "d",
135            Operator::Yank => "y",
136            Operator::Replace => "r",
137            Operator::FindForward { before: false } => "f",
138            Operator::FindForward { before: true } => "t",
139            Operator::FindBackward { after: false } => "F",
140            Operator::FindBackward { after: true } => "T",
141        }
142    }
143
144    pub fn context_flags(&self) -> &'static [&'static str] {
145        match self {
146            Operator::Object { .. } => &["VimObject"],
147            Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => {
148                &["VimWaiting"]
149            }
150            _ => &[],
151        }
152    }
153}