From d876818df17ad7b25ebb38a678aa23c79b826e38 Mon Sep 17 00:00:00 2001 From: sigoden Date: Sun, 5 Mar 2023 10:42:35 +0800 Subject: refactor: optimize clear screen (#16) --- src/editor.rs | 110 ----------------------------------------------- src/main.rs | 2 +- src/repl.rs | 11 +++-- src/term.rs | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 114 deletions(-) delete mode 100644 src/editor.rs create mode 100644 src/term.rs (limited to 'src') diff --git a/src/editor.rs b/src/editor.rs deleted file mode 100644 index 1da71a1..0000000 --- a/src/editor.rs +++ /dev/null @@ -1,110 +0,0 @@ -use std::io::{self, Stdout, Write}; - -use anyhow::Result; - -use crossterm::{ - cursor, - event::{self, Event, KeyCode, KeyModifiers}, - queue, style, - terminal::{self, disable_raw_mode, enable_raw_mode}, -}; - -use crate::utils::paste; - -pub fn edit() -> Result { - enable_raw_mode()?; - let mut stdout = io::stdout(); - - let ret = edit_inner(&mut stdout); - - // restore terminal - disable_raw_mode()?; - - ret -} - -fn edit_inner(writer: &mut Stdout) -> Result { - let mut session = Session::new(writer); - - loop { - let evt = event::read()?; - if let Event::Key(key) = evt { - match key.code { - KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => { - // quit - return Ok(String::new()); - } - KeyCode::Char('d') if key.modifiers == KeyModifiers::CONTROL => { - // submit - return Ok(session.buffer); - } - KeyCode::Char('v') if key.modifiers == KeyModifiers::CONTROL => { - let content = paste()?; - session.push_str(&content)?; - } - KeyCode::Char(c) - if matches!(key.modifiers, KeyModifiers::NONE | KeyModifiers::SHIFT) => - { - session.push(c)?; - } - KeyCode::Enter => { - session.push('\n')?; - } - _ => {} - } - } - session.flush()?; - } -} - -struct Session<'a, T: Write> { - writer: &'a mut T, - buffer: String, - dirty: bool, -} - -impl<'a, T: Write> Session<'a, T> { - fn new<'b: 'a>(writer: &'b mut T) -> Self { - Self { - buffer: String::new(), - writer, - dirty: false, - } - } - fn push(&mut self, ch: char) -> io::Result<()> { - if ch == '\n' { - self.new_line()?; - } else { - queue!(self.writer, style::Print(ch))?; - } - self.buffer.push(ch); - self.dirty = true; - Ok(()) - } - fn push_str(&mut self, text: &str) -> io::Result<()> { - for line in text.lines() { - if !line.is_empty() { - queue!(self.writer, style::Print(line))?; - } - self.new_line()?; - } - - Ok(()) - } - fn new_line(&mut self) -> io::Result<()> { - let (_, y) = cursor::position()?; - let (_, h) = terminal::size()?; - if y == h - 1 { - queue!(self.writer, terminal::ScrollUp(1), cursor::MoveTo(0, y))?; - } else { - queue!(self.writer, cursor::MoveToNextLine(1))?; - } - Ok(()) - } - fn flush(&mut self) -> io::Result<()> { - if self.dirty { - return self.writer.flush(); - } - Ok(()) - } -} diff --git a/src/main.rs b/src/main.rs index 55c09a6..dc54b01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ mod cli; mod client; mod config; -mod editor; mod render; mod repl; +mod term; mod utils; use std::sync::Arc; diff --git a/src/repl.rs b/src/repl.rs index 2ca1ea0..cd9e990 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,7 +1,7 @@ use crate::client::ChatGptClient; use crate::config::{Config, Role}; -use crate::editor; use crate::render::{self, MarkdownRender}; +use crate::term; use crate::utils::{copy, dump}; use anyhow::{anyhow, Result}; use crossbeam::channel::{unbounded, Sender}; @@ -115,7 +115,7 @@ impl Repl { dump_repl_help(); } ".clear" => match args { - Some("screen") => self.editor.clear_scrollback()?, + Some("screen") => term::clear_screen(0)?, Some("history") => { let history = Box::new(self.editor.history_mut()); history @@ -139,7 +139,7 @@ impl Repl { "// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)", 1, ); - let content = editor::edit()?; + let content = term::edit()?; dump("", 1); handler.handle(ReplCmd::Submit(content))?; } @@ -189,6 +189,11 @@ impl Repl { ReedlineEvent::MenuNext, ]), ); + keybindings.add_binding( + KeyModifiers::CONTROL, + KeyCode::Char('l'), + ReedlineEvent::ExecuteHostCommand(".clear screen".into()), + ); keybindings } diff --git a/src/term.rs b/src/term.rs new file mode 100644 index 0000000..0d2780e --- /dev/null +++ b/src/term.rs @@ -0,0 +1,136 @@ +use std::io::{self, Stdout, Write}; + +use anyhow::Result; + +use crossterm::{ + cursor, + event::{self, Event, KeyCode, KeyModifiers}, + queue, style, + terminal::{self, disable_raw_mode, enable_raw_mode, ClearType}, +}; + +use crate::utils::paste; + +pub fn edit() -> Result { + enable_raw_mode()?; + let mut stdout = io::stdout(); + + let ret = edit_inner(&mut stdout); + + // restore terminal + disable_raw_mode()?; + + ret +} + +fn edit_inner(writer: &mut Stdout) -> Result { + let mut session = Session::new(writer); + + loop { + let evt = event::read()?; + if let Event::Key(key) = evt { + match key.code { + KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => { + // quit + return Ok(String::new()); + } + KeyCode::Char('d') if key.modifiers == KeyModifiers::CONTROL => { + // submit + return Ok(session.buffer); + } + KeyCode::Char('v') if key.modifiers == KeyModifiers::CONTROL => { + let content = paste()?; + session.push_str(&content)?; + } + KeyCode::Char(c) + if matches!(key.modifiers, KeyModifiers::NONE | KeyModifiers::SHIFT) => + { + session.push(c)?; + } + KeyCode::Enter => { + session.push('\n')?; + } + _ => {} + } + } + session.flush()?; + } +} + +struct Session<'a, T: Write> { + writer: &'a mut T, + buffer: String, + dirty: bool, +} + +impl<'a, T: Write> Session<'a, T> { + fn new<'b: 'a>(writer: &'b mut T) -> Self { + Self { + buffer: String::new(), + writer, + dirty: false, + } + } + fn push(&mut self, ch: char) -> io::Result<()> { + if ch == '\n' { + self.new_line()?; + } else { + queue!(self.writer, style::Print(ch))?; + } + self.buffer.push(ch); + self.dirty = true; + Ok(()) + } + fn push_str(&mut self, text: &str) -> io::Result<()> { + for line in text.lines() { + if !line.is_empty() { + queue!(self.writer, style::Print(line))?; + } + self.new_line()?; + } + + Ok(()) + } + fn new_line(&mut self) -> io::Result<()> { + let (_, y) = cursor::position()?; + let (_, h) = terminal::size()?; + if y == h - 1 { + queue!(self.writer, terminal::ScrollUp(1), cursor::MoveTo(0, y))?; + } else { + queue!(self.writer, cursor::MoveToNextLine(1))?; + } + Ok(()) + } + fn flush(&mut self) -> io::Result<()> { + if self.dirty { + return self.writer.flush(); + } + Ok(()) + } +} + +pub fn clear_screen(keep_lines: u16) -> Result<()> { + enable_raw_mode()?; + let mut stdout = io::stdout(); + + let ret = clear_screen_inner(&mut stdout, keep_lines); + + // restore terminal + disable_raw_mode()?; + + ret +} + +fn clear_screen_inner(writer: &mut Stdout, keep_lines: u16) -> Result<()> { + let (_, h) = terminal::size()?; + queue!( + writer, + style::Print("\n".repeat((h - 2).into())), + terminal::ScrollUp(2), + cursor::MoveTo(0, 0), + terminal::Clear(ClearType::FromCursorDown), + terminal::ScrollUp(keep_lines), + )?; + writer.flush()?; + Ok(()) +} -- cgit v1.2.3