ad_editor/editor/
mod.rs

1//! The main control flow and functionality of the `ad` editor.
2use crate::{
3    buffer::{ActionOutcome, Buffer},
4    config::Config,
5    die,
6    dot::TextObject,
7    exec::{Addr, Address},
8    fsys::{AdFs, InputFilter, LogEvent, Message, Req},
9    input::Event,
10    key::{Arrow, Input},
11    mode::{modes, Mode},
12    plumb::PlumbingRules,
13    set_config,
14    system::{DefaultSystem, System},
15    term::CurShape,
16    ui::{Layout, StateChange, Ui, UserInterface},
17    LogBuffer,
18};
19use ad_event::Source;
20use std::{
21    env, panic,
22    path::PathBuf,
23    sync::mpsc::{channel, Receiver, Sender},
24    time::Instant,
25};
26use tracing::{debug, trace, warn};
27
28mod actions;
29mod built_in_commands;
30mod commands;
31mod minibuffer;
32mod mouse;
33
34pub(crate) use actions::{Action, Actions, ViewPort};
35pub(crate) use built_in_commands::built_in_commands;
36pub(crate) use minibuffer::{MiniBufferSelection, MiniBufferState};
37pub(crate) use mouse::Click;
38
39/// The mode that the [Editor] will run in following a call to [Editor::run].
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum EditorMode {
42    /// Run as a TUI
43    Terminal,
44    /// Run without a user interface
45    Headless,
46}
47
48/// The main editor state.
49#[derive(Debug)]
50pub struct Editor<S>
51where
52    S: System,
53{
54    system: S,
55    ui: Ui,
56    cwd: PathBuf,
57    running: bool,
58    modes: Vec<Mode>,
59    pending_keys: Vec<Input>,
60    layout: Layout,
61    tx_events: Sender<Event>,
62    rx_events: Receiver<Event>,
63    tx_fsys: Sender<LogEvent>,
64    rx_fsys: Option<Receiver<LogEvent>>,
65    log_buffer: LogBuffer,
66    plumbing_rules: PlumbingRules,
67    held_click: Option<Click>,
68    last_click_was_left: bool,
69    last_click_time: Instant,
70}
71
72impl Editor<DefaultSystem> {
73    /// Construct a new [Editor] with the provided config.
74    pub fn new(
75        cfg: Config,
76        plumbing_rules: PlumbingRules,
77        mode: EditorMode,
78        log_buffer: LogBuffer,
79    ) -> Self {
80        Self::new_with_system(cfg, plumbing_rules, mode, log_buffer, DefaultSystem)
81    }
82}
83
84impl<S> Editor<S>
85where
86    S: System,
87{
88    /// Construct a new [Editor] with the provided config and System.
89    pub fn new_with_system(
90        cfg: Config,
91        plumbing_rules: PlumbingRules,
92        mode: EditorMode,
93        log_buffer: LogBuffer,
94        system: S,
95    ) -> Self {
96        let cwd = match env::current_dir() {
97            Ok(cwd) => cwd,
98            Err(e) => die!("Unable to determine working directory: {e}"),
99        };
100        let (tx_events, rx_events) = channel();
101        let (tx_fsys, rx_fsys) = channel();
102
103        set_config(cfg);
104
105        Self {
106            system,
107            ui: mode.into(),
108            cwd,
109            running: true,
110            modes: modes(),
111            pending_keys: Vec::new(),
112            layout: Layout::new(0, 0),
113            tx_events,
114            rx_events,
115            tx_fsys,
116            rx_fsys: Some(rx_fsys),
117            log_buffer,
118            plumbing_rules,
119            held_click: None,
120            last_click_was_left: false,
121            last_click_time: Instant::now(),
122        }
123    }
124
125    /// The id of the currently active buffer
126    #[inline]
127    pub fn active_buffer_id(&self) -> usize {
128        self.layout.active_buffer().id
129    }
130
131    /// Update the stored window size, accounting for the status and message bars
132    /// This will panic if the available screen rows are 0 or 1
133    pub(crate) fn update_window_size(&mut self, screen_rows: usize, screen_cols: usize) {
134        trace!("window size updated: rows={screen_rows} cols={screen_cols}");
135        self.layout.update_screen_size(screen_rows - 2, screen_cols);
136    }
137
138    /// Ensure that opening without any files initialises the fsys state correctly
139    fn ensure_correct_fsys_state(&self) {
140        if self.layout.is_empty_scratch() {
141            _ = self.tx_fsys.send(LogEvent::Open(0));
142            _ = self.tx_fsys.send(LogEvent::Focus(0));
143        }
144    }
145
146    /// Initialise any UI state required for our [EditorMode] and run the main event loop.
147    pub fn run(mut self) {
148        let rx_fsys = self.rx_fsys.take().expect("to have fsys channels");
149        AdFs::new(self.tx_events.clone(), rx_fsys).run_threaded();
150        self.ensure_correct_fsys_state();
151        self.run_event_loop();
152    }
153
154    #[inline]
155    fn handle_event(&mut self, event: Event) {
156        match event {
157            Event::Input(i) => self.handle_input(i),
158            Event::Action(a) => self.handle_action(a, Source::Fsys),
159            Event::Message(msg) => self.handle_message(msg),
160            Event::WinsizeChanged { rows, cols } => self.update_window_size(rows, cols),
161        }
162    }
163
164    pub(super) fn refresh_screen_w_minibuffer(&mut self, mb: Option<MiniBufferState<'_>>) {
165        self.layout.clamp_scroll();
166        self.ui.refresh(
167            &self.modes[0].name,
168            &self.layout,
169            &self.pending_keys,
170            self.held_click.as_ref(),
171            mb,
172        );
173    }
174
175    fn run_event_loop(mut self) {
176        let tx = self.tx_events.clone();
177        let (screen_rows, screen_cols) = self.ui.init(tx);
178        self.update_window_size(screen_rows, screen_cols);
179        self.ui.set_cursor_shape(self.current_cursor_shape());
180
181        while self.running {
182            self.refresh_screen_w_minibuffer(None);
183
184            match self.rx_events.recv() {
185                Ok(next_event) => self.handle_event(next_event),
186                _ => break,
187            }
188        }
189
190        self.ui.shutdown();
191    }
192
193    /// Update the status line to contain the given message.
194    pub fn set_status_message(&mut self, msg: &str) {
195        self.ui.state_change(StateChange::StatusMessage {
196            msg: msg.to_string(),
197        });
198    }
199
200    pub(crate) fn current_cursor_shape(&self) -> CurShape {
201        self.modes[0].cur_shape
202    }
203
204    pub(crate) fn block_for_input(&mut self) -> Input {
205        loop {
206            match self.rx_events.recv().unwrap() {
207                Event::Input(k) => return k,
208                Event::Action(a) => self.handle_action(a, Source::Fsys),
209                Event::Message(msg) => self.handle_message(msg),
210                Event::WinsizeChanged { rows, cols } => self.update_window_size(rows, cols),
211            }
212        }
213    }
214
215    fn send_buffer_resp(
216        &self,
217        id: usize,
218        tx: Sender<Result<String, String>>,
219        f: fn(&Buffer) -> String,
220    ) {
221        match self.layout.buffer_with_id(id) {
222            Some(b) => _ = tx.send(Ok((f)(b))),
223            None => {
224                _ = tx.send(Err("unknown buffer".to_string()));
225                _ = self.tx_fsys.send(LogEvent::Close(id));
226            }
227        }
228    }
229
230    fn handle_buffer_mutation<F: FnOnce(&mut Buffer, String)>(
231        &mut self,
232        id: usize,
233        tx: Sender<Result<String, String>>,
234        s: String,
235        f: F,
236    ) {
237        match self.layout.buffer_with_id_mut(id) {
238            Some(b) => {
239                (f)(b, s);
240                _ = tx.send(Ok("handled".to_string()))
241            }
242
243            None => {
244                _ = tx.send(Err("unknown buffer".to_string()));
245                _ = self.tx_fsys.send(LogEvent::Close(id));
246            }
247        }
248    }
249
250    fn handle_message(&mut self, Message { req, tx }: Message) {
251        use Req::*;
252
253        debug!("received fys message: {req:?}");
254        let default_handled = || _ = tx.send(Ok("handled".to_string()));
255
256        match req {
257            ControlMessage { msg } => {
258                self.execute_command(&msg);
259                default_handled();
260            }
261
262            MinibufferSelect { prompt, lines, tx } => {
263                self.fsys_minibuffer(prompt, lines, tx);
264                default_handled();
265            }
266
267            ReadBufferName { id } => self.send_buffer_resp(id, tx, |b| b.full_name().to_string()),
268            ReadBufferAddr { id } => self.send_buffer_resp(id, tx, |b| b.addr()),
269            ReadBufferDot { id } => self.send_buffer_resp(id, tx, |b| b.dot_contents()),
270            ReadBufferXAddr { id } => self.send_buffer_resp(id, tx, |b| b.xaddr()),
271            ReadBufferXDot { id } => self.send_buffer_resp(id, tx, |b| b.xdot_contents()),
272            ReadBufferBody { id } => self.send_buffer_resp(id, tx, |b| b.str_contents()),
273
274            SetBufferAddr { id, s } => self.handle_buffer_mutation(id, tx, s, |b, s| {
275                if let Ok(mut expr) = Addr::parse(&mut s.trim_end().chars().peekable()) {
276                    b.dot = b.map_addr(&mut expr);
277                };
278            }),
279            SetBufferDot { id, s } => self.handle_buffer_mutation(id, tx, s, |b, s| {
280                b.handle_action(Action::InsertString { s }, Source::Fsys);
281            }),
282            SetBufferXAddr { id, s } => self.handle_buffer_mutation(id, tx, s, |b, s| {
283                if let Ok(mut expr) = Addr::parse(&mut s.trim_end().chars().peekable()) {
284                    b.xdot = b.map_addr(&mut expr);
285                };
286            }),
287            SetBufferXDot { id, s } => self.handle_buffer_mutation(id, tx, s, |b, s| {
288                let dot = b.dot;
289                b.dot = b.xdot;
290                b.handle_action(Action::InsertString { s }, Source::Fsys);
291                (b.xdot, b.dot) = (b.dot, dot);
292                b.dot.clamp_idx(b.txt.len_chars()); // xdot clamped as part of handling the insert
293            }),
294
295            ClearBufferBody { id } => self.handle_buffer_mutation(id, tx, String::new(), |b, _| {
296                b.handle_action(Action::DotSet(TextObject::BufferStart, 1), Source::Fsys);
297                b.handle_action(
298                    Action::DotExtendForward(TextObject::BufferEnd, 1),
299                    Source::Fsys,
300                );
301                b.handle_action(Action::Delete, Source::Fsys);
302                b.xdot.clamp_idx(b.txt.len_chars());
303            }),
304
305            AppendBufferBody { id, s } => self.handle_buffer_mutation(id, tx, s, |b, s| {
306                b.append(s, Source::Fsys);
307            }),
308
309            AppendOutput { id, s } => {
310                self.layout.write_output_for_buffer(id, s, &self.cwd);
311                default_handled();
312            }
313
314            AddInputEventFilter { id, filter } => {
315                let resp = if self.try_set_input_filter(id, filter) {
316                    Ok("handled".to_string())
317                } else {
318                    Err("filter already in place".to_string())
319                };
320                _ = tx.send(resp);
321            }
322
323            RemoveInputEventFilter { id } => {
324                self.clear_input_filter(id);
325                default_handled();
326            }
327
328            LoadInBuffer { id, txt } => {
329                self.load_string_in_buffer(id, txt, false);
330                default_handled();
331            }
332
333            ExecuteInBuffer { id, txt } => {
334                self.execute_explicit_string(id, txt, Source::Fsys);
335                default_handled();
336            }
337        }
338    }
339
340    fn handle_input(&mut self, input: Input) {
341        self.pending_keys.push(input);
342
343        if let Some(actions) = self.modes[0].handle_keys(&mut self.pending_keys) {
344            self.handle_actions(actions, Source::Keyboard);
345        }
346    }
347
348    fn handle_actions(&mut self, actions: Actions, source: Source) {
349        match actions {
350            Actions::Single(action) => self.handle_action(action, source),
351            Actions::Multi(actions) => {
352                for action in actions.into_iter() {
353                    self.handle_action(action, source);
354                    if !self.running {
355                        break;
356                    };
357                }
358            }
359        }
360    }
361
362    fn handle_action(&mut self, action: Action, source: Source) {
363        use Action::*;
364
365        match action {
366            AppendToOutputBuffer { bufid, content } => self
367                .layout
368                .write_output_for_buffer(bufid, content, &self.cwd),
369            ChangeDirectory { path } => self.change_directory(path),
370            CommandMode => self.command_mode(),
371            DeleteBuffer { force } => self.delete_buffer(self.active_buffer_id(), force),
372            DeleteColumn { force } => self.delete_active_column(force),
373            DeleteWindow { force } => self.delete_active_window(force),
374            DragWindow {
375                direction: Arrow::Up,
376            } => self.layout.drag_up(),
377            DragWindow {
378                direction: Arrow::Down,
379            } => self.layout.drag_down(),
380            DragWindow {
381                direction: Arrow::Left,
382            } => self.layout.drag_left(),
383            DragWindow {
384                direction: Arrow::Right,
385            } => self.layout.drag_right(),
386            EditCommand { cmd } => self.execute_edit_command(&cmd),
387            ExecuteDot => self.default_execute_dot(None, source),
388            ExecuteString { s } => self.execute_explicit_string(self.active_buffer_id(), s, source),
389            Exit { force } => self.exit(force),
390            ExpandDot => self.expand_current_dot(),
391            FindFile { new_window } => self.find_file(new_window),
392            FindRepoFile { new_window } => self.find_repo_file(new_window),
393            FocusBuffer { id } => self.focus_buffer(id),
394            JumpListForward => self.jump_forward(),
395            JumpListBack => self.jump_backward(),
396            LoadDot { new_window } => self.default_load_dot(source, new_window),
397            MarkClean { bufid } => self.mark_clean(bufid),
398            NewEditLogTransaction => self.layout.active_buffer_mut().new_edit_log_transaction(),
399            NewColumn => self.layout.new_column(),
400            NewWindow => self.layout.new_window(),
401            NextBuffer => {
402                let id = self.layout.focus_next_buffer();
403                _ = self.tx_fsys.send(LogEvent::Focus(id));
404            }
405            NextColumn => {
406                self.layout.next_column();
407                let id = self.active_buffer_id();
408                _ = self.tx_fsys.send(LogEvent::Focus(id));
409            }
410            NextWindowInColumn => {
411                self.layout.next_window_in_column();
412                let id = self.active_buffer_id();
413                _ = self.tx_fsys.send(LogEvent::Focus(id));
414            }
415            OpenFile { path } => self.open_file_relative_to_cwd(&path, false),
416            OpenFileInNewWindow { path } => self.open_file_relative_to_cwd(&path, true),
417            Paste => self.paste_from_clipboard(source),
418            PreviousBuffer => {
419                let id = self.layout.focus_previous_buffer();
420                _ = self.tx_fsys.send(LogEvent::Focus(id));
421            }
422            PreviousColumn => {
423                self.layout.prev_column();
424                let id = self.active_buffer_id();
425                _ = self.tx_fsys.send(LogEvent::Focus(id));
426            }
427            PreviousWindowInColumn => {
428                self.layout.prev_window_in_column();
429                let id = self.active_buffer_id();
430                _ = self.tx_fsys.send(LogEvent::Focus(id));
431            }
432            ReloadActiveBuffer => self.reload_active_buffer(),
433            ReloadBuffer { id } => self.reload_buffer(id),
434            ReloadConfig => self.reload_config(),
435            RunMode => self.run_mode(),
436            SamMode => self.sam_mode(),
437            SaveBufferAs { path, force } => self.save_current_buffer(Some(path), force),
438            SaveBuffer { force } => self.save_current_buffer(None, force),
439            SearchInCurrentBuffer => self.search_in_current_buffer(),
440            SelectBuffer => self.select_buffer(),
441            SetMode { m } => self.set_mode(m),
442            SetStatusMessage { message } => self.set_status_message(&message),
443            SetViewPort(vp) => self.layout.set_viewport(vp),
444            ShellPipe { cmd } => self.pipe_dot_through_shell_cmd(&cmd),
445            ShellReplace { cmd } => self.replace_dot_with_shell_cmd(&cmd),
446            ShellRun { cmd } => self.run_shell_cmd(&cmd),
447            ShowHelp => self.show_help(),
448            UpdateConfig { input } => self.update_config(&input),
449            ViewLogs => self.view_logs(),
450            Yank => self.set_clipboard(self.layout.active_buffer().dot_contents()),
451
452            DebugBufferContents => self.debug_buffer_contents(),
453            DebugEditLog => self.debug_edit_log(),
454
455            RawInput { i } if i == Input::PageUp || i == Input::PageDown => {
456                let arr = if i == Input::PageUp {
457                    Arrow::Up
458                } else {
459                    Arrow::Down
460                };
461
462                self.forward_action_to_active_buffer(
463                    DotSet(TextObject::Arr(arr), self.layout.active_window_rows()),
464                    Source::Keyboard,
465                );
466            }
467            RawInput {
468                i: Input::Mouse(evt),
469            } => self.handle_mouse_event(evt),
470
471            a => self.forward_action_to_active_buffer(a, source),
472        }
473    }
474
475    fn jump_forward(&mut self) {
476        if let Some(id) = self.layout.jump_forward() {
477            _ = self.tx_fsys.send(LogEvent::Focus(id));
478        }
479    }
480
481    fn jump_backward(&mut self) {
482        if let Some(id) = self.layout.jump_backward() {
483            _ = self.tx_fsys.send(LogEvent::Focus(id));
484        }
485    }
486
487    fn forward_action_to_active_buffer(&mut self, a: Action, source: Source) {
488        if let Some(o) = self.layout.active_buffer_mut().handle_action(a, source) {
489            match o {
490                ActionOutcome::SetStatusMessage(msg) => self.set_status_message(&msg),
491                ActionOutcome::SetClipboard(s) => self.set_clipboard(s),
492            }
493        }
494    }
495
496    /// Returns `true` if the filter was successfully set, false if there was already one in place.
497    pub(crate) fn try_set_input_filter(&mut self, bufid: usize, filter: InputFilter) -> bool {
498        let b = match self.layout.buffer_with_id_mut(bufid) {
499            Some(b) => b,
500            None => return false,
501        };
502
503        if b.input_filter.is_some() {
504            warn!("attempt to set an input filter when one is already in place. id={bufid:?}");
505            return false;
506        }
507
508        b.input_filter = Some(filter);
509
510        true
511    }
512
513    /// Remove the input filter for the given scope if one exists.
514    pub(crate) fn clear_input_filter(&mut self, bufid: usize) {
515        if let Some(b) = self.layout.buffer_with_id_mut(bufid) {
516            b.input_filter = None;
517        }
518    }
519}