summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/main.rs116
-rw-r--r--src/terminal.rs50
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
+  }
 }