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