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