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}