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