1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
//! `Action`: what the user intends, independent of where the carets are
//! (PLAN 3.5). Key bindings map to actions and macros record them.
use std::fmt;
use serde::{Deserialize, Serialize};
use crate::buffer::BufferId;
use crate::position::VisualCol;
use crate::search::SearchQuery;
use crate::selection::SelectionSet;
use crate::transform::{CaseMode, SortOptions};
use crate::view::ViewId;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Motion {
/// One grapheme cluster.
Left,
Right,
WordLeft,
WordRight,
/// Smart home: first non-blank of the line, or column 0 if already there.
Home,
LineStart,
LineEnd,
/// Home with word wrap: to the start of the caret's display row, or
/// `Home` when already there or on a line's first row. Without wrap it
/// is `Home`.
RowHome,
/// End with word wrap: to the end of the caret's display row, or the
/// line end when already there. Without wrap it is `LineEnd`.
RowEnd,
DocStart,
DocEnd,
/// Vertical moves keep a remembered visual column.
Up,
Down,
/// Page moves by the given number of lines (the view's page height).
PageUp(usize),
PageDown(usize),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Direction {
Up,
Down,
Left,
Right,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Action {
InsertText(String),
/// Like `InsertText`, but when there are several selections and the text
/// has exactly that many lines, each selection gets one line.
Paste(String),
/// Tab key: a tab character, or spaces to the next tab stop when the
/// editor is set to indent with spaces.
InsertTab,
/// Newline plus the current line's leading whitespace (auto-indent).
InsertNewline,
DeleteBackward,
DeleteForward,
DeleteWordBackward,
DeleteWordForward,
MoveCursor {
motion: Motion,
extend: bool,
},
SelectAll,
SelectWord,
SelectLine,
AddCursorAbove,
AddCursorBelow,
/// Collapse to a caret at the primary selection's head.
CollapseSelections,
/// Replace the selections (mouse clicks and drags). Positions are clamped
/// into the document and onto char boundaries.
SetSelections(SelectionSet),
/// Rectangular selection in visual columns: one selection per line from
/// the anchor corner to the head corner. Lines shorter than the rectangle
/// get an empty selection at their end (no virtual space).
ColumnSelect {
anchor_line: usize,
anchor_col: VisualCol,
head_line: usize,
head_col: VisualCol,
},
/// Grow or shrink the rectangle by moving its head corner (Alt+Shift+
/// arrows); starts a rectangle at the primary caret if there is none.
ColumnExtend(Direction),
/// Paste a rectangular clip: line `i` goes to the caret's visual column
/// on the `i`-th line below it, padding short lines with spaces and
/// adding lines at the end of the document as needed. With several
/// selections or a non-empty one it behaves like `Paste`.
PasteBlock(String),
/// Select the next (or previous) match after the primary selection.
/// Fails with `NotFound` when there is none (macros stop there).
Find {
query: SearchQuery,
backward: bool,
},
/// Replace the primary selection if it is exactly a match, then find
/// the next match.
Replace {
query: SearchQuery,
replacement: String,
},
/// Replace every match (inside the selections with "in selection") as
/// one undo step.
ReplaceAll {
query: SearchQuery,
replacement: String,
},
/// One selection per match.
SelectAllMatches(SearchQuery),
/// Convert each selection (or the caret's line) to a case.
ConvertCase(CaseMode),
/// Sort the selected lines, the rectangle's lines keyed by the text in
/// the rectangle, or the whole document when nothing is selected.
SortLines(SortOptions),
/// Remove trailing spaces and tabs from the selected lines (all lines
/// when nothing is selected).
TrimTrailingWhitespace,
/// Duplicate the caret's line, or each selection.
DuplicateLine,
MoveLinesUp,
MoveLinesDown,
/// Join the selected lines (or the caret's line and the next).
JoinLines,
/// Remove empty lines from the selected lines (all lines when nothing
/// is selected); with `include_whitespace`, also lines of only spaces
/// and tabs.
RemoveEmptyLines {
include_whitespace: bool,
},
/// Remove lines equal to an earlier selected line (all lines when
/// nothing is selected), keeping the order; with `consecutive_only`,
/// only lines equal to the line right before them.
RemoveDuplicateLines {
consecutive_only: bool,
},
/// Fold the region holding the caret: the caret's line if it starts a
/// region, else the innermost region around it. Fold actions change the
/// view only (no undo step) and are not recorded in macros.
Fold,
/// Unfold the caret's line, or every fold inside the region around it.
Unfold,
/// Unfold the caret's line if it is folded, else `Fold`.
ToggleFold,
FoldAll,
UnfoldAll,
/// Fold every region of this level: the nesting depth for indentation
/// (1 is outermost), the heading level in Markdown.
FoldLevel(usize),
UnfoldLevel(usize),
Undo,
Redo,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ActionError {
UnknownView(ViewId),
UnknownBuffer(BufferId),
InvalidPattern(String),
NotFound,
/// The buffer is read-only.
ReadOnly,
}
impl fmt::Display for ActionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ActionError::UnknownView(v) => write!(f, "no view with id {}", v.0),
ActionError::UnknownBuffer(b) => write!(f, "no buffer with id {}", b.0),
ActionError::InvalidPattern(e) => write!(f, "invalid search pattern: {e}"),
ActionError::NotFound => f.write_str("no match found"),
ActionError::ReadOnly => f.write_str("the document is read-only"),
}
}
}
impl std::error::Error for ActionError {}