1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum EditorMode {
42 Terminal,
44 Headless,
46}
47
48#[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 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 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 #[inline]
127 pub fn active_buffer_id(&self) -> usize {
128 self.layout.active_buffer().id
129 }
130
131 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 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 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 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()); }),
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 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 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}