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 Replace,
32 Object { around: bool },
33 FindForward { before: bool },
34 FindBackward { after: bool },
35}
36
37#[derive(Default)]
38pub struct VimState {
39 pub mode: Mode,
40 pub operator_stack: Vec<Operator>,
41}
42
43impl VimState {
44 pub fn cursor_shape(&self) -> CursorShape {
45 match self.mode {
46 Mode::Normal => {
47 if self.operator_stack.is_empty() {
48 CursorShape::Block
49 } else {
50 CursorShape::Underscore
51 }
52 }
53 Mode::Visual { .. } => CursorShape::Block,
54 Mode::Insert => CursorShape::Bar,
55 }
56 }
57
58 pub fn vim_controlled(&self) -> bool {
59 !matches!(self.mode, Mode::Insert)
60 || matches!(
61 self.operator_stack.last(),
62 Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. })
63 )
64 }
65
66 pub fn clip_at_line_end(&self) -> bool {
67 !matches!(self.mode, Mode::Insert | Mode::Visual { .. })
68 }
69
70 pub fn empty_selections_only(&self) -> bool {
71 !matches!(self.mode, Mode::Visual { .. })
72 }
73
74 pub fn keymap_context_layer(&self) -> KeymapContext {
75 let mut context = KeymapContext::default();
76 context.add_key(
77 "vim_mode",
78 match self.mode {
79 Mode::Normal => "normal",
80 Mode::Visual { .. } => "visual",
81 Mode::Insert => "insert",
82 },
83 );
84
85 if self.vim_controlled() {
86 context.add_identifier("VimControl");
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.add_identifier(*context_flag);
94 }
95 }
96
97 context.add_key(
98 "vim_operator",
99 active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
100 );
101
102 context
103 }
104}
105
106impl Operator {
107 pub fn id(&self) -> &'static str {
108 match self {
109 Operator::Number(_) => "n",
110 Operator::Namespace(Namespace::G) => "g",
111 Operator::Namespace(Namespace::Z) => "z",
112 Operator::Object { around: false } => "i",
113 Operator::Object { around: true } => "a",
114 Operator::Change => "c",
115 Operator::Delete => "d",
116 Operator::Yank => "y",
117 Operator::Replace => "r",
118 Operator::FindForward { before: false } => "f",
119 Operator::FindForward { before: true } => "t",
120 Operator::FindBackward { after: false } => "F",
121 Operator::FindBackward { after: true } => "T",
122 }
123 }
124
125 pub fn context_flags(&self) -> &'static [&'static str] {
126 match self {
127 Operator::Object { .. } => &["VimObject"],
128 Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => {
129 &["VimWaiting"]
130 }
131 _ => &[],
132 }
133 }
134}