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