summary refs log tree commit diff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs255
1 files changed, 215 insertions, 40 deletions
diff --git a/src/main.rs b/src/main.rs
index f21096f..4b5dc5a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -41,6 +41,13 @@ struct Window {
    * land in, if it exists in the target row.
    */
   cursor_neutral_column: RwLock<usize>,
+  scroll_top: RwLock<usize>,
+}
+
+enum MovementColumnBehavior {
+  CurrentFromNeutral,
+  NeutralFromCurrent,
+  FirstNonBlank,
 }
 
 
@@ -132,43 +139,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?;
@@ -186,10 +220,10 @@ impl Ivy {
                * the movement actually moves. Tentatively, it doesn't look
                * like POSIX has an opinion on this, but it matches Vim.
                */
-              *window.cursor_neutral_column.write().await = *column;
+              Ok(MovementColumnBehavior::NeutralFromCurrent)
+            } else {
+              Ok(MovementColumnBehavior::CurrentFromNeutral)
             }
-
-            Ok(())
           }).await?;
         },
 
@@ -203,7 +237,7 @@ impl Ivy {
               *row += 1;
             };
 
-            Ok(())
+            Ok(MovementColumnBehavior::CurrentFromNeutral)
           }).await?;
         },
 
@@ -216,7 +250,7 @@ impl Ivy {
               *row -= 1;
             }
 
-            Ok(())
+            Ok(MovementColumnBehavior::CurrentFromNeutral)
           }).await?;
         },
 
@@ -237,11 +271,13 @@ impl Ivy {
                  * the movement actually moves. Tentatively, it doesn't look
                  * like POSIX has an opinion on this, but it matches Vim.
                  */
-                *window.cursor_neutral_column.write().await = *column;
+                Ok(MovementColumnBehavior::NeutralFromCurrent)
+              } else {
+                Ok(MovementColumnBehavior::CurrentFromNeutral)
               }
+            } else {
+              Ok(MovementColumnBehavior::CurrentFromNeutral)
             }
-
-            Ok(())
           }).await?;
         },
 
@@ -255,9 +291,7 @@ impl Ivy {
             /* For this one, the neutral column changes regardless of
              * whether the column does.
              */
-            *window.cursor_neutral_column.write().await = *column;
-
-            Ok(())
+            Ok(MovementColumnBehavior::NeutralFromCurrent)
           }).await?;
         },
 
@@ -276,14 +310,43 @@ impl Ivy {
               } else {
                 *column = 0;
               }
+            }
 
-              /* For this one, the neutral column changes regardless of
-               * whether the column does.
-               */
-              *window.cursor_neutral_column.write().await = *column;
+            /* For this one, the neutral column changes regardless of
+             * whether the column does.
+             */
+            Ok(MovementColumnBehavior::NeutralFromCurrent)
+          }).await?;
+        },
+
+        'H' => {
+          self.handle_movement(async |ivy: &mut Ivy| {
+            let window = ivy.window.write().await;
+
+            let mut row = window.cursor_row.write().await;
+            *row = *window.scroll_top.read().await;
+
+            Ok(MovementColumnBehavior::FirstNonBlank)
+          }).await?;
+        },
+
+        'L' => {
+          self.handle_movement(async |ivy: &mut Ivy| {
+            let window = ivy.window.write().await;
+            let terminal = ivy.terminal.read().await;
+            let buffer = ivy.buffer.read().await;
+
+            let scroll_top = *window.scroll_top.read().await;
+            let height = *terminal.height.read().await;
+            let total_lines = buffer.lines.read().await.len();
+
+            let mut row = window.cursor_row.write().await;
+            *row = scroll_top + height - 2;
+            if *row > total_lines - 1 {
+              *row = total_lines - 1;
             }
 
-            Ok(())
+            Ok(MovementColumnBehavior::FirstNonBlank)
           }).await?;
         },
 
@@ -297,9 +360,13 @@ impl Ivy {
   /* A movement is an arbitrarily complicated action that will not change the
    * contents of the buffer, but may change other things, including the cursor
    * position.
+   *
+   * The return value of the action indicates whether to update the neutral
+   * column.
    */
   async fn handle_movement(&mut self,
-                           movement: impl AsyncFn(&mut Ivy) -> Result<()>)
+                           movement: impl AsyncFn(&mut Ivy)
+                                         -> Result<MovementColumnBehavior>)
       -> Result<()>
   {
     let (old_row, old_column) = {
@@ -309,15 +376,71 @@ impl Ivy {
        *window.cursor_column.read().await)
     };
 
-    movement(self).await?;
+    let column_behavior = movement(self).await?;
+
+    match column_behavior {
+      MovementColumnBehavior::NeutralFromCurrent => {
+        /* We clamp the destination column to the line width and use that as
+         * the neutral column.
+         */
+        self.update_neutral_column().await?;
+      },
+      MovementColumnBehavior::CurrentFromNeutral => {
+        /* We clamp the neutral column to the line width and use that as the
+         * destination column.
+         */
+        self.clamp_cursor_column().await?;
+      },
+      MovementColumnBehavior::FirstNonBlank => {
+        /* We compute the first non-blank column on the line and use that as
+         * both the destination and neutral columns.
+         */
+        self.first_non_blank_column().await?;
+      },
+    }
 
-    /* We clamp the column to the line width unconditionally, regardless of
-     * what kind of movement we did. This is always correct, because no
-     * intended behavior ever places the cursor beyond the end of the line.
-     * It does require taking a couple of locks that we might always need, but
-     * there's no strong reason to avoid that.
-     */
+    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 update_neutral_column(&mut self) -> Result<()> {
+    let buffer = self.buffer.write().await;
+    let window = self.window.write().await;
+    let mut column = window.cursor_column.write().await;
+    let mut neutral_column = window.cursor_neutral_column.write().await;
+    let row = window.cursor_row.read().await;
+
+    if let Some(span) = buffer.line_span(*row).await {
+      let width = span.end - span.start;
+
+      if width == 0 {
+        *column = 0;
+      } else if *column > width - 1 {
+        *column = width - 1;
+      }
+    }
+
+    *neutral_column = *column;
 
+    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 +459,62 @@ impl Ivy {
       }
     }
 
-    if old_row != *row || old_column != *column {
-      let mut terminal = self.terminal.write().await;
+    Ok(())
+  }
+
+  async fn first_non_blank_column(&mut self) -> Result<()> {
+    let row = *self.window.read().await.cursor_row.read().await;
+
+    if let Some(span) = self.buffer.read().await.line_span(row).await {
+      /* TODO */
+      let window = self.window.write().await;
+      *window.cursor_column.write().await = 0;
+      *window.cursor_neutral_column.write().await = 0;
+    } else {
+      let window = self.window.write().await;
+      *window.cursor_column.write().await = 0;
+      *window.cursor_neutral_column.write().await = 0;
+    }
+
+    Ok(())
+  }
 
-      terminal.do_cursor_position(*column, *row).await?;
-      terminal.stdout.flush().await?;
+  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?;
+
+        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 +610,7 @@ impl Window {
       cursor_row: RwLock::new(0),
       cursor_column: RwLock::new(0),
       cursor_neutral_column: RwLock::new(0),
+      scroll_top: RwLock::new(0),
     }
   }
 }