diff options
| -rw-r--r-- | src/main.rs | 116 | ||||
| -rw-r--r-- | src/terminal.rs | 50 |
2 files changed, 145 insertions, 21 deletions
diff --git a/src/main.rs b/src/main.rs index f21096f..9b2a2e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,6 +41,7 @@ struct Window { * land in, if it exists in the target row. */ cursor_neutral_column: RwLock<usize>, + scroll_top: RwLock<usize>, } @@ -132,43 +133,70 @@ impl Ivy { Ok(()) } - async fn draw(&mut self) -> Result<()> { + async fn draw_all(&mut self) -> Result<()> { + let height = *self.terminal.read().await.height.read().await; + + self.draw_range(0 .. height - 1).await?; + self.draw_status_line().await?; + self.fix_cursor_position().await?; + + self.terminal.write().await.stdout.flush().await?; + + Ok(()) + } + + async fn draw_range(&mut self, range: Range<usize>) -> Result<()> { let mut terminal = self.terminal.write().await; + let window = self.window.read().await; let buffer = self.buffer.read().await; + let scroll_top = *window.scroll_top.read().await; - let height = *terminal.height.read().await; + terminal.do_cursor_position(0, range.start).await?; - let mut screen_y = 0; - for logical_y in 0 .. height - 1 { - if let Some(span) = buffer.line_span(logical_y).await { - if logical_y > 0 { + let mut screen_y = range.start; + for i in range.clone() { + if let Some(span) = buffer.line_span(i + scroll_top).await { + if i > range.start { terminal.stdout.write_all("\n".as_bytes()).await?; } terminal.stdout.write_all(&buffer.contents.read().await[span]).await?; - screen_y = logical_y + 1; + screen_y += 1; } else { break; } } - for _ in screen_y .. height - 1 { + for _ in screen_y .. range.end { terminal.stdout.write_all("\n~".as_bytes()).await?; } - terminal.stdout.write_all("\n status goes here".as_bytes()).await?; + Ok(()) + } + async fn draw_status_line(&mut self) -> Result<()> { + let mut terminal = self.terminal.write().await; + let height = *terminal.height.read().await; + + terminal.do_cursor_position(0, height).await?; + terminal.stdout.write_all(" status goes here".as_bytes()).await?; + + Ok(()) + } + + async fn fix_cursor_position(&mut self) -> Result<()> { + let mut terminal = self.terminal.write().await; let window = self.window.read().await; terminal.do_cursor_position(*window.cursor_column.read().await, - *window.cursor_row.read().await).await?; - terminal.stdout.flush().await?; + *window.cursor_row.read().await + - *window.scroll_top.read().await).await?; Ok(()) } async fn interact(&mut self) -> Result<()> { - self.draw().await?; + self.draw_all().await?; loop { let c = self.terminal.write().await.read_char().await?; @@ -317,7 +345,28 @@ impl Ivy { * It does require taking a couple of locks that we might always need, but * there's no strong reason to avoid that. */ + self.clamp_cursor_column().await?; + + self.scroll_to_cursor().await?; + + { + let window = self.window.read().await; + let row = *window.cursor_row.read().await; + let column = *window.cursor_column.read().await; + if old_row != row || old_column != column { + let mut terminal = self.terminal.write().await; + let scroll_top = *window.scroll_top.read().await; + + terminal.do_cursor_position(column, row - scroll_top).await?; + terminal.stdout.flush().await?; + } + } + + Ok(()) + } + + async fn clamp_cursor_column(&mut self) -> Result<()> { let window = self.window.write().await; let row = window.cursor_row.read().await; let mut column = window.cursor_column.write().await; @@ -336,11 +385,45 @@ impl Ivy { } } - if old_row != *row || old_column != *column { - let mut terminal = self.terminal.write().await; + Ok(()) + } + + async fn scroll_to_cursor(&mut self) -> Result<()> { + let old_scroll_top = *self.window.read().await.scroll_top.read().await; + + { + let terminal = self.terminal.read().await; + let window = self.window.write().await; + + let mut scroll_top = window.scroll_top.write().await; + let row = *window.cursor_row.read().await; + let height = *terminal.height.read().await; + let last_visible = *scroll_top + height - 2; + + if row < *scroll_top { + *scroll_top -= *scroll_top - row; + } else if row > last_visible { + *scroll_top += row - last_visible; + } + } + + let new_scroll_top = *self.window.read().await.scroll_top.read().await; + let height = *self.terminal.read().await.height.read().await - 1; + if new_scroll_top != old_scroll_top { + let difference = new_scroll_top as isize - old_scroll_top as isize; + if (difference.abs() as usize) < height { + self.terminal.write().await.scroll(difference).await?; - terminal.do_cursor_position(*column, *row).await?; - terminal.stdout.flush().await?; + if difference > 0 { + self.draw_range((height as isize - difference) as usize + .. height).await?; + } else { + self.draw_range(0 .. difference.abs() as usize).await?; + } + } else { + self.terminal.write().await.clear().await?; + self.draw_all().await?; + } } Ok(()) @@ -436,6 +519,7 @@ impl Window { cursor_row: RwLock::new(0), cursor_column: RwLock::new(0), cursor_neutral_column: RwLock::new(0), + scroll_top: RwLock::new(0), } } } diff --git a/src/terminal.rs b/src/terminal.rs index 28e83b3..1ba2b01 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -81,10 +81,18 @@ impl Terminal { *self.width.write().await = width.try_into().unwrap(); *self.height.write().await = height.try_into().unwrap(); + self.set_scroll_region(0, height - 1).await?; + Ok(()) } pub async fn zap_full_screen(&mut self) -> Result<()> { + let (width, height) = self.do_report_size().await?; + *self.width.write().await = width.try_into().unwrap(); + *self.height.write().await = height.try_into().unwrap(); + + self.set_scroll_region(0, height).await?; + self.do_end_alternate_screen().await?; self.stdout.flush().await?; @@ -113,7 +121,7 @@ impl Terminal { } pub async fn read_report(&mut self, escape_type: EscapeType) - -> Result<(char, Vec<u64>)> + -> Result<(char, Vec<usize>)> { let mut expected = escape_type.intro(); @@ -126,7 +134,7 @@ impl Terminal { let mut values = Vec::new(); - let mut number: u64 = 0; + let mut number: usize = 0; let mut in_number = false; let mut after_semicolon = false; @@ -140,9 +148,11 @@ impl Terminal { if c.is_ascii_digit() { if in_number { number = number * 10 - + <u32 as Into<u64>>::into(c.to_digit(10).unwrap()); + + <u32 as TryInto<usize>>::try_into( + c.to_digit(10).unwrap()).unwrap(); } else { - number = c.to_digit(10).unwrap().into(); + number = <u32 as TryInto<usize>>::try_into( + c.to_digit(10).unwrap()).unwrap(); in_number = true; after_semicolon = false; @@ -195,7 +205,7 @@ impl Terminal { } // dtterm? xterm - pub async fn do_report_size(&mut self) -> Result<(u64, u64)> { + pub async fn do_report_size(&mut self) -> Result<(usize, usize)> { self.do_escape(EscapeType::CSI, "t", &[18]).await?; self.stdout.flush().await?; let (code, values) = self.read_report(EscapeType::CSI).await?; @@ -205,6 +215,36 @@ impl Terminal { Err(std::io::Error::other("Couldn't read terminal size.")) } } + + // vt100 + pub async fn clear(&mut self) -> Result<()> { + self.do_escape(EscapeType::CSI, "J", &[2]).await + } + + // vt420, ECMA-48 + pub async fn scroll_up(&mut self, distance: usize) -> Result<()> { + self.do_escape(EscapeType::CSI, "S", &[distance]).await + } + + // vt420, ECMA-48 + pub async fn scroll_down(&mut self, distance: usize) -> Result<()> { + self.do_escape(EscapeType::CSI, "T", &[distance]).await + } + + pub async fn scroll(&mut self, distance_up: isize) -> Result<()> { + if distance_up >= 0 { + self.scroll_up(distance_up as usize).await + } else { + self.scroll_down(-distance_up as usize).await + } + } + + // vt100 + pub async fn set_scroll_region(&mut self, top: usize, bottom: usize) + -> Result<()> + { + self.do_escape(EscapeType::CSI, "r", &[top, bottom]).await + } } |