async_editor/
lib.rs

1//! AsyncEditor
2//! Async Console Editor that supports asyncronous output to a resizeable print window. Perfect for an AI Chat Interface.
3//!
4//! Keyboard Commands:
5//!
6//! - Arrows, PgUp, PgDn => Move
7//! todo - Ctrl-W: Erase the input from the cursor to the previous whitespace
8//! todo - Ctrl-U: Erase the input before the cursor
9//! - Ctrl-L: Clear the screen
10//! - Ctrl-Left / Ctrl-Right: Move to previous/next whitespace
11//! - Home: Jump to the start of the line
12//! - End: Jump to the end of the line
13//! - Ctrl-Up, Ctrl-Down:  Contract / Expand Print vs Edit windows
14//! - Ctrl-C: Ignored
15//!   Ctrl Left/Right => Move Left/Right by Word
16//! - Ctrl PgUp / PgDn - Print History Scrollback, ESC to exit.
17//! in dev - Ctrl-k => Delete current line
18//!   Ctrl-C, Ctrl-D, Ctrl-Q, Ctrl-X => Exit/Quit
19//!
20//! Note: this works, but doctest will fail, so doc test have been disabled in Cargo.toml
21//! ```rust
22//! use async_editor::{EditorEvent, AsyncEditor, Result};
23//! use std::io::Write;
24//! use std::time::Duration;
25//! use tokio::time::sleep;
26//!
27//! #[tokio::main]
28//! async fn main() -> Result<()> {
29//!   let mut st = r###"LInitial String"###;
30//!   let st = st.to_string();
31//!   let (mut async_editor, mut async_editor_stdout) = AsyncEditor::new(&st, "Ctrl-C/D/Q/X to Quit".to_string(), 0.5f32, 4)?;
32//!   let mut count: u32 = 0;
33//!
34//!   loop {
35//! 	tokio::select! {
36//! 	_ = sleep(Duration::from_millis(200)) => {
37//! 		count += 1;
38//! 		//write!(async_editor_stdout, "{}", format!("_Message {}\nreceived\n!", count))?;
39//! 		write!(async_editor_stdout, "{}", format!("_Message {} received!", count))?;
40//! 	}
41//! 	cmd = async_editor.async_editor() => match cmd {
42//! 		Ok(EditorEvent::CtrlC | EditorEvent::CtrlD | EditorEvent::CtrlQ | EditorEvent::CtrlX) => {
43//! 			break;
44//! 		}
45//! 		Ok(EditorEvent::CtrlS) => {
46//! 			writeln!(async_editor_stdout, "\n\nCtrlS\n")?;
47//! 		}
48//! 		Ok(_) => {continue;}
49//! 			Err(e) => {
50//! 				writeln!(async_editor_stdout, "\n\nError: {e:?}\n")?;
51//! 				break;
52//! 			}
53//! 		}
54//! 	}
55//! 	async_editor.flush()?;
56//!   }
57//!   async_editor.flush()?;
58//!
59//!   let text = async_editor.text();
60//!   drop(async_editor);
61//!
62//!   println!("\n\nEdited Text:\n{}",text);
63//!   Ok(())
64//! }
65//!```
66
67// Note: Extended Grapheme Clusters are NOT rust char, and cannot be converted
68//       to / from rust char. This software handles everything as a
69//       Extended Grapheme Cluster.
70//
71
72use crossterm::{
73    QueueableCommand,
74    cursor::{self, position},
75    event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
76    style::Print,
77    terminal::{self, disable_raw_mode},
78};
79use futures_util::{FutureExt, StreamExt, select};
80use grapheme_utils::*;
81use historybuffer::HistoryBuffer;
82use std::{
83    io::{self, Stdout, Write, stdout},
84    ops::DerefMut,
85    rc::Rc,
86    string::String,
87};
88use thingbuf::mpsc::{Receiver, Sender, errors::TrySendError};
89use unicode_segmentation::UnicodeSegmentation;
90
91mod error;
92pub use self::error::{Error, Result};
93
94const HISTORY_BUFFER_SIZE: usize = 300 * 160 * 4;
95
96#[derive(Debug)]
97pub enum EditorEvent {
98    CtrlC,
99    CtrlD,
100    CtrlQ,
101    CtrlN,
102    CtrlS,
103    CtrlX,
104}
105
106pub enum WriteHistoryType {
107    PageUp,
108    PageDown,
109    Quit,
110}
111
112/// AsyncEditor - Multiline Terminal Editor with simultaneous stdout
113///
114/// AsyncEditor is a functional multline editor supporting standard
115/// control keys, while simultaneously supporting stdout printing
116/// on the upper portion of the screen.
117///
118/// Extensible keyboard events are bubbled out for special functions.
119///
120//
121// The main AsyncEditor struct functions as a ReadWriteRouter
122pub struct AsyncEditor {
123    event_stream: EventStream,    // Crossterm Event Stream
124    stdout_rx: Receiver<Vec<u8>>, // Stdout pipe
125    editor: Editor,               // Multiline Editor
126}
127
128impl AsyncEditor {
129    // Create a new `AsyncEditor` instance with an associated
130    // [`SharedStdout`]
131    pub fn new(
132        initial_content: &str,
133        split_prompt: String,
134        print_height: f32,
135        tabstop: u8,
136    ) -> Result<(Self, SharedStdout)> {
137        let (stdout_tx, stdout_rx) = thingbuf::mpsc::channel(500);
138
139        let editor = Editor::new(initial_content, split_prompt, print_height, tabstop)?;
140
141        let mut async_editor = AsyncEditor {
142            event_stream: EventStream::new(),
143            stdout_rx,
144            editor,
145        };
146        async_editor.editor.term.queue(terminal::EnableLineWrap)?;
147        async_editor.editor.term.flush()?;
148        Ok((
149            async_editor,
150            SharedStdout {
151                buf: Vec::new(),
152                stdout_tx: stdout_tx,
153            },
154        ))
155    }
156
157    pub fn flush(&mut self) -> Result<()> {
158        while let Ok(buf) = self.stdout_rx.try_recv_ref() {
159            self.editor.writeout(&buf)?;
160        }
161        self.editor.term.flush()?;
162        Ok(())
163    }
164
165    /// Polling function for async_editor, manages all input and output.
166    /// Returns either an EditorEvent or an Error
167    pub async fn async_editor(&mut self) -> Result<EditorEvent> {
168        loop {
169            select! {
170                event = self.event_stream.next().fuse() => match event {
171                    Some(Ok(event)) => {
172                        match self.editor.handle_event(event) {
173                            Ok(Some(event)) => {
174                                self.editor.term.flush()?;
175                                return Result::<_>::Ok(event) // Try return Ok(event);
176                            },
177                            Err(e) => return Err(e),
178                            Ok(None) => self.editor.term.flush()?,
179                        }
180                    }
181                    Some(Err(e)) => return Err(e.into()),
182                    None => {},
183                },
184                result = self.stdout_rx.recv_ref().fuse() => match result {
185                    Some(buf) => {
186                        self.editor.writeout(&buf)?;
187                        self.editor.term.flush()?;
188                    },
189                    None => return Err(Error::SharedStdoutClosed),
190                },
191            }
192        }
193    }
194
195    pub fn text(&self) -> String {
196        self.editor.text()
197    }
198}
199
200fn string_to_hex(s: &str, maxlen: usize) -> String {
201    let mut new_hex_string = String::with_capacity(s.as_bytes().len() * 2);
202
203    for byte in s.as_bytes().iter() {
204        let s = format!("{:02x} ", byte);
205        new_hex_string.push_str(&s);
206        if new_hex_string.len() >= maxlen {
207            return new_hex_string[..maxlen].to_string();
208        }
209    }
210    new_hex_string
211}
212
213pub struct Editor {
214    curx: u16, // Grapheme Cursor Position
215    cury: u16,
216    hb_active: bool,
217    hb_start_index: usize,
218    hb_end_index: usize,
219    histbuf: HistoryBuffer, // Make the buffer large enough to hold a huge terminal window screen with LOTS of escape characters
220    lidx: usize,
221    lines: Vec<String>, // Editor text without \n
222    lineidx: usize,     // Which line active
223    lofs: usize,
224    loose_cursor: bool, // Detects when we've moved off a long line.
225    printlines: u16,    // Number of Lines used printing
226    printx: u16,        // print cursor pos
227    printy: u16,
228    scrollstart: usize,
229    sizex: u16, // screen size
230    sizey: u16,
231    split_prompt: String,
232    tabstop: u8,
233    term: Stdout,
234    tmpbuf: Rc<String>,
235}
236
237impl Editor {
238    pub fn new(
239        initial_content: &str,
240        split_prompt: String,
241        print_height: f32,
242        tabstop: u8,
243    ) -> Result<Self> {
244        let term = stdout();
245        let (sizex, sizey) = terminal::size()?;
246        let (_curx, cury) = position()?;
247        let newprintlines = (sizey as f32 * print_height.max(0.1).min(0.9)) as u16;
248        terminal::enable_raw_mode()?;
249
250        Ok(Self {
251            curx: 0,
252            cury: newprintlines + 2,
253            hb_active: false,
254            hb_start_index: 0,
255            hb_end_index: 0,
256            histbuf: HistoryBuffer::new(HISTORY_BUFFER_SIZE), // Make the buffer large enough to hold a huge terminal window screen with LOTS of escape characters
257            lidx: 0, // line index of grapheme at the cursor
258            lines: initial_content.split("\n").map(|s| s.to_string()).collect(), // convert_tabs(s,'→',8).to_string()).collect(),  // Exlusive \n makes a few painful things easier
259            lineidx: 0,
260            lofs: 0, // line index offset to the start of the displayed text
261            loose_cursor: false,
262            printlines: newprintlines,
263            printx: 0,
264            printy: cury + 1,
265            scrollstart: 0,
266            sizex: sizex,
267            sizey: sizey,
268            split_prompt: split_prompt,
269            tabstop: tabstop,
270            term: term,
271            tmpbuf: Rc::new(String::new()), // with_capacity(BUFFER_SIZE)), not needed...  Grows to largest need, then reused
272        })
273    }
274
275    fn ch(&self, idx: usize) -> char {
276        grapheme_at_idx(&self.lines[self.lineidx], idx)
277            .chars()
278            .next()
279            .unwrap_or('\0')
280    }
281
282    fn grapheme_idx_at_idx(&self, idx: usize) -> usize {
283        grapheme_idx_at_idx(&self.lines[self.lineidx], idx)
284    }
285
286    fn grapheme_width_lofs_to_lidx(&self) -> u16 {
287        let st = &self.lines[self.lineidx][self.lofs..self.lidx];
288        if !st.contains('\t') {
289            return string_width(&st) as u16;
290        }
291        let ofs = string_width(&self.lines[self.lineidx][..self.lofs]) % self.tabstop as usize;
292        let mut char_width;
293        let mut width = 0;
294        for (_, g) in st.grapheme_indices(true) {
295            if g == "\t" {
296                let ts = self.tabstop as usize;
297                char_width = ts - ((width + ofs) % ts);
298            } else {
299                char_width = string_width(g);
300            }
301            width += char_width;
302        }
303        return width as u16;
304    }
305
306    pub fn handle_event(&mut self, event: Event) -> Result<Option<EditorEvent>> {
307        match event {
308            // Doesn't work to detect ctrl-shift  <= a *terminal* thing I thinks
309            // Control Keys
310            Event::Key(KeyEvent {
311                code,
312                modifiers: KeyModifiers::CONTROL,
313                kind: KeyEventKind::Press,
314                ..
315            }) => match code {
316                // Move to beginning
317                KeyCode::Char('a') => {
318                    self.lidx = 0;
319                    self.lofs = 0;
320                    self.setpos()?;
321                }
322                // End of text (CTRL-C)
323                KeyCode::Char('c') => {
324                    //if self.should_print_line_on_control_c {
325                    //	self.print(&format!("{}{}", self.prompt, self.editor))?;
326                    //}
327                    return Ok(Some(EditorEvent::CtrlC));
328                }
329                // End of transmission (CTRL-D)
330                KeyCode::Char('d') => {
331                    //writeln!(self.term)?;
332                    return Ok(Some(EditorEvent::CtrlD));
333                }
334                // Move to end
335                KeyCode::Char('e') => {
336                    self.move_end()?;
337                }
338                KeyCode::Char('l') => {
339                    self.printx = 0;
340                    self.printy = 0;
341                    self.redraw()?;
342                }
343                KeyCode::Char('n') => {
344                    return Ok(Some(EditorEvent::CtrlS));
345                }
346                KeyCode::Char('q') => {
347                    return Ok(Some(EditorEvent::CtrlQ));
348                }
349                KeyCode::Char('s') => {
350                    return Ok(Some(EditorEvent::CtrlS));
351                }
352                KeyCode::Char('x') => {
353                    return Ok(Some(EditorEvent::CtrlX));
354                }
355                KeyCode::Char('u') => {
356                    self.lines[self.lineidx].drain(0..self.lidx);
357                    self.redraw()?;
358                }
359                KeyCode::Down => {
360                    self.resize(3)?;
361                }
362                KeyCode::End => {
363                    self.lineidx = self.lines.len().saturating_sub(1);
364                    self.scrollstart = self.lineidx; //.saturating_sub(1);
365                    self.cury = self.printlines + 2; // + (self.lineidx - self.scrollstart) as u16;
366                    self.lidx = self.len();
367                    self.setpos()?;
368                    self.redraw()?;
369                }
370                KeyCode::Home => {
371                    self.lineidx = 0;
372                    self.scrollstart = 0;
373                    self.cury = self.printlines + 2;
374                    self.lidx = 0;
375                    self.setpos()?;
376                    self.redraw()?;
377                }
378                // Ctrl-Left  Move cursor left to previous word
379                KeyCode::Left => {
380                    if self.lidx == 0 {
381                        self.move_up(1, true)?;
382                        self.lidx = self.len();
383                    }
384                    while self.lidx > 0 && self.prev_char(self.lidx).is_whitespace() {
385                        self.lidx = self.prev_grapheme_idx_from_idx(self.lidx);
386                    }
387                    while self.lidx > 0 && !self.prev_char(self.lidx).is_whitespace() {
388                        self.lidx = self.prev_grapheme_idx_from_idx(self.lidx);
389                    }
390                    self.setpos()?;
391                }
392                KeyCode::PageDown => {
393                    // Go forward in history, Quit if you're caught up
394                    // Only pageUp can activate history...  No need to save the index here
395                    self.writehistory(WriteHistoryType::PageDown)?;
396                }
397                KeyCode::PageUp => {
398                    // Go back in history. Activate if necessary
399                    if !self.hb_active {
400                        self.hb_active = true;
401                        self.hb_start_index = self.histbuf.get_last_index();
402                        self.hb_end_index = self.hb_start_index;
403                    } else {
404                        if self.hb_start_index == 0 {
405                            return Ok(None);
406                        }
407                    }
408                    self.writehistory(WriteHistoryType::PageUp)?;
409                }
410                // Ctrl-Right  Move cursor right to next word
411                KeyCode::Right => {
412                    while self.lidx < self.len() && !self.ch(self.lidx).is_whitespace() {
413                        self.lidx = self.next_grapheme_idx_from_idx(self.lidx);
414                    }
415                    if self.lidx == self.len() {
416                        self.move_down(1, true)?;
417                        self.lidx = 0;
418                    }
419                    while self.lidx < self.len() && self.ch(self.lidx).is_whitespace() {
420                        self.lidx = self.next_grapheme_idx_from_idx(self.lidx);
421                    }
422                    self.setpos()?;
423                }
424                KeyCode::Up => {
425                    self.resize(-3)?;
426                }
427                _ => {}
428            },
429            /////////////////////////////////////////////////////////////////////////////
430            // Everything Else
431            Event::Key(KeyEvent {
432                code,
433                modifiers: _,
434                kind: KeyEventKind::Press,
435                ..
436            }) => match code {
437                KeyCode::Backspace => {
438                    if self.lidx == 0 {
439                        if self.lineidx == 0 {
440                            return Ok(None);
441                        }
442                        self.lidx = self.lines[self.lineidx - 1].len();
443                        //self.loose_cursor = true;
444                        let s = self.lines[self.lineidx].clone();
445                        self.lines[self.lineidx - 1].push_str(&s);
446                        self.lines.remove(self.lineidx);
447                        self.move_up(1, false)?;
448                        self.setpos()?;
449                        self.redraw()?;
450                    } else {
451                        if self.lidx > self.len() {
452                            self.lidx = self.len();
453                            self.lofs = 0;
454                        }
455                        let start = self.prev_grapheme_idx_from_idx(self.lidx);
456                        let mut gwid = self.grapheme_width_lofs_to_lidx(); // width with tabs computed the correct width
457                        self.lines[self.lineidx].replace_range(start..self.lidx, "");
458                        self.lidx = start;
459                        gwid = gwid.saturating_sub(self.grapheme_width_lofs_to_lidx());
460                        self.curx = self.curx.saturating_sub(gwid);
461                        self.redrawline()?;
462                    }
463                }
464                KeyCode::Char(c) => {
465                    self.insert_charstr(&c.to_string())?;
466                }
467                KeyCode::Delete => {
468                    if self.lidx == self.len() {
469                        if self.lineidx + 1 < self.lines.len() {
470                            let s = self.lines[self.lineidx + 1].clone();
471                            self.lines[self.lineidx].push_str(&s);
472                            self.lines.remove(self.lineidx + 1);
473                            self.redraw()?;
474                        }
475                    } else {
476                        let end = self.next_grapheme_idx_from_idx(self.lidx);
477                        self.lines[self.lineidx].replace_range(self.lidx..end, "");
478                        self.redrawline()?;
479                    }
480                }
481                KeyCode::Down => {
482                    self.move_down(1, false)?;
483                    self.redraw()?;
484                }
485                KeyCode::End => {
486                    self.move_end()?;
487                }
488                KeyCode::Esc => {
489                    self.writehistory(WriteHistoryType::Quit)?;
490                    self.hb_active = false;
491                }
492                KeyCode::Enter => {
493                    if self.lidx > self.len() {
494                        self.lidx = self.len();
495                    }
496                    self.lines.insert(
497                        self.lineidx + 1,
498                        self.lines[self.lineidx][self.lidx..].to_string(),
499                    );
500                    self.lines[self.lineidx].drain(self.lidx..);
501                    self.move_down(1, true)?;
502                    self.redraw()?;
503                }
504                KeyCode::Home => {
505                    self.lidx = 0;
506                    self.lofs = 0;
507                    self.setpos()?;
508                }
509                KeyCode::Left => {
510                    if self.lidx == 0 {
511                        if self.lineidx == 0 {
512                            return Ok(None);
513                        }
514                        self.move_up(1, true)?;
515                        self.lidx = self.len();
516                    } else {
517                        self.lidx = self.prev_grapheme_idx_from_idx(self.lidx);
518                    }
519                    self.setpos()?;
520                }
521                KeyCode::PageDown => {
522                    let numlines = self.sizey - self.printlines - 2;
523                    self.move_down(numlines, false)?;
524                }
525                KeyCode::PageUp => {
526                    let numlines = self.sizey - self.printlines - 2;
527                    self.move_up(numlines, false)?;
528                }
529                KeyCode::Right => {
530                    if self.lidx >= self.len() {
531                        if self.lineidx + 1 == self.lines.len() {
532                            return Ok(None);
533                        }
534                        self.move_down(1, true)?;
535                        self.lidx = 0;
536                    } else {
537                        self.lidx = self.next_grapheme_idx_from_idx(self.lidx);
538                    }
539                    self.setpos()?;
540                }
541                KeyCode::Tab => {
542                    self.insert_charstr("\t")?;
543                }
544                KeyCode::Up => {
545                    self.move_up(1, false)?;
546                }
547                _ => {}
548            },
549            Event::Resize(x, y) => {
550                let curp: f32 = (self.printlines as f32 / self.sizey as f32)
551                    .max(0.9)
552                    .min(0.1);
553                let delta: i16 = (curp * y as f32) as i16 - self.printlines as i16;
554
555                self.sizex = x;
556                self.sizey = y;
557                self.resize(delta)?;
558
559                self.sizey = y;
560            }
561            _ => {}
562        }
563        if false {
564            // Debug code
565            self.split_prompt = string_to_hex(&self.lines[self.lineidx], 40);
566            self.redraw()?;
567        }
568        self.term.queue(cursor::MoveTo(self.curx, self.cury))?;
569        self.term.flush()?;
570        Ok(None)
571    }
572
573    fn insert_charstr(&mut self, ch: &str) -> Result<()> {
574        if self.lidx > self.len() {
575            self.lidx = self.len();
576            self.lofs = 0;
577        }
578        //let pre_cnt = self.num_graphemes();
579        self.lines[self.lineidx].insert_str(self.lidx, ch);
580        //if pre_cnt != self.num_graphemes() {
581        self.lidx = self.next_grapheme_idx_from_idx(self.lidx);
582        self.curx = self.grapheme_width_lofs_to_lidx();
583
584        self.redrawline()?;
585        Ok(())
586    }
587
588    fn len(&mut self) -> usize {
589        self.lines[self.lineidx].len()
590    }
591
592    /// Match the curx position as much as possible moving from line to line
593    fn matchpos(&mut self) -> Result<()> {
594        // Future work -
595        // It's nice for the cursor to stay at nearly the same curx as you move
596        // up and down... but it's a feature that can wait.
597        self.setpos()?;
598        Ok(())
599    }
600
601    fn move_down(&mut self, num: u16, move_to_beginning: bool) -> Result<()> {
602        self.loose_cursor = true;
603        if self.lineidx + 1 == self.lines.len() && self.scrollstart + 1 == self.lines.len() as usize
604        {
605            self.lidx = self.len();
606            self.setpos()?;
607            self.redrawline()?;
608            return Ok(());
609        }
610
611        // To support scrolling beyond the bottom of a full screen, the
612        // scrollstart and cury calculations sometimes use a virtual
613        // lineidx - the line number that would be printed at the bottom of the
614        // screen.
615        // The switch occurs when the real bottom line rises above the bottom
616        // of the screen, the virtual bottom line numbers.
617        let virtidx;
618        if self.scrollstart
619            <= (self.sizey as usize + 2).saturating_sub((self.cury + self.printlines) as usize)
620        {
621            virtidx = (self.cury - self.printlines) as usize - 2;
622        } else {
623            virtidx = self.scrollstart + (self.sizey - self.printlines) as usize - 3;
624        }
625
626        self.cury += num;
627        self.lineidx = (self.lineidx + num as usize).min(self.lines.len().saturating_sub(1));
628        if self.cury > self.sizey - 1 || self.lineidx + 1 == self.lines.len() {
629            if num > 10 {
630                // Pagedown - Max scrollstart move
631                self.scrollstart = (self.scrollstart + num as usize).min(self.lineidx);
632            } else {
633                // Linedown - Minimum scrollstart
634                self.scrollstart = (virtidx + num as usize + self.printlines as usize + 3)
635                    .saturating_sub(self.sizey as usize)
636                    .min(self.lineidx);
637            }
638        }
639        self.cury = self
640            .cury
641            .min((self.lineidx - self.scrollstart) as u16 + self.printlines + 2)
642            .min(self.sizey - 1);
643
644        if move_to_beginning {
645            self.lidx = 0;
646            self.lofs = 0;
647            self.curx = 0;
648        } else {
649            self.matchpos()?;
650        }
651        self.redraw()?;
652        Ok(())
653    }
654
655    fn move_end(&mut self) -> Result<()> {
656        self.lidx = self.len();
657        self.setpos()?;
658        self.redrawline()?;
659        Ok(())
660    }
661
662    fn move_up(&mut self, num: u16, move_to_end: bool) -> Result<()> {
663        self.loose_cursor = true;
664
665        if self.lineidx == 0 && self.scrollstart == 0 {
666            self.lofs = 0;
667            self.lidx = 0;
668            self.curx = 0;
669            self.redrawline()?;
670            return Ok(());
671        }
672
673        self.lineidx = self.lineidx.saturating_sub(num as usize);
674        if self.cury == self.printlines + 2 || self.lineidx < self.scrollstart {
675            self.scrollstart = self.scrollstart.saturating_sub(num as usize);
676        }
677        self.cury = (self.lineidx - self.scrollstart) as u16 + self.printlines + 2; // Possible underflow, but...
678        if move_to_end {
679            self.lidx = self.len();
680        }
681        self.matchpos()?;
682        self.redraw()?;
683        Ok(())
684    }
685
686    fn next_grapheme_from_idx(&self, idx: usize) -> &str {
687        next_grapheme_from_idx(&self.lines[self.lineidx], idx)
688    }
689
690    fn next_grapheme_idx_from_idx(&self, idx: usize) -> usize {
691        next_grapheme_idx_from_idx(&self.lines[self.lineidx], idx)
692    }
693
694    fn prev_char(&self, idx: usize) -> char {
695        self.prev_grapheme_from_idx(idx)
696            .chars()
697            .next()
698            .unwrap_or('\0')
699    }
700
701    fn prev_grapheme_from_idx(&self, idx: usize) -> &str {
702        prev_grapheme_from_idx(&self.lines[self.lineidx], idx)
703    }
704
705    fn prev_grapheme_idx_from_idx(&self, idx: usize) -> usize {
706        prev_grapheme_idx_from_idx(&self.lines[self.lineidx], idx)
707    }
708
709    pub fn redraw(&mut self) -> Result<()> {
710        //   Can't run  "let (cx, cy) = position()?;"  for the case when redraw called from writeout:
711
712        let tbuf = Rc::get_mut(&mut self.tmpbuf).ok_or(Error::RedrawRcError)?;
713        //let tbuf = Rc::get_mut(&mut self.tmpbuf).ok_or(Error::Msg("Redraw Rc Error"))?;
714        tbuf.clear();
715        // Just ='s self.buf.extend(std::iter::repeat("=").take(extend).chain(std::iter::once("\n")).collect::<String>().as_bytes());
716        //let s = format!("== {} == {}, c: {} {} cursor: {} {} print: {} {} pline: {} scroll: {}  screen: {} {}  ==", self.split_prompt, s, cx, cy, self.curx, self.cury, self.printx, self.printy, self.printlines, self.scrollstart, self.sizex, self.sizey);
717
718        /* Cursor Debug * /
719        let s = format!(
720                "== {} ==  l: {} o: {} == curx: {} {} print: {} {} pline: {} lineidx: {} sofs: {}  screen: {} {} == Ctrl-D to Quit ",
721                self.split_prompt,
722                self.lidx,
723                self.lofs,
724                self.curx,
725                self.cury,
726                self.printx,
727                self.printy,
728                self.printlines,
729                self.lineidx,
730                self.scrollstart,
731                self.sizex,
732                self.sizey
733            );
734        let s = format!(
735                "=============== {} ==  s: {} e: {} =======  Ctrl-PgUp/Ctrl-PgDn  ===  Ctrl-C/D/Q/X to Quit  ",
736                self.split_prompt,
737                self.hb_start_index,
738                self.hb_end_index);*/
739        let s = format!(
740            "=====  AsyncEditor  ========== {} ==  Ctrl ⬅️  / ⮕ / ⬆️/ / ⬇️  ==  Ctrl-PgUp/Ctrl-PgDn  ",
741            self.split_prompt
742        );
743        let extend_count = (self.sizex as usize).saturating_sub(string_width(&s));
744
745        self.term
746            .queue(cursor::MoveTo(0, self.printlines + 1 as u16))?;
747        self.term
748            .queue(terminal::Clear(terminal::ClearType::FromCursorDown))?;
749        //tbuf.push_str(&format!("{}{}\n", s, std::iter::repeat("=").take(extend_count).collect::<String>()));
750        self.term.queue(Print(&format!(
751            "{}{}\n",
752            s,
753            std::iter::repeat("=")
754                .take(extend_count)
755                .collect::<String>()
756        )))?;
757        self.term.queue(cursor::MoveToColumn(0))?;
758
759        let end_index = (self.scrollstart
760            + (self.sizey.saturating_sub(self.printlines + 2) as usize))
761            .min(self.lines.len());
762        for lidx in self.scrollstart..end_index {
763            // For each line in range
764            if lidx == self.lineidx {
765                self.redrawline()?;
766                if lidx != end_index - 1 {
767                    self.term.queue(cursor::MoveToNextLine(1))?;
768                }
769                continue;
770            }
771
772            let line = &self.lines[lidx];
773            let maxwidth = self.sizex as usize - 1;
774            let stwidth = string_width(line);
775            let mut width = 0usize;
776            let mut char_width;
777            let mut s = String::with_capacity(200);
778            let mut news = String::with_capacity(200);
779
780            match (stwidth > maxwidth, line.contains('\t')) {
781                (false, false) => {
782                    //Printable"
783                    self.term.queue(Print(&line))?;
784                }
785                (_, _) => {
786                    // Too Wide or Tabs or both
787                    s.clear();
788                    for (_, g) in line.grapheme_indices(true) {
789                        news.clear();
790                        if g == "\t" {
791                            let ts = self.tabstop as usize;
792                            char_width = ts - (width % ts);
793                            let tab_arrow_string: String =
794                                std::iter::repeat("→").take(char_width as usize).collect();
795                            news.push_str(&tab_arrow_string);
796                        } else {
797                            char_width = string_width(g);
798                            news.push_str(g);
799                        }
800                        if width + char_width as usize > maxwidth {
801                            break;
802                        }
803                        s.push_str(&news);
804                        width += char_width;
805                    }
806                    self.term.queue(Print(&s))?;
807                    if stwidth > maxwidth {
808                        self.term.queue(cursor::MoveToColumn(self.sizex - 1))?;
809                        self.term.queue(Print(&'>'))?;
810                    }
811                }
812            }
813            if lidx != end_index - 1 {
814                self.term.queue(cursor::MoveToNextLine(1))?;
815            }
816        }
817        self.term.queue(cursor::MoveTo(self.curx, self.cury))?;
818        self.term.flush()?;
819        Ok(())
820    }
821
822    fn redrawline(&mut self) -> Result<()> {
823        self.term
824            .queue(terminal::Clear(terminal::ClearType::CurrentLine))?;
825        self.term.queue(cursor::MoveToColumn(0))?;
826        //if self.lofs > self.len() { self.lofs = 0; }
827        let start = if self.lofs > self.len() {
828            0
829        } else {
830            self.grapheme_idx_at_idx(self.lofs)
831        };
832
833        // Current line may start at lofs
834        let line = &self.lines[self.lineidx][start..];
835        let maxwidth = self.sizex as usize - 2;
836        let stwidth = string_width(line);
837        let mut width = 0usize;
838        let mut char_width;
839        let mut s = String::with_capacity(200);
840        let mut news = String::with_capacity(200);
841
842        match (stwidth > maxwidth, line.contains('\t')) {
843            (false, false) => {
844                //Printable"
845                //tbuf.push_str(line);
846                self.term.queue(Print(&line))?;
847            }
848            (_, _) => {
849                // Too Wide or Tabs or both
850                s.clear();
851                for (_, g) in line.grapheme_indices(true) {
852                    news.clear();
853                    if g == "\t" {
854                        let ts = self.tabstop as usize;
855                        char_width = ts - (width % ts);
856                        let tab_arrow_string: String =
857                            std::iter::repeat("→").take(char_width as usize).collect();
858                        news.push_str(&tab_arrow_string);
859                    } else {
860                        char_width = string_width(g);
861                        news.push_str(g);
862                    }
863                    if width + char_width as usize > maxwidth + 1 {
864                        break;
865                    }
866                    s.push_str(&news);
867                    width += char_width;
868                }
869                //tbuf.push_str(&line[0..end]);
870                self.term.queue(Print(&s))?;
871            }
872        }
873        self.term.queue(cursor::MoveTo(self.curx, self.cury))?;
874        Ok(())
875    }
876
877    fn resize(&mut self, delta: i16) -> Result<()> {
878        self.term
879            .queue(cursor::MoveTo(self.printx, self.printy as u16))?;
880        self.term
881            .queue(terminal::Clear(terminal::ClearType::FromCursorDown))?;
882        let pre = self.printlines as i16;
883        self.printlines = (self.printlines as i16 + delta)
884            .max((self.sizey >> 3) as i16)
885            .min(self.sizey as i16 - 8) as u16;
886        let new_delta = self.printlines as i16 - pre;
887        self.cury = (self.cury as i16 + new_delta).max(self.printlines as i16 + 2) as u16;
888        while self.cury >= self.sizey {
889            self.cury -= 1;
890            self.lineidx -= 1;
891        }
892        self.setpos()?;
893        self.writebuf(&[])?;
894        Ok(())
895    }
896
897    // Set self.curx / self.lofs so self.lidx is visable (string_width[lofs to idx] is < maxsize
898    fn setpos(&mut self) -> Result<()> {
899        let maxwidth = self.sizex - 1;
900        self.lidx = self.grapheme_idx_at_idx(self.lidx);
901
902        // loose_cursor - Signals that the cursor just moved off a possibly long line
903        // We want to stay loose until the user starts a changing action, AND
904        // We want to keep the current alignment if we're on another long line.
905        if self.loose_cursor {
906            // Lean toward resetting lofs, but don't for other long lines
907            if self.lidx < (self.lofs + (self.sizey << 2) as usize + 15) {
908                // really short line detection  - Know changed mode.
909                self.lofs = 0;
910            } else {
911                //self.lofs = grapheme_idx_at_idx(&self.lines[self.lineidx], self.lofs); // Fix when lofs hits in the middle of a char
912                self.lofs = self.grapheme_idx_at_idx(self.lofs); // Fix when lofs hits in the middle of a char
913            }
914        }
915        if self.lidx < self.lofs {
916            // really short line detection  - Know changed mode.
917            self.lofs = 0;
918        }
919        self.loose_cursor = false;
920
921        let mut stwidth = self.grapheme_width_lofs_to_lidx();
922
923        loop {
924            if stwidth <= maxwidth {
925                self.curx = stwidth;
926                return Ok(());
927            }
928            // It would be nice to just subtract the first char width, but for tabs
929            //stwidth -= self.string_width_at_idx(self.lofs);
930            self.lofs = self.next_grapheme_idx_from_idx(self.lofs);
931            stwidth = self.grapheme_width_lofs_to_lidx();
932        }
933    }
934
935    pub fn text(&self) -> String {
936        self.lines.join("\n")
937    }
938
939    fn writebuf(&mut self, buf: &[u8]) -> Result<()> {
940        // The common writing worker
941
942        // The caller is responsible for restoring the cursor to self.printx/y
943        // and clearing the screen below
944
945        for line in buf.split_inclusive(|b| *b == b'\n') {
946            self.term.write_all(line)?;
947            self.term.flush()?;
948            if line.ends_with(b"\n") {
949                self.term.queue(cursor::MoveToColumn(0))?;
950                self.printx = 0;
951                self.printy += 1;
952            } else {
953                let mut x = self.printx as usize + line.len(); // Todo - count graphemes!
954                while x > self.sizex as usize {
955                    x -= self.sizex as usize;
956                    self.printy += 1;
957                }
958                self.printx = x as u16;
959            }
960        }
961
962        self.printy = self.printy.min(self.sizey);
963        if self.printy > self.printlines {
964            self.term
965                .queue(terminal::ScrollUp(self.printy - self.printlines))?;
966            self.printy = self.printlines;
967        }
968
969        self.redraw()?;
970        Ok(())
971    }
972
973    fn writehistory(&mut self, write_history_type: WriteHistoryType) -> Result<()> {
974        self.term.queue(cursor::MoveTo(0, 0))?;
975        self.term.queue(terminal::Clear(terminal::ClearType::All))?;
976
977        self.printx = 0;
978        self.printy = 0;
979
980        //let mut limit = self.printlines * self.sizey - 1;
981        let mut linecnt = self.sizex;
982        let mut num_lines = 0;
983        let mut buf = Vec::<u8>::with_capacity(self.printlines as usize * self.sizex as usize);
984        let mut revbuf: Vec<u8> = vec![];
985
986        match write_history_type {
987            WriteHistoryType::PageDown => {
988                self.hb_start_index = self.hb_end_index;
989                while let Some(ch) = self.histbuf.get(self.hb_start_index + buf.len()) {
990                    buf.push(ch);
991                    linecnt -= 1;
992                    if ch == b'\n' || linecnt == 0 {
993                        linecnt = self.sizex - 1;
994                        num_lines += 1;
995                        if num_lines >= self.printlines {
996                            break;
997                        }
998                    }
999                }
1000                self.hb_end_index = self.hb_start_index + buf.len();
1001                if num_lines < self.printlines {
1002                    // Back at the bottom
1003                    buf = self
1004                        .histbuf
1005                        .get_recent(self.printlines as usize * self.sizex as usize);
1006                    self.writebuf(&buf)?;
1007                    self.hb_active = false;
1008                } else {
1009                    self.writebuf(&buf)?;
1010                }
1011            }
1012            WriteHistoryType::PageUp => {
1013                self.hb_end_index = self.hb_start_index;
1014                while let Some(ch) = self.histbuf.get(self.hb_start_index) {
1015                    buf.push(ch);
1016                    linecnt -= 1;
1017                    if ch == b'\n' || linecnt == 0 {
1018                        linecnt = self.sizex - 1;
1019                        num_lines += 1;
1020                        if num_lines >= self.printlines {
1021                            break;
1022                        }
1023                    }
1024                    if self.hb_start_index == 0 {
1025                        break;
1026                    }
1027                    self.hb_start_index = self.hb_start_index.saturating_sub(1);
1028                }
1029                if self.hb_start_index == 0 {
1030                    // Load a full page
1031                    let mut linecnt = self.sizex;
1032                    let mut num_lines = 0;
1033                    revbuf =
1034                        Vec::<u8>::with_capacity(self.printlines as usize * self.sizex as usize);
1035                    while let Some(ch) = self.histbuf.get(self.hb_start_index + revbuf.len()) {
1036                        revbuf.push(ch);
1037                        linecnt -= 1;
1038                        if ch == b'\n' || linecnt == 0 {
1039                            linecnt = self.sizex - 1;
1040                            num_lines += 1;
1041                            if num_lines >= self.printlines {
1042                                break;
1043                            }
1044                        }
1045                    }
1046                    self.hb_end_index = revbuf.len();
1047                } else {
1048                    revbuf = buf.into_iter().rev().collect();
1049                }
1050                self.writebuf(&revbuf)?;
1051            }
1052            WriteHistoryType::Quit => {
1053                buf = self
1054                    .histbuf
1055                    .get_recent(self.printlines as usize * self.sizex as usize);
1056                self.writebuf(&buf)?;
1057                self.hb_active = false;
1058            }
1059        }
1060        Ok(())
1061    }
1062
1063    fn writeout(&mut self, buf: &[u8]) -> Result<()> {
1064        // Can't request position: let (cx, cy) = position()?; // Causes Timeout:
1065        self.histbuf.add(buf);
1066        if !self.hb_active {
1067            self.term
1068                .queue(cursor::MoveTo(self.printx, self.printy as u16))?;
1069            self.term
1070                .queue(terminal::Clear(terminal::ClearType::FromCursorDown))?;
1071            self.writebuf(buf)?;
1072        }
1073        Ok(())
1074    }
1075}
1076
1077impl Drop for Editor {
1078    fn drop(&mut self) {
1079        let _ = disable_raw_mode();
1080        self.term.queue(cursor::MoveTo(0, self.sizey - 1)).unwrap();
1081        self.term.queue(cursor::MoveToNextLine(1)).unwrap();
1082        self.term.flush().unwrap();
1083    }
1084}
1085
1086#[pin_project::pin_project]
1087pub struct SharedStdout {
1088    #[pin]
1089    buf: Vec<u8>,
1090    stdout_tx: Sender<Vec<u8>>,
1091}
1092
1093impl io::Write for SharedStdout {
1094    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
1095        self.buf.extend_from_slice(buf);
1096        if true {
1097            match self.stdout_tx.try_send_ref() {
1098                Ok(mut send_buf) => {
1099                    std::mem::swap(send_buf.deref_mut(), &mut self.buf);
1100                    self.buf.clear();
1101                }
1102                Err(TrySendError::Full(_)) => return Err(io::ErrorKind::WouldBlock.into()),
1103                _ => {
1104                    return Err(io::Error::new(
1105                        io::ErrorKind::Other,
1106                        "io Error: ThingBuf Receiver Closed",
1107                    ));
1108                }
1109            }
1110        }
1111        Ok(buf.len())
1112    }
1113    fn flush(&mut self) -> io::Result<()> {
1114        Ok(())
1115    }
1116}