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