Skip to main content

floem/views/editor/
actions.rs

1use std::ops::Range;
2
3use floem_editor_core::{
4    command::{EditCommand, MotionModeCommand, MultiSelectionCommand, ScrollCommand},
5    cursor::Cursor,
6    mode::MotionMode,
7    movement::Movement,
8    register::Register,
9};
10use floem_reactive::{SignalGet, SignalUpdate, SignalWith};
11use ui_events::keyboard::Modifiers;
12
13use super::{
14    Editor,
15    command::{Command, CommandExecuted},
16    movement,
17};
18
19pub fn handle_command_default(
20    ed: &Editor,
21    action: &dyn CommonAction,
22    cmd: &Command,
23    count: Option<usize>,
24    modifiers: Modifiers,
25) -> CommandExecuted {
26    match cmd {
27        Command::Edit(cmd) => handle_edit_command_default(ed, action, cmd),
28        Command::Move(cmd) => {
29            let movement = cmd.to_movement(count);
30            handle_move_command_default(ed, action, movement, count, modifiers)
31        }
32        Command::Scroll(cmd) => handle_scroll_command_default(ed, cmd, count, modifiers),
33        Command::MotionMode(cmd) => handle_motion_mode_command_default(ed, action, cmd, count),
34        Command::MultiSelection(cmd) => handle_multi_selection_command_default(ed, cmd),
35    }
36}
37fn handle_edit_command_default(
38    ed: &Editor,
39    action: &dyn CommonAction,
40    cmd: &EditCommand,
41) -> CommandExecuted {
42    let modal = ed.es.with_untracked(|es| es.modal());
43    let smart_tab = ed.es.with_untracked(|es| es.smart_tab());
44    let mut cursor = ed.cursor.get_untracked();
45    let mut register = ed.register.get_untracked();
46
47    let text = ed.rope_text();
48
49    let yank_data = if let floem_editor_core::cursor::CursorMode::Visual { .. } = &cursor.mode {
50        Some(cursor.yank(&text))
51    } else {
52        None
53    };
54
55    // TODO: Should we instead pass the editor so that it can grab
56    // modal + smart-tab (etc) if it wants?
57    // That would end up with some duplication of logic, but it would
58    // be more flexible.
59    let had_edits = action.do_edit(ed, &mut cursor, cmd, modal, &mut register, smart_tab);
60
61    if had_edits && let Some(data) = yank_data {
62        register.add_delete(data);
63    }
64
65    ed.cursor.set(cursor);
66    ed.register.set(register);
67
68    CommandExecuted::Yes
69}
70fn handle_move_command_default(
71    ed: &Editor,
72    action: &dyn CommonAction,
73    movement: Movement,
74    count: Option<usize>,
75    modifiers: Modifiers,
76) -> CommandExecuted {
77    // TODO: should we track jump locations?
78
79    ed.last_movement.set(movement.clone());
80
81    let mut cursor = ed.cursor.get_untracked();
82    let modify = modifiers.shift();
83    ed.register.update(|register| {
84        movement::move_cursor(
85            ed,
86            action,
87            &mut cursor,
88            &movement,
89            count.unwrap_or(1),
90            modify,
91            register,
92        )
93    });
94
95    ed.cursor.set(cursor);
96
97    CommandExecuted::Yes
98}
99
100fn handle_scroll_command_default(
101    ed: &Editor,
102    cmd: &ScrollCommand,
103    count: Option<usize>,
104    mods: Modifiers,
105) -> CommandExecuted {
106    match cmd {
107        ScrollCommand::PageUp => {
108            ed.page_move(false, mods);
109        }
110        ScrollCommand::PageDown => {
111            ed.page_move(true, mods);
112        }
113        ScrollCommand::ScrollUp => ed.scroll(0.0, false, count.unwrap_or(1), mods),
114        ScrollCommand::ScrollDown => {
115            ed.scroll(0.0, true, count.unwrap_or(1), mods);
116        }
117        // TODO:
118        ScrollCommand::CenterOfWindow => {}
119        ScrollCommand::TopOfWindow => {}
120        ScrollCommand::BottomOfWindow => {}
121    }
122
123    CommandExecuted::Yes
124}
125
126fn handle_motion_mode_command_default(
127    ed: &Editor,
128    action: &dyn CommonAction,
129    cmd: &MotionModeCommand,
130    count: Option<usize>,
131) -> CommandExecuted {
132    let count = count.unwrap_or(1);
133    let motion_mode = match cmd {
134        MotionModeCommand::MotionModeDelete => MotionMode::Delete { count },
135        MotionModeCommand::MotionModeIndent => MotionMode::Indent,
136        MotionModeCommand::MotionModeOutdent => MotionMode::Outdent,
137        MotionModeCommand::MotionModeYank => MotionMode::Yank { count },
138    };
139    let mut cursor = ed.cursor.get_untracked();
140    let mut register = ed.register.get_untracked();
141
142    movement::do_motion_mode(ed, action, &mut cursor, motion_mode, &mut register);
143
144    ed.cursor.set(cursor);
145    ed.register.set(register);
146
147    CommandExecuted::Yes
148}
149
150fn handle_multi_selection_command_default(
151    ed: &Editor,
152    cmd: &MultiSelectionCommand,
153) -> CommandExecuted {
154    let mut cursor = ed.cursor.get_untracked();
155    movement::do_multi_selection(ed, &mut cursor, cmd);
156    ed.cursor.set(cursor);
157
158    CommandExecuted::Yes
159}
160
161/// Trait for common actions needed for the default implementation of the
162/// operations.
163pub trait CommonAction {
164    // TODO: should this use Rope's Interval instead of Range?
165    fn exec_motion_mode(
166        &self,
167        ed: &Editor,
168        cursor: &mut Cursor,
169        motion_mode: MotionMode,
170        range: Range<usize>,
171        is_vertical: bool,
172        register: &mut Register,
173    );
174
175    // TODO: should we have a more general cursor state structure?
176    // since modal is about cursor, and register is sortof about cursor
177    // but also there might be other state it wants. Should we just pass Editor to it?
178    /// Perform an edit.
179    ///
180    /// Returns `true` if there was any change.
181    fn do_edit(
182        &self,
183        ed: &Editor,
184        cursor: &mut Cursor,
185        cmd: &EditCommand,
186        modal: bool,
187        register: &mut Register,
188        smart_tab: bool,
189    ) -> bool;
190}