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