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 {
62        if let Some(data) = yank_data {
63            register.add_delete(data);
64        }
65    }
66
67    ed.cursor.set(cursor);
68    ed.register.set(register);
69
70    CommandExecuted::Yes
71}
72fn handle_move_command_default(
73    ed: &Editor,
74    action: &dyn CommonAction,
75    movement: Movement,
76    count: Option<usize>,
77    modifiers: Modifiers,
78) -> CommandExecuted {
79    // TODO: should we track jump locations?
80
81    ed.last_movement.set(movement.clone());
82
83    let mut cursor = ed.cursor.get_untracked();
84    let modify = modifiers.shift();
85    ed.register.update(|register| {
86        movement::move_cursor(
87            ed,
88            action,
89            &mut cursor,
90            &movement,
91            count.unwrap_or(1),
92            modify,
93            register,
94        )
95    });
96
97    ed.cursor.set(cursor);
98
99    CommandExecuted::Yes
100}
101
102fn handle_scroll_command_default(
103    ed: &Editor,
104    cmd: &ScrollCommand,
105    count: Option<usize>,
106    mods: Modifiers,
107) -> CommandExecuted {
108    match cmd {
109        ScrollCommand::PageUp => {
110            ed.page_move(false, mods);
111        }
112        ScrollCommand::PageDown => {
113            ed.page_move(true, mods);
114        }
115        ScrollCommand::ScrollUp => ed.scroll(0.0, false, count.unwrap_or(1), mods),
116        ScrollCommand::ScrollDown => {
117            ed.scroll(0.0, true, count.unwrap_or(1), mods);
118        }
119        // TODO:
120        ScrollCommand::CenterOfWindow => {}
121        ScrollCommand::TopOfWindow => {}
122        ScrollCommand::BottomOfWindow => {}
123    }
124
125    CommandExecuted::Yes
126}
127
128fn handle_motion_mode_command_default(
129    ed: &Editor,
130    action: &dyn CommonAction,
131    cmd: &MotionModeCommand,
132    count: Option<usize>,
133) -> CommandExecuted {
134    let count = count.unwrap_or(1);
135    let motion_mode = match cmd {
136        MotionModeCommand::MotionModeDelete => MotionMode::Delete { count },
137        MotionModeCommand::MotionModeIndent => MotionMode::Indent,
138        MotionModeCommand::MotionModeOutdent => MotionMode::Outdent,
139        MotionModeCommand::MotionModeYank => MotionMode::Yank { count },
140    };
141    let mut cursor = ed.cursor.get_untracked();
142    let mut register = ed.register.get_untracked();
143
144    movement::do_motion_mode(ed, action, &mut cursor, motion_mode, &mut register);
145
146    ed.cursor.set(cursor);
147    ed.register.set(register);
148
149    CommandExecuted::Yes
150}
151
152fn handle_multi_selection_command_default(
153    ed: &Editor,
154    cmd: &MultiSelectionCommand,
155) -> CommandExecuted {
156    let mut cursor = ed.cursor.get_untracked();
157    movement::do_multi_selection(ed, &mut cursor, cmd);
158    ed.cursor.set(cursor);
159
160    CommandExecuted::Yes
161}
162
163/// Trait for common actions needed for the default implementation of the
164/// operations.
165pub trait CommonAction {
166    // TODO: should this use Rope's Interval instead of Range?
167    fn exec_motion_mode(
168        &self,
169        ed: &Editor,
170        cursor: &mut Cursor,
171        motion_mode: MotionMode,
172        range: Range<usize>,
173        is_vertical: bool,
174        register: &mut Register,
175    );
176
177    // TODO: should we have a more general cursor state structure?
178    // since modal is about cursor, and register is sortof about cursor
179    // but also there might be other state it wants. Should we just pass Editor to it?
180    /// Perform an edit.
181    ///
182    /// Returns `true` if there was any change.
183    fn do_edit(
184        &self,
185        ed: &Editor,
186        cursor: &mut Cursor,
187        cmd: &EditCommand,
188        modal: bool,
189        register: &mut Register,
190        smart_tab: bool,
191    ) -> bool;
192}