1use std::{ops::Range, sync::Arc};
2
3use gpui::{keymap_matcher::KeymapContext, Action};
4use language::CursorShape;
5use serde::{Deserialize, Serialize};
6use workspace::searchable::Direction;
7
8use crate::motion::Motion;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
11pub enum Mode {
12 Normal,
13 Insert,
14 Visual,
15 VisualLine,
16 VisualBlock,
17}
18
19impl Mode {
20 pub fn is_visual(&self) -> bool {
21 match self {
22 Mode::Normal | Mode::Insert => false,
23 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => true,
24 }
25 }
26}
27
28impl Default for Mode {
29 fn default() -> Self {
30 Self::Normal
31 }
32}
33
34#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
35pub enum Operator {
36 Number(usize),
37 Change,
38 Delete,
39 Yank,
40 Replace,
41 Object { around: bool },
42 FindForward { before: bool },
43 FindBackward { after: bool },
44}
45
46#[derive(Default, Clone)]
47pub struct EditorState {
48 pub mode: Mode,
49 pub last_mode: Mode,
50 pub operator_stack: Vec<Operator>,
51}
52
53#[derive(Default, Clone, Debug)]
54pub enum RecordedSelection {
55 #[default]
56 None,
57 Visual {
58 rows: u32,
59 cols: u32,
60 },
61 SingleLine {
62 cols: u32,
63 },
64 VisualBlock {
65 rows: u32,
66 cols: u32,
67 },
68 VisualLine {
69 rows: u32,
70 },
71}
72
73#[derive(Default, Clone)]
74pub struct WorkspaceState {
75 pub search: SearchState,
76 pub last_find: Option<Motion>,
77
78 pub recording: bool,
79 pub stop_recording_after_next_action: bool,
80 pub replaying: bool,
81 pub recorded_count: Option<usize>,
82 pub recorded_actions: Vec<ReplayableAction>,
83 pub recorded_selection: RecordedSelection,
84}
85
86#[derive(Debug)]
87pub enum ReplayableAction {
88 Action(Box<dyn Action>),
89 Insertion {
90 text: Arc<str>,
91 utf16_range_to_replace: Option<Range<isize>>,
92 },
93}
94
95impl Clone for ReplayableAction {
96 fn clone(&self) -> Self {
97 match self {
98 Self::Action(action) => Self::Action(action.boxed_clone()),
99 Self::Insertion {
100 text,
101 utf16_range_to_replace,
102 } => Self::Insertion {
103 text: text.clone(),
104 utf16_range_to_replace: utf16_range_to_replace.clone(),
105 },
106 }
107 }
108}
109
110#[derive(Clone)]
111pub struct SearchState {
112 pub direction: Direction,
113 pub count: usize,
114 pub initial_query: String,
115}
116
117impl Default for SearchState {
118 fn default() -> Self {
119 Self {
120 direction: Direction::Next,
121 count: 1,
122 initial_query: "".to_string(),
123 }
124 }
125}
126
127impl EditorState {
128 pub fn cursor_shape(&self) -> CursorShape {
129 match self.mode {
130 Mode::Normal => {
131 if self.operator_stack.is_empty() {
132 CursorShape::Block
133 } else {
134 CursorShape::Underscore
135 }
136 }
137 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
138 Mode::Insert => CursorShape::Bar,
139 }
140 }
141
142 pub fn vim_controlled(&self) -> bool {
143 !matches!(self.mode, Mode::Insert)
144 || matches!(
145 self.operator_stack.last(),
146 Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. })
147 )
148 }
149
150 pub fn should_autoindent(&self) -> bool {
151 !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
152 }
153
154 pub fn clip_at_line_ends(&self) -> bool {
155 match self.mode {
156 Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => false,
157 Mode::Normal => true,
158 }
159 }
160
161 pub fn keymap_context_layer(&self) -> KeymapContext {
162 let mut context = KeymapContext::default();
163 context.add_identifier("VimEnabled");
164 context.add_key(
165 "vim_mode",
166 match self.mode {
167 Mode::Normal => "normal",
168 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
169 Mode::Insert => "insert",
170 },
171 );
172
173 if self.vim_controlled() {
174 context.add_identifier("VimControl");
175 }
176
177 let active_operator = self.operator_stack.last();
178
179 if let Some(active_operator) = active_operator {
180 for context_flag in active_operator.context_flags().into_iter() {
181 context.add_identifier(*context_flag);
182 }
183 }
184
185 context.add_key(
186 "vim_operator",
187 active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
188 );
189
190 context
191 }
192}
193
194impl Operator {
195 pub fn id(&self) -> &'static str {
196 match self {
197 Operator::Number(_) => "n",
198 Operator::Object { around: false } => "i",
199 Operator::Object { around: true } => "a",
200 Operator::Change => "c",
201 Operator::Delete => "d",
202 Operator::Yank => "y",
203 Operator::Replace => "r",
204 Operator::FindForward { before: false } => "f",
205 Operator::FindForward { before: true } => "t",
206 Operator::FindBackward { after: false } => "F",
207 Operator::FindBackward { after: true } => "T",
208 }
209 }
210
211 pub fn context_flags(&self) -> &'static [&'static str] {
212 match self {
213 Operator::Object { .. } => &["VimObject"],
214 Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => {
215 &["VimWaiting"]
216 }
217 _ => &[],
218 }
219 }
220}