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