edtui_papier/
actions.rs

1//! Editor actions such as move, insert, delete
2pub mod cpaste;
3pub mod delete;
4pub mod insert;
5pub mod motion;
6pub mod search;
7pub mod select;
8
9use enum_dispatch::enum_dispatch;
10use serde::{Deserialize, Serialize};
11
12pub use self::{
13    cpaste::{CopySelection, Paste},
14    delete::{DeleteChar, DeleteLine, DeleteSelection, RemoveChar},
15    insert::{AppendNewline, InsertChar, InsertNewline, LineBreak},
16    motion::MoveWordForwardEnd,
17    motion::{
18        MoveBackward, MoveDown, MoveForward, MoveToEnd, MoveToFirst, MoveToStart, MoveUp, MoveWordBackward,
19        MoveWordForwardStart,
20    },
21    search::{
22        AppendCharToSearch, FindNext, FindPrevious, RemoveCharFromSearch, StartSearch, StopSearch, TriggerSearch,
23    },
24    select::SelectBetween,
25};
26
27use crate::{helper::clamp_column, state::selection::Selection, EditorMode, EditorState};
28
29#[enum_dispatch(Execute, Clone, Serialize, Deserialize)]
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(tag = "action", content = "payload")]
32pub enum Action<I: Clone + Execute> {
33    SwitchMode(SwitchMode),
34    Append(Append),
35    MoveForward(MoveForward),
36    MoveBackward(MoveBackward),
37    MoveUp(MoveUp),
38    MoveDown(MoveDown),
39    MoveWordForwardStart(MoveWordForwardStart),
40    MoveWordFowardEnd(MoveWordForwardEnd),
41    MoveWordBackward(MoveWordBackward),
42    MoveToStart(MoveToStart),
43    MoveToFirst(MoveToFirst),
44    MoveToEnd(MoveToEnd),
45    InsertChar(InsertChar),
46    LineBreak(LineBreak),
47    AppendNewline(AppendNewline),
48    InsertNewline(InsertNewline),
49    RemoveChar(RemoveChar),
50    DeleteChar(DeleteChar),
51    DeleteLine(DeleteLine),
52    DeleteSelection(DeleteSelection),
53    SelectBetween(SelectBetween),
54    Undo(Undo),
55    Redo(Redo),
56    Paste(Paste),
57    CopySelection(CopySelection),
58    Composed(Composed<I>),
59    StartSearch(StartSearch),
60    StopSearch(StopSearch),
61    TriggerSearch(TriggerSearch),
62    FindNext(FindNext),
63    FindPrevious(FindPrevious),
64    AppendCharToSearch(AppendCharToSearch),
65    RemoveCharFromSearch(RemoveCharFromSearch),
66    Custom(Custom<I>),
67}
68
69#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
70pub struct Custom<I: Clone + Execute>(pub I);
71
72impl<I> Execute for Custom<I>
73where
74    I: Clone + Execute,
75{
76    fn execute(&mut self, state: &mut EditorState) {
77        self.0.execute(state);
78    }
79}
80
81#[enum_dispatch]
82pub trait Execute {
83    fn execute(&mut self, state: &mut EditorState);
84}
85
86#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
87pub struct SwitchMode(pub EditorMode);
88
89impl Execute for SwitchMode {
90    fn execute(&mut self, state: &mut EditorState) {
91        clamp_column(state);
92        match self.0 {
93            EditorMode::Normal => {
94                state.selection = None;
95            },
96            EditorMode::Visual => {
97                state.selection = Some(Selection::new(state.cursor, state.cursor));
98            },
99            EditorMode::Insert | EditorMode::Search | EditorMode::Command => {},
100        }
101        state.mode = self.0;
102    }
103}
104
105/// Switch to insert mode and move one character forward
106#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
107pub struct Append;
108
109impl Execute for Append {
110    fn execute(&mut self, state: &mut EditorState) {
111        SwitchMode(EditorMode::Insert).execute(state);
112        MoveForward(1).execute(state);
113    }
114}
115
116#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
117pub struct Undo;
118
119impl Execute for Undo {
120    fn execute(&mut self, state: &mut EditorState) {
121        state.undo();
122    }
123}
124
125#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
126pub struct Redo;
127
128impl Execute for Redo {
129    fn execute(&mut self, state: &mut EditorState) {
130        state.redo();
131    }
132}
133
134/// Executes multiple actions one after the other.
135#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
136pub struct Composed<I: Clone + Execute>(Vec<Action<I>>);
137
138impl<I> Composed<I>
139where
140    I: Clone + Execute + Serialize,
141{
142    #[must_use]
143    pub fn new<A: Into<Action<I>>>(action: A) -> Self {
144        Self(vec![action.into()])
145    }
146
147    #[must_use]
148    pub fn chain<A: Into<Action<I>>>(mut self, action: A) -> Self {
149        self.0.push(action.into());
150        self
151    }
152}
153
154impl<I> Execute for Composed<I>
155where
156    I: Clone + Execute,
157{
158    fn execute(&mut self, state: &mut EditorState) {
159        for action in &mut self.0 {
160            action.execute(state);
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::{clipboard::InternalClipboard, Index2, Lines};
169    enum TestAction {
170        Test,
171    }
172    fn test_state() -> EditorState {
173        let mut state = EditorState::new(Lines::from("Hello World!\n\n123."), "txt");
174        state.set_clipboard(InternalClipboard::default());
175        state
176    }
177
178    #[test]
179    fn test_switch_mode() {
180        let mut state = test_state();
181        assert_eq!(state.mode, EditorMode::Normal);
182
183        SwitchMode(EditorMode::Insert).execute(&mut state);
184        assert_eq!(state.mode, EditorMode::Insert);
185
186        SwitchMode(EditorMode::Visual).execute(&mut state);
187        assert_eq!(state.mode, EditorMode::Visual);
188    }
189
190    #[test]
191    fn test_append() {
192        let mut state = test_state();
193
194        Append.execute(&mut state);
195        assert_eq!(state.mode, EditorMode::Insert);
196        assert_eq!(state.cursor, Index2::new(0, 1));
197
198        state.mode = EditorMode::Normal;
199        state.cursor = Index2::new(0, 11);
200        Append.execute(&mut state);
201        assert_eq!(state.mode, EditorMode::Insert);
202        assert_eq!(state.cursor, Index2::new(0, 12));
203    }
204}