From b276dfedd2d13de9918b9b87a3d543e7b5eca95e Mon Sep 17 00:00:00 2001 From: sigoden Date: Mon, 30 Oct 2023 10:07:01 +0800 Subject: feat: support textwrap (#171) * feat: support textwrap * improve cmd_render_stream * done cmd_render_stream * add `-w` alias to `--wrap` * done repl_render_stream * add `config.wrap_code` * remove cached config.wrap_width * fix unxpected duplicate lines on kitty * refactor markdown render * improve render * fix test --- src/render/cmd.rs | 56 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 16 deletions(-) (limited to 'src/render/cmd.rs') diff --git a/src/render/cmd.rs b/src/render/cmd.rs index 527c194..fb45b0a 100644 --- a/src/render/cmd.rs +++ b/src/render/cmd.rs @@ -5,15 +5,16 @@ use crate::repl::{ReplyStreamEvent, SharedAbortSignal}; use anyhow::Result; use crossbeam::channel::Receiver; +use textwrap::core::display_width; #[allow(clippy::unnecessary_wraps, clippy::module_name_repetitions)] pub fn cmd_render_stream( rx: &Receiver, - light_theme: bool, + render: &mut MarkdownRender, abort: &SharedAbortSignal, ) -> Result<()> { let mut buffer = String::new(); - let mut markdown_render = MarkdownRender::new(light_theme); + let mut col = 0; loop { if abort.aborted() { return Ok(()); @@ -23,28 +24,39 @@ pub fn cmd_render_stream( ReplyStreamEvent::Text(text) => { if text.contains('\n') { let text = format!("{buffer}{text}"); - let mut lines: Vec<&str> = text.split('\n').collect(); - buffer = lines.pop().unwrap_or_default().to_string(); - let output = lines.join("\n"); - print_now!("{}\n", markdown_render.render_block(&output)); + let (head, tail) = split_line_tail(&text); + buffer = tail.to_string(); + let input = format!("{}{head}", spaces(col)); + let output = render.render(&input); + print_now!("{}\n", &output[col..]); + col = 0; } else { buffer = format!("{buffer}{text}"); - if !(markdown_render.is_code_block() - || buffer.len() < 60 + if !(render.is_code() + || buffer.len() < 40 || buffer.starts_with('#') || buffer.starts_with('>') || buffer.starts_with('|')) { - if let Some((output, remain)) = split_line(&buffer) { - print_now!("{}", markdown_render.render_line(&output)); + if let Some((head, remain)) = split_line_sematic(&buffer) { buffer = remain; + let input = format!("{}{head}", spaces(col)); + let output = render.render(&input); + let output = &output[col..]; + let (_, tail) = split_line_tail(output); + if output.contains('\n') { + col = display_width(tail); + } else { + col += display_width(output); + } + print_now!("{}", output); } } } } ReplyStreamEvent::Done => { - let output = markdown_render.render_block(&buffer); - print_now!("{}\n", output.trim_end()); + let input = format!("{}{buffer}", spaces(col)); + print_now!("{}\n", render.render(&input)); break; } } @@ -53,9 +65,9 @@ pub fn cmd_render_stream( Ok(()) } -fn split_line(line: &str) -> Option<(String, String)> { +fn split_line_sematic(text: &str) -> Option<(String, String)> { let mut balance: Vec = Vec::new(); - let chars: Vec = line.chars().collect(); + let chars: Vec = text.chars().collect(); let mut index = 0; let len = chars.len(); while index < len - 1 { @@ -82,6 +94,18 @@ fn split_line(line: &str) -> Option<(String, String)> { None } +pub(crate) fn split_line_tail(text: &str) -> (&str, &str) { + if let Some((head, tail)) = text.rsplit_once('\n') { + (head, tail) + } else { + ("", text) + } +} + +fn spaces(n: usize) -> String { + " ".repeat(n) +} + #[derive(Debug, Clone, Copy, Eq, PartialEq)] enum Kind { ParentheseStart, @@ -176,12 +200,12 @@ mod tests { macro_rules! assert_split_line { ($a:literal, $b:literal, true) => { assert_eq!( - split_line(&format!("{}{}", $a, $b)), + split_line_sematic(&format!("{}{}", $a, $b)), Some(($a.into(), $b.into())) ); }; ($a:literal, $b:literal, false) => { - assert_eq!(split_line(&format!("{}{}", $a, $b)), None); + assert_eq!(split_line_sematic(&format!("{}{}", $a, $b)), None); }; } -- cgit v1.2.3