Skip to main content

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 {}