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