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