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