diff options
| author | sigoden <sigoden@gmail.com> | 2023-11-08 18:26:38 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-11-08 18:26:38 +0800 |
| commit | eb30d90391ef73429589a98f34dfe4d3d88d0a38 (patch) | |
| tree | 3ba569b16048a289d729262ffce475d610e51187 /src/render/repl.rs | |
| parent | a8a504e0f07c2d1c725bace7f0affb468cf0622d (diff) | |
| download | aichat-eb30d90391ef73429589a98f34dfe4d3d88d0a38.tar.gz | |
refactor: improve render (#235)
* refactor: redesign render
- if stdout is not terminal, just write reply text to stdout
- rename repl_render_stream to markdown_stream
- deprecate cmd_render_stream
- use raw_stream to just print streaming reply text
* optimize rendering error
* optimize render_stream
Diffstat (limited to 'src/render/repl.rs')
| -rw-r--r-- | src/render/repl.rs | 163 |
1 files changed, 0 insertions, 163 deletions
diff --git a/src/render/repl.rs b/src/render/repl.rs deleted file mode 100644 index eeb5420..0000000 --- a/src/render/repl.rs +++ /dev/null @@ -1,163 +0,0 @@ -use super::{MarkdownRender, ReplyEvent}; - -use crate::utils::{split_line_tail, AbortSignal}; - -use anyhow::Result; -use crossbeam::channel::Receiver; -use crossterm::{ - cursor, - event::{self, Event, KeyCode, KeyModifiers}, - queue, style, - terminal::{self, disable_raw_mode, enable_raw_mode}, -}; -use std::{ - io::{self, Stdout, Write}, - time::{Duration, Instant}, -}; -use textwrap::core::display_width; - -pub fn repl_render_stream( - rx: &Receiver<ReplyEvent>, - render: &mut MarkdownRender, - abort: &AbortSignal, -) -> Result<()> { - enable_raw_mode()?; - let mut stdout = io::stdout(); - - let ret = repl_render_stream_inner(rx, render, abort, &mut stdout); - - disable_raw_mode()?; - - ret -} - -fn repl_render_stream_inner( - rx: &Receiver<ReplyEvent>, - render: &mut MarkdownRender, - abort: &AbortSignal, - writer: &mut Stdout, -) -> Result<()> { - let mut last_tick = Instant::now(); - let tick_rate = Duration::from_millis(50); - - let mut buffer = String::new(); - let mut buffer_rows = 1; - - let columns = terminal::size()?.0; - - loop { - if abort.aborted() { - return Ok(()); - } - - if let Ok(evt) = rx.try_recv() { - match evt { - ReplyEvent::Text(text) => { - let (col, mut row) = cursor::position()?; - - // Fix unexpected duplicate lines on kitty, see https://github.com/sigoden/aichat/issues/105 - if col == 0 && row > 0 && display_width(&buffer) == columns as usize { - row -= 1; - } - - if row + 1 >= buffer_rows { - queue!(writer, cursor::MoveTo(0, row + 1 - buffer_rows),)?; - } else { - let scroll_rows = buffer_rows - row - 1; - queue!( - writer, - terminal::ScrollUp(scroll_rows), - cursor::MoveTo(0, 0), - )?; - } - - // No guarantee that text returned by render will not be re-layouted, so it is better to clear it. - queue!(writer, terminal::Clear(terminal::ClearType::FromCursorDown))?; - - if text.contains('\n') { - let text = format!("{buffer}{text}"); - let (head, tail) = split_line_tail(&text); - buffer = tail.to_string(); - let output = render.render(head); - print_block(writer, &output, columns)?; - queue!(writer, style::Print(&buffer),)?; - - // No guarantee the buffer width of the buffer will not exceed the number of columns. - // So we calculate the number of rows needed, rather than setting it directly to 1. - buffer_rows = need_rows(&buffer, columns); - } else { - buffer = format!("{buffer}{text}"); - let output = render.render_line(&buffer); - if output.contains('\n') { - let (head, tail) = split_line_tail(&output); - buffer_rows = print_block(writer, head, columns)?; - queue!(writer, style::Print(&tail),)?; - - // Same as above. - buffer_rows += need_rows(tail, columns); - } else { - queue!(writer, style::Print(&output))?; - buffer_rows = need_rows(&output, columns); - } - } - - writer.flush()?; - } - ReplyEvent::Done => { - #[cfg(target_os = "windows")] - let eol = "\n\n"; - #[cfg(not(target_os = "windows"))] - let eol = "\n"; - queue!(writer, style::Print(eol))?; - writer.flush()?; - - break; - } - } - continue; - } - - let timeout = tick_rate - .checked_sub(last_tick.elapsed()) - .unwrap_or_else(|| Duration::from_secs(0)); - if crossterm::event::poll(timeout)? { - if let Event::Key(key) = event::read()? { - match key.code { - KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => { - abort.set_ctrlc(); - return Ok(()); - } - KeyCode::Char('d') if key.modifiers == KeyModifiers::CONTROL => { - abort.set_ctrld(); - return Ok(()); - } - _ => {} - } - } - } - - if last_tick.elapsed() >= tick_rate { - last_tick = Instant::now(); - } - } - Ok(()) -} - -fn print_block(writer: &mut Stdout, text: &str, columns: u16) -> Result<u16> { - let mut num = 0; - for line in text.split('\n') { - queue!( - writer, - style::Print(line), - style::Print("\n"), - cursor::MoveLeft(columns), - )?; - num += 1; - } - Ok(num) -} - -fn need_rows(text: &str, columns: u16) -> u16 { - let buffer_width = display_width(text).max(1) as u16; - (buffer_width + columns - 1) / columns -} |
