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_key(
95 "vim_mode",
96 match self.mode {
97 Mode::Normal => "normal",
98 Mode::Visual { .. } => "visual",
99 Mode::Insert => "insert",
100 },
101 );
102
103 if self.vim_controlled() {
104 context.add_identifier("VimControl");
105 }
106
107 let active_operator = self.operator_stack.last();
108
109 if let Some(active_operator) = active_operator {
110 for context_flag in active_operator.context_flags().into_iter() {
111 context.add_identifier(*context_flag);
112 }
113 }
114
115 context.add_key(
116 "vim_operator",
117 active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
118 );
119
120 context
121 }
122}
123
124impl Operator {
125 pub fn id(&self) -> &'static str {
126 match self {
127 Operator::Number(_) => "n",
128 Operator::Namespace(Namespace::G) => "g",
129 Operator::Namespace(Namespace::Z) => "z",
130 Operator::Object { around: false } => "i",
131 Operator::Object { around: true } => "a",
132 Operator::Change => "c",
133 Operator::Delete => "d",
134 Operator::Yank => "y",
135 Operator::Replace => "r",
136 Operator::FindForward { before: false } => "f",
137 Operator::FindForward { before: true } => "t",
138 Operator::FindBackward { after: false } => "F",
139 Operator::FindBackward { after: true } => "T",
140 }
141 }
142
143 pub fn context_flags(&self) -> &'static [&'static str] {
144 match self {
145 Operator::Object { .. } => &["VimObject"],
146 Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => {
147 &["VimWaiting"]
148 }
149 _ => &[],
150 }
151 }
152}