1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, JsonSchema)]
9pub enum Mode {
10 #[default]
11 Normal,
12 Insert,
13 Visual,
14 VisualLine,
15 Command,
16}
17
18impl Mode {
19 #[must_use]
20 pub const fn as_str(self) -> &'static str {
21 match self {
22 Self::Normal => "NORMAL",
23 Self::Insert => "INSERT",
24 Self::Visual => "VISUAL",
25 Self::VisualLine => "V-LINE",
26 Self::Command => "COMMAND",
27 }
28 }
29
30 #[must_use]
31 pub const fn is_insertish(self) -> bool {
32 matches!(self, Self::Insert | Self::Command)
33 }
34
35 #[must_use]
36 pub const fn is_visualish(self) -> bool {
37 matches!(self, Self::Visual | Self::VisualLine)
38 }
39
40 #[must_use]
46 pub const fn cursor_shape(self) -> CursorShape {
47 match self {
48 Self::Normal | Self::Command => CursorShape::Block,
50 Self::Insert => CursorShape::Bar,
52 Self::Visual | Self::VisualLine => CursorShape::Underline,
55 }
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, JsonSchema)]
64pub enum CursorShape {
65 #[default]
67 Block,
68 Bar,
70 Underline,
72}
73
74impl CursorShape {
75 #[must_use]
78 pub const fn as_str(self) -> &'static str {
79 match self {
80 Self::Block => "block",
81 Self::Bar => "bar",
82 Self::Underline => "underline",
83 }
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
90pub struct ModeTransition {
91 pub from: Mode,
92 pub to: Mode,
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn default_is_normal() {
101 assert_eq!(Mode::default(), Mode::Normal);
102 }
103
104 #[test]
105 fn classifiers() {
106 assert!(Mode::Insert.is_insertish());
107 assert!(Mode::Command.is_insertish());
108 assert!(!Mode::Normal.is_insertish());
109 assert!(Mode::Visual.is_visualish());
110 assert!(Mode::VisualLine.is_visualish());
111 }
112
113 #[test]
114 fn display_labels() {
115 assert_eq!(Mode::Normal.as_str(), "NORMAL");
116 assert_eq!(Mode::Insert.as_str(), "INSERT");
117 }
118
119 #[test]
120 fn cursor_shape_per_mode() {
121 assert_eq!(Mode::Normal.cursor_shape(), CursorShape::Block);
123 assert_eq!(Mode::Command.cursor_shape(), CursorShape::Block);
124 assert_eq!(Mode::Insert.cursor_shape(), CursorShape::Bar);
125 assert_eq!(Mode::Visual.cursor_shape(), CursorShape::Underline);
126 assert_eq!(Mode::VisualLine.cursor_shape(), CursorShape::Underline);
127 }
128
129 #[test]
130 fn cursor_shape_labels() {
131 assert_eq!(CursorShape::Block.as_str(), "block");
132 assert_eq!(CursorShape::Bar.as_str(), "bar");
133 assert_eq!(CursorShape::Underline.as_str(), "underline");
134 }
135
136 #[test]
137 fn insert_is_the_only_bar_mode() {
138 let modes = [
140 Mode::Normal,
141 Mode::Insert,
142 Mode::Visual,
143 Mode::VisualLine,
144 Mode::Command,
145 ];
146 let bars = modes
147 .into_iter()
148 .filter(|m| m.cursor_shape() == CursorShape::Bar)
149 .count();
150 assert_eq!(bars, 1);
151 }
152}