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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum EditorMode {
46 Terminal,
48 Headless,
50}
51
52#[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 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 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 #[inline]
141 pub fn active_buffer_id(&self) -> usize {
142 self.layout.active_buffer().id
143 }
144
145 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 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 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 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()); }),
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 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 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}