ochrepad_core/action.rs
1//! `Action`: what the user intends, independent of where the carets are
2//! (PLAN 3.5). Key bindings map to actions and macros record them.
3
4use std::fmt;
5
6use serde::{Deserialize, Serialize};
7
8use crate::buffer::{BufferId, Indent};
9use crate::column_edit::ColumnInsert;
10use crate::position::VisualCol;
11use crate::search::SearchQuery;
12use crate::selection::SelectionSet;
13use crate::text_tools::{Codec, InvalidJson};
14use crate::transform::{CaseMode, SortOptions};
15use crate::view::ViewId;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
18pub enum Motion {
19 /// One grapheme cluster.
20 Left,
21 Right,
22 WordLeft,
23 WordRight,
24 /// Smart home: first non-blank of the line, or column 0 if already there.
25 Home,
26 LineStart,
27 LineEnd,
28 /// Home with word wrap: to the start of the caret's display row, or
29 /// `Home` when already there or on a line's first row. Without wrap it
30 /// is `Home`.
31 RowHome,
32 /// End with word wrap: to the end of the caret's display row, or the
33 /// line end when already there. Without wrap it is `LineEnd`.
34 RowEnd,
35 DocStart,
36 DocEnd,
37 /// Vertical moves keep a remembered visual column.
38 Up,
39 Down,
40 /// Page moves by the given number of lines (the view's page height).
41 PageUp(usize),
42 PageDown(usize),
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
46pub enum Direction {
47 Up,
48 Down,
49 Left,
50 Right,
51}
52
53#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
54pub enum Action {
55 InsertText(String),
56 /// Like `InsertText`, but when there are several selections and the text
57 /// has exactly that many lines, each selection gets one line.
58 Paste(String),
59 /// Tab key: a tab character, or spaces to the next tab stop when the
60 /// editor is set to indent with spaces.
61 InsertTab,
62 /// Newline plus the current line's leading whitespace (auto-indent).
63 InsertNewline,
64 DeleteBackward,
65 DeleteForward,
66 DeleteWordBackward,
67 DeleteWordForward,
68 MoveCursor {
69 motion: Motion,
70 extend: bool,
71 },
72 SelectAll,
73 SelectWord,
74 SelectLine,
75 AddCursorAbove,
76 AddCursorBelow,
77 /// Collapse to a caret at the primary selection's head.
78 CollapseSelections,
79 /// Replace the selections (mouse clicks and drags). Positions are clamped
80 /// into the document and onto char boundaries.
81 SetSelections(SelectionSet),
82 /// Rectangular selection in visual columns: one selection per line from
83 /// the anchor corner to the head corner. Lines shorter than the rectangle
84 /// get an empty selection at their end (no virtual space).
85 ColumnSelect {
86 anchor_line: usize,
87 anchor_col: VisualCol,
88 head_line: usize,
89 head_col: VisualCol,
90 },
91 /// Grow or shrink the rectangle by moving its head corner (Alt+Shift+
92 /// arrows); starts a rectangle at the primary caret if there is none.
93 ColumnExtend(Direction),
94 /// Paste a rectangular clip: line `i` goes to the caret's visual column
95 /// on the `i`-th line below it, padding short lines with spaces and
96 /// adding lines at the end of the document as needed. With several
97 /// selections or a non-empty one it behaves like `Paste`.
98 PasteBlock(String),
99 /// Select the next (or previous) match after the primary selection.
100 /// Fails with `NotFound` when there is none (macros stop there).
101 Find {
102 query: SearchQuery,
103 backward: bool,
104 },
105 /// Replace the primary selection if it is exactly a match, then find
106 /// the next match.
107 Replace {
108 query: SearchQuery,
109 replacement: String,
110 },
111 /// Replace every match (inside the selections with "in selection") as
112 /// one undo step.
113 ReplaceAll {
114 query: SearchQuery,
115 replacement: String,
116 },
117 /// One selection per match.
118 SelectAllMatches(SearchQuery),
119 /// Multi-select Next: add the next occurrence of the primary
120 /// selection's text (after it, going round from the end) as a new
121 /// primary selection. With a caret, select the word at it first.
122 MultiSelectNext {
123 match_case: bool,
124 whole_word: bool,
125 },
126 /// Multi-select All: a selection on every occurrence of the primary
127 /// selection's text (or of the word at the caret).
128 MultiSelectAll {
129 match_case: bool,
130 whole_word: bool,
131 },
132 /// Convert each selection (or the caret's line) to a case.
133 ConvertCase(CaseMode),
134 /// Sort the selected lines, the rectangle's lines keyed by the text in
135 /// the rectangle, or the whole document when nothing is selected.
136 SortLines(SortOptions),
137 /// Put the lines Sort Lines would sort in a random order (Randomize
138 /// Line Order); the seed decides the order, so a macro replays it.
139 ShuffleLines {
140 seed: u64,
141 },
142 /// Remove trailing spaces and tabs from the selected lines (all lines
143 /// when nothing is selected).
144 TrimTrailingWhitespace,
145 /// Duplicate the caret's line, or each selection.
146 DuplicateLine,
147 MoveLinesUp,
148 MoveLinesDown,
149 /// Join the selected lines (or the caret's line and the next).
150 JoinLines,
151 /// Split each selected line (or the caret's line) wider than `width`
152 /// columns at the blanks between words, as Notepad++'s Split Lines;
153 /// the new lines start with the line's indentation.
154 SplitLines {
155 width: usize,
156 },
157 /// Remove empty lines from the selected lines (all lines when nothing
158 /// is selected); with `include_whitespace`, also lines of only spaces
159 /// and tabs.
160 RemoveEmptyLines {
161 include_whitespace: bool,
162 },
163 /// Remove lines equal to an earlier selected line (all lines when
164 /// nothing is selected), keeping the order; with `consecutive_only`,
165 /// only lines equal to the line right before them.
166 RemoveDuplicateLines {
167 consecutive_only: bool,
168 },
169 /// Delete the lines the selections touch (the caret's line), with their
170 /// line breaks: Notepad++'s Line Delete (Ctrl+Shift+L).
171 DeleteLine,
172 /// Swap each caret's line with the line above it (Ctrl+T); the caret
173 /// goes to the start of its line, as in Notepad++.
174 TransposeLines,
175 /// Insert a line above each caret's line (a selection's first line),
176 /// indented like it, and move the caret there (Ctrl+Alt+Enter).
177 InsertLineAbove,
178 /// Insert a line below each caret's line (a selection's last line),
179 /// indented like it, and move the caret there (Ctrl+Alt+Shift+Enter).
180 InsertLineBelow,
181 /// Reverse the order of the selected lines (every line when nothing is
182 /// selected).
183 ReverseLines,
184 /// Remove leading spaces and tabs from the selected lines (all lines
185 /// when nothing is selected).
186 TrimLeadingWhitespace,
187 /// Remove leading and trailing spaces and tabs from the selected lines
188 /// (all lines when nothing is selected).
189 TrimLeadingAndTrailingWhitespace,
190 /// Replace the line breaks between the selected lines (all lines) with
191 /// spaces, keeping the blanks around them.
192 EolToSpace,
193 /// Replace every tab on the selected lines (all lines) with spaces to
194 /// the same tab stop.
195 TabsToSpaces,
196 /// Replace spaces with tabs on the selected lines (all lines) wherever
197 /// the columns stay the same: the indentation, and runs of two or more
198 /// spaces reaching a tab stop.
199 SpacesToTabs,
200 /// The Column Editor: insert text or a number sequence at one column
201 /// on many lines (see [`ColumnInsert`]).
202 ColumnInsert(ColumnInsert),
203 /// Fold the region holding the caret: the caret's line if it starts a
204 /// region, else the innermost region around it. Fold actions change the
205 /// view only (no undo step) and are not recorded in macros.
206 Fold,
207 /// Unfold the caret's line, or every fold inside the region around it.
208 Unfold,
209 /// Unfold the caret's line if it is folded, else `Fold`.
210 ToggleFold,
211 FoldAll,
212 UnfoldAll,
213 /// Fold every region of this level: the nesting depth for indentation
214 /// (1 is outermost), the heading level in Markdown.
215 FoldLevel(usize),
216 UnfoldLevel(usize),
217 /// Move the caret to a line and visual column, both counted from 1 as
218 /// in the status bar (column 1 is the line start).
219 GoToLine {
220 line: usize,
221 column: usize,
222 },
223 /// Indent the selected lines, or the caret's line, by one level (Tab
224 /// does this only when a selection spans lines).
225 Indent,
226 /// Shift+Tab: remove one level of indentation from the selected lines,
227 /// or the caret's line. (Tab with a selection over several lines
228 /// indents them.)
229 Unindent,
230 /// Comment the selected lines (or the caret's line) with the language's
231 /// line comment, or uncomment them if they all are. Languages with only
232 /// block comments get a block comment around the lines.
233 ToggleLineComment,
234 /// Comment the selected lines (or the caret's line) even if they
235 /// already are, as Notepad++'s Single Line Comment. Languages with only
236 /// block comments get a block comment around the lines unless they are
237 /// in one.
238 CommentLines,
239 /// Remove one line comment from each selected line that has one (or
240 /// the block comment around the lines), as Notepad++'s Single Line
241 /// Uncomment. Other lines are left alone.
242 UncommentLines,
243 /// Wrap each selection (or the caret's line) in a block comment, or
244 /// unwrap it.
245 ToggleBlockComment,
246 /// Rewrite every line's leading whitespace as spaces or as tabs, keeping
247 /// its width, and indent with that from now on.
248 ConvertIndentation {
249 to_spaces: bool,
250 },
251 /// Move the caret to the bracket matching the one beside it.
252 GoToMatchingBracket,
253 /// Select from the bracket beside the caret to its match, both included.
254 SelectToMatchingBracket,
255 /// Pretty-print the JSON in each selection, or the whole document when
256 /// nothing is selected: one member or element per line, indented with
257 /// `indent` (the document's indentation when `None`). Key order,
258 /// numbers and strings are kept exactly. Fails on invalid JSON.
259 FormatJson {
260 #[serde(default)]
261 indent: Option<Indent>,
262 },
263 /// Remove the whitespace between the JSON tokens of each selection (or
264 /// the whole document).
265 MinifyJson,
266 /// Encode each selection, or the whole document when nothing is
267 /// selected, as UTF-8 with the document's line endings.
268 Encode(Codec),
269 /// Decode each selection (or the whole document). Fails when it is not
270 /// valid or the bytes are not UTF-8 text.
271 Decode(Codec),
272 /// Bookmark each caret's line, or remove the bookmarks if every one of
273 /// those lines has one (Ctrl+F2). Bookmarks belong to the document;
274 /// changing them is not an undo step.
275 ToggleBookmark,
276 /// Move the caret to the start of the next bookmarked line, wrapping
277 /// around to the first (F2).
278 NextBookmark,
279 /// Move the caret to the start of the previous bookmarked line,
280 /// wrapping around to the last (Shift+F2).
281 PrevBookmark,
282 /// Remove every bookmark.
283 ClearBookmarks,
284 /// Bookmark every line without a bookmark, and remove the others.
285 InverseBookmarks,
286 /// Bookmark every line holding the start of a match (in the selections
287 /// with "in selection"), keeping the bookmarks already there.
288 BookmarkMatches(SearchQuery),
289 /// Delete the bookmarked lines with their line breaks.
290 RemoveBookmarkedLines,
291 /// Delete every line without a bookmark.
292 RemoveUnbookmarkedLines,
293 /// Replace the text of each bookmarked line (not its line break) with
294 /// this text: Paste to (Replace) Bookmarked Lines.
295 ReplaceBookmarkedLines(String),
296 Undo,
297 Redo,
298}
299
300#[derive(Clone, Debug, PartialEq, Eq)]
301pub enum ActionError {
302 UnknownView(ViewId),
303 UnknownBuffer(BufferId),
304 InvalidPattern(String),
305 NotFound,
306 /// The buffer is read-only.
307 ReadOnly,
308 /// The language has no comment markers ochrepad knows.
309 NoCommentSyntax,
310 /// The JSON is not valid (boxed to keep errors small).
311 InvalidJson(Box<InvalidJson>),
312 /// The text cannot be decoded; the message says why.
313 Decode(String),
314 /// A bookmark command found no bookmarks.
315 NoBookmarks,
316}
317
318impl fmt::Display for ActionError {
319 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
320 match self {
321 ActionError::UnknownView(v) => write!(f, "no view with id {}", v.0),
322 ActionError::UnknownBuffer(b) => write!(f, "no buffer with id {}", b.0),
323 ActionError::InvalidPattern(e) => write!(f, "invalid search pattern: {e}"),
324 ActionError::NotFound => f.write_str("no match found"),
325 ActionError::ReadOnly => f.write_str("the document is read-only"),
326 ActionError::NoCommentSyntax => {
327 f.write_str("no comment markers are known for this language")
328 }
329 ActionError::InvalidJson(e) => write!(
330 f,
331 "invalid JSON on line {}, column {}: {}",
332 e.line, e.column, e.message
333 ),
334 ActionError::Decode(message) => f.write_str(message),
335 ActionError::NoBookmarks => f.write_str("there are no bookmarks"),
336 }
337 }
338}
339
340impl std::error::Error for ActionError {}