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