1use gpui::keymap::Context;
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}
33
34#[derive(Default)]
35pub struct VimState {
36 pub mode: Mode,
37 pub operator_stack: Vec<Operator>,
38}
39
40impl VimState {
41 pub fn cursor_shape(&self) -> CursorShape {
42 match self.mode {
43 Mode::Normal => {
44 if self.operator_stack.is_empty() {
45 CursorShape::Block
46 } else {
47 CursorShape::Underscore
48 }
49 }
50 Mode::Visual { .. } => CursorShape::Block,
51 Mode::Insert => CursorShape::Bar,
52 }
53 }
54
55 pub fn vim_controlled(&self) -> bool {
56 !matches!(self.mode, Mode::Insert)
57 }
58
59 pub fn clip_at_line_end(&self) -> bool {
60 !matches!(self.mode, Mode::Insert | Mode::Visual { .. })
61 }
62
63 pub fn empty_selections_only(&self) -> bool {
64 !matches!(self.mode, Mode::Visual { .. })
65 }
66
67 pub fn keymap_context_layer(&self) -> Context {
68 let mut context = Context::default();
69 context.map.insert(
70 "vim_mode".to_string(),
71 match self.mode {
72 Mode::Normal => "normal",
73 Mode::Visual { .. } => "visual",
74 Mode::Insert => "insert",
75 }
76 .to_string(),
77 );
78
79 if self.vim_controlled() {
80 context.set.insert("VimControl".to_string());
81 }
82
83 let active_operator = self.operator_stack.last();
84 if matches!(active_operator, Some(Operator::Object { .. })) {
85 context.set.insert("VimObject".to_string());
86 }
87
88 Operator::set_context(active_operator, &mut context);
89
90 context
91 }
92}
93
94impl Operator {
95 pub fn set_context(operator: Option<&Operator>, context: &mut Context) {
96 let operator_context = match operator {
97 Some(Operator::Number(_)) => "n",
98 Some(Operator::Namespace(Namespace::G)) => "g",
99 Some(Operator::Namespace(Namespace::Z)) => "z",
100 Some(Operator::Object { around: false }) => "i",
101 Some(Operator::Object { around: true }) => "a",
102 Some(Operator::Change) => "c",
103 Some(Operator::Delete) => "d",
104 Some(Operator::Yank) => "y",
105
106 None => "none",
107 }
108 .to_owned();
109
110 context
111 .map
112 .insert("vim_operator".to_string(), operator_context);
113 }
114}