1use gpui::keymap::Context;
2use language::CursorShape;
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
6pub enum Mode {
7 Normal,
8 Insert,
9 Visual { line: bool },
10}
11
12impl Default for Mode {
13 fn default() -> Self {
14 Self::Normal
15 }
16}
17
18#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
19pub enum Namespace {
20 G,
21}
22
23#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
24pub enum Operator {
25 Number(usize),
26 Namespace(Namespace),
27 Change,
28 Delete,
29 Yank,
30 Object { around: bool },
31}
32
33#[derive(Default)]
34pub struct VimState {
35 pub mode: Mode,
36 pub operator_stack: Vec<Operator>,
37}
38
39impl VimState {
40 pub fn cursor_shape(&self) -> CursorShape {
41 match self.mode {
42 Mode::Normal => {
43 if self.operator_stack.is_empty() {
44 CursorShape::Block
45 } else {
46 CursorShape::Underscore
47 }
48 }
49 Mode::Visual { .. } => CursorShape::Block,
50 Mode::Insert => CursorShape::Bar,
51 }
52 }
53
54 pub fn vim_controlled(&self) -> bool {
55 !matches!(self.mode, Mode::Insert)
56 }
57
58 pub fn clip_at_line_end(&self) -> bool {
59 !matches!(self.mode, Mode::Insert | Mode::Visual { .. })
60 }
61
62 pub fn empty_selections_only(&self) -> bool {
63 !matches!(self.mode, Mode::Visual { .. })
64 }
65
66 pub fn keymap_context_layer(&self) -> Context {
67 let mut context = Context::default();
68 context.map.insert(
69 "vim_mode".to_string(),
70 match self.mode {
71 Mode::Normal => "normal",
72 Mode::Visual { .. } => "visual",
73 Mode::Insert => "insert",
74 }
75 .to_string(),
76 );
77
78 if self.vim_controlled() {
79 context.set.insert("VimControl".to_string());
80 }
81
82 let active_operator = self.operator_stack.last();
83 if matches!(active_operator, Some(Operator::Object { .. })) {
84 context.set.insert("VimObject".to_string());
85 }
86
87 Operator::set_context(active_operator, &mut context);
88
89 context
90 }
91}
92
93impl Operator {
94 pub fn set_context(operator: Option<&Operator>, context: &mut Context) {
95 let operator_context = match operator {
96 Some(Operator::Number(_)) => "n",
97 Some(Operator::Namespace(Namespace::G)) => "g",
98 Some(Operator::Object { around: false }) => "i",
99 Some(Operator::Object { around: true }) => "a",
100 Some(Operator::Change) => "c",
101 Some(Operator::Delete) => "d",
102 Some(Operator::Yank) => "y",
103
104 None => "none",
105 }
106 .to_owned();
107
108 context
109 .map
110 .insert("vim_operator".to_string(), operator_context);
111 }
112}