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