From 1515576de700e785d89986f34bd3ecc8350c91bd Mon Sep 17 00:00:00 2001 From: Feng Liang Date: Fri, 15 Sep 2023 20:10:44 +0800 Subject: [PATCH 1/5] add an example of incrementally printing markdown --- examples/incremental_print_markdown/main.rs | 44 +++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 examples/incremental_print_markdown/main.rs diff --git a/examples/incremental_print_markdown/main.rs b/examples/incremental_print_markdown/main.rs new file mode 100644 index 0000000..20b209a --- /dev/null +++ b/examples/incremental_print_markdown/main.rs @@ -0,0 +1,44 @@ +use std::io::{stdout, Write}; +use std::thread::sleep; +use termimad::crossterm::{cursor, ExecutableCommand}; +use termimad::{FmtText, MadSkin}; +use termimad::crossterm::terminal::Clear; +use termimad::crossterm::terminal::ClearType::FromCursorDown; + +const MARKDOWN_TEXT: &str = r#" +# Hello + +This is inline code `print("hello")`. + +```python +print("hello") +``` + +Here ends it. +"#; + + +fn main() { + let skin = MadSkin::default(); + stdout().execute(cursor::Hide).unwrap(); // Hide cursor to hide cursor movement + let mut string_buffer = String::new(); + let mut generator = MARKDOWN_TEXT.chars(); + stdout().execute(cursor::SavePosition).unwrap(); // save initial cursor position + loop { + let next_one = generator.next(); + if let Some(chunk) = next_one { + stdout() + .execute(cursor::RestorePosition).unwrap() // restore cursor position to initial + .execute(Clear(FromCursorDown)).unwrap(); // clear previous output + string_buffer.push(chunk); + let formatted_text = FmtText::from(&skin, &string_buffer, None); // can have Some(width) to enable hard wrapping + print!("{}", formatted_text); + stdout().flush().unwrap(); + sleep(std::time::Duration::from_millis(100)); + } else { + break; + } + } + stdout().execute(cursor::Show).unwrap(); + sleep(std::time::Duration::from_millis(500)); +} \ No newline at end of file From 2bfb578623c1d46c077be1587a338debea5d4ca5 Mon Sep 17 00:00:00 2001 From: Feng Liang Date: Fri, 15 Sep 2023 23:08:38 +0800 Subject: [PATCH 2/5] use while let instead in incremental_print_markdown example --- examples/incremental_print_markdown/main.rs | 23 ++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/examples/incremental_print_markdown/main.rs b/examples/incremental_print_markdown/main.rs index 20b209a..965fc5d 100644 --- a/examples/incremental_print_markdown/main.rs +++ b/examples/incremental_print_markdown/main.rs @@ -24,20 +24,15 @@ fn main() { let mut string_buffer = String::new(); let mut generator = MARKDOWN_TEXT.chars(); stdout().execute(cursor::SavePosition).unwrap(); // save initial cursor position - loop { - let next_one = generator.next(); - if let Some(chunk) = next_one { - stdout() - .execute(cursor::RestorePosition).unwrap() // restore cursor position to initial - .execute(Clear(FromCursorDown)).unwrap(); // clear previous output - string_buffer.push(chunk); - let formatted_text = FmtText::from(&skin, &string_buffer, None); // can have Some(width) to enable hard wrapping - print!("{}", formatted_text); - stdout().flush().unwrap(); - sleep(std::time::Duration::from_millis(100)); - } else { - break; - } + while let Some(chunk) = generator.next() { + stdout() + .execute(cursor::RestorePosition).unwrap() // restore cursor position to initial + .execute(Clear(FromCursorDown)).unwrap(); // clear previous output + string_buffer.push(chunk); + let formatted_text = FmtText::from(&skin, &string_buffer, None); // can have Some(width) to enable hard wrapping + print!("{}", formatted_text); + stdout().flush().unwrap(); + sleep(std::time::Duration::from_millis(100)); } stdout().execute(cursor::Show).unwrap(); sleep(std::time::Duration::from_millis(500)); From fb5b0eb52d9ae278e8280704428fb4cd8336c73f Mon Sep 17 00:00:00 2001 From: Feng Liang Date: Sat, 16 Sep 2023 18:54:25 +0800 Subject: [PATCH 3/5] refined the example incremental_print_markdown Now a scrollbar is not a problem --- Cargo.toml | 1 + examples/incremental_print_markdown/main.rs | 120 +++++++++++++++++--- 2 files changed, 106 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 48a8999..1259111 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ cli-log = "2.0" crokey = "0.4" deser-hjson = "2.0" terminal-clipboard = "0.3.1" +ctrlc = "3.4" [patch.crates-io] # coolor = { path = "../coolor" } diff --git a/examples/incremental_print_markdown/main.rs b/examples/incremental_print_markdown/main.rs index 965fc5d..92061a6 100644 --- a/examples/incremental_print_markdown/main.rs +++ b/examples/incremental_print_markdown/main.rs @@ -1,39 +1,129 @@ use std::io::{stdout, Write}; +use std::process::exit; use std::thread::sleep; use termimad::crossterm::{cursor, ExecutableCommand}; use termimad::{FmtText, MadSkin}; use termimad::crossterm::terminal::Clear; use termimad::crossterm::terminal::ClearType::FromCursorDown; +pub struct IncrementalMarkdownPrinter { + pub skin: MadSkin, + pub wrap_width: Option, + cursor_anchor: Option<(u16, u16)>, + activated: bool, +} + +impl IncrementalMarkdownPrinter { + pub fn new(skin: MadSkin, wrap_width: Option) -> Self { + IncrementalMarkdownPrinter { + skin, + wrap_width, + cursor_anchor: None, + activated: false, + } + } + + pub fn activated(&self) -> bool { + self.activated + } + + pub fn activate(&mut self) { + if self.activated { + eprintln!("IncrementalMarkdownPrinter is already activated"); + return; + } + self.activated = true; + self.set_anchor(); + stdout().execute(cursor::Hide).unwrap(); + } + + pub fn set_anchor_with(&mut self, anchor_position: (u16, u16)) { + assert!(self.activated, "IncrementalMarkdownPrinter must be activated before anchoring cursor"); + self.cursor_anchor = Some(anchor_position); + } + + pub fn set_anchor(&mut self) { + self.set_anchor_with(cursor::position().unwrap()); + } + + pub fn deactivate(&mut self) { + if !self.activated { + eprintln!("IncrementalMarkdownPrinter is already deactivated"); + return; + } + self.activated = false; + self.cursor_anchor = None; + stdout().execute(cursor::Show).unwrap(); + } + + pub fn print(&mut self, partial_markdown: &str) { + assert!(self.activated, "IncrementalMarkdownPrinter must be activated before printing"); + let cursor_anchor = self.cursor_anchor.unwrap(); + // restore cursor position to anchor + stdout() + .execute(cursor::MoveTo(cursor_anchor.0, cursor_anchor.1)).unwrap() + .execute(Clear(FromCursorDown)).unwrap(); // clear previous output + let formatted_text = FmtText::from(&self.skin, partial_markdown, self.wrap_width.clone()); + let rows = formatted_text.lines.len() as u16; + let columns = formatted_text.lines.last().as_ref().map_or(0, |last_line| last_line.visible_length()) as u16; + print!("{}", formatted_text); + stdout().flush().unwrap(); + // update cursor anchor + let mut new_cursor_anchor = cursor::position().unwrap(); + if new_cursor_anchor.0 > columns { + new_cursor_anchor.0 -= columns; + } else { + new_cursor_anchor.0 = 0; + } + if new_cursor_anchor.1 > rows { + new_cursor_anchor.1 -= rows; + } else { + new_cursor_anchor.1 = 0; + } + self.set_anchor_with(new_cursor_anchor); + } +} + +impl Drop for IncrementalMarkdownPrinter { + fn drop(&mut self) { + if self.activated { + self.deactivate(); + } + } +} + const MARKDOWN_TEXT: &str = r#" # Hello This is inline code `print("hello")`. +A super long line to "tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttest" the hard wrapping. + ```python print("hello") ``` -Here ends it. -"#; - +Here ends it."#; fn main() { + ctrlc::set_handler(move || { + // to avoid missing cursor when Ctrl-C is pressed + stdout().execute(cursor::Show).unwrap(); + exit(0); + }).expect("Error setting Ctrl-C handler"); + let skin = MadSkin::default(); - stdout().execute(cursor::Hide).unwrap(); // Hide cursor to hide cursor movement - let mut string_buffer = String::new(); + // let terminal_width = termimad::terminal_size().0 as usize; + let terminal_width = 40; // for testing + let mut printer = IncrementalMarkdownPrinter::new(skin, Some(terminal_width)); + printer.activate(); let mut generator = MARKDOWN_TEXT.chars(); - stdout().execute(cursor::SavePosition).unwrap(); // save initial cursor position + let mut string_buffer = String::new(); while let Some(chunk) = generator.next() { - stdout() - .execute(cursor::RestorePosition).unwrap() // restore cursor position to initial - .execute(Clear(FromCursorDown)).unwrap(); // clear previous output string_buffer.push(chunk); - let formatted_text = FmtText::from(&skin, &string_buffer, None); // can have Some(width) to enable hard wrapping - print!("{}", formatted_text); - stdout().flush().unwrap(); - sleep(std::time::Duration::from_millis(100)); + printer.print(string_buffer.as_str()); + sleep(std::time::Duration::from_millis(50)); } - stdout().execute(cursor::Show).unwrap(); - sleep(std::time::Duration::from_millis(500)); + printer.deactivate(); + sleep(std::time::Duration::from_millis(1000)); } \ No newline at end of file From eac3d8cabb22318036130b40af73f0aa6a4dc064 Mon Sep 17 00:00:00 2001 From: Feng Liang Date: Sat, 16 Sep 2023 19:00:06 +0800 Subject: [PATCH 4/5] add comment --- examples/incremental_print_markdown/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/incremental_print_markdown/main.rs b/examples/incremental_print_markdown/main.rs index 92061a6..2209230 100644 --- a/examples/incremental_print_markdown/main.rs +++ b/examples/incremental_print_markdown/main.rs @@ -69,6 +69,7 @@ impl IncrementalMarkdownPrinter { print!("{}", formatted_text); stdout().flush().unwrap(); // update cursor anchor + // the cursor position is relative to the terminal not the screen/history, so the anchor "floats" when a scrollbar appears. let mut new_cursor_anchor = cursor::position().unwrap(); if new_cursor_anchor.0 > columns { new_cursor_anchor.0 -= columns; From 185d5d24ffe4c8ad91e1a3f867a48e664eab2f0a Mon Sep 17 00:00:00 2001 From: Feng Liang Date: Sat, 16 Sep 2023 19:01:12 +0800 Subject: [PATCH 5/5] modify comment --- examples/incremental_print_markdown/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/incremental_print_markdown/main.rs b/examples/incremental_print_markdown/main.rs index 2209230..87b3337 100644 --- a/examples/incremental_print_markdown/main.rs +++ b/examples/incremental_print_markdown/main.rs @@ -69,7 +69,7 @@ impl IncrementalMarkdownPrinter { print!("{}", formatted_text); stdout().flush().unwrap(); // update cursor anchor - // the cursor position is relative to the terminal not the screen/history, so the anchor "floats" when a scrollbar appears. + // the cursor position is relative to the terminal not the screen/history, so the anchor "floats/drifts" when a scrollbar appears. let mut new_cursor_anchor = cursor::position().unwrap(); if new_cursor_anchor.0 > columns { new_cursor_anchor.0 -= columns;