diff options
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 163 |
1 files changed, 137 insertions, 26 deletions
diff --git a/src/main.rs b/src/main.rs index 9b2a2e7..70bc0b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use crate::types::*; use smol::prelude::*; use smol::fs::File; +use smol::io::Cursor; use smol::lock::RwLock; use std::path::PathBuf; use std::process::ExitCode; @@ -44,6 +45,12 @@ struct Window { scroll_top: RwLock<usize>, } +enum MovementColumnBehavior { + CurrentFromNeutral, + NeutralFromCurrent, + FirstNonBlank, +} + fn main() -> ExitCode { smol::block_on(async { @@ -214,10 +221,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?; }, @@ -231,7 +238,7 @@ impl Ivy { *row += 1; }; - Ok(()) + Ok(MovementColumnBehavior::CurrentFromNeutral) }).await?; }, @@ -244,7 +251,7 @@ impl Ivy { *row -= 1; } - Ok(()) + Ok(MovementColumnBehavior::CurrentFromNeutral) }).await?; }, @@ -265,11 +272,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?; }, @@ -283,9 +292,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?; }, @@ -304,14 +311,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?; }, @@ -325,9 +361,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) = { @@ -337,15 +377,28 @@ impl Ivy { *window.cursor_column.read().await) }; - movement(self).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.clamp_cursor_column().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?; + }, + } self.scroll_to_cursor().await?; @@ -366,6 +419,28 @@ impl Ivy { 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; @@ -388,6 +463,42 @@ impl Ivy { Ok(()) } + async fn first_non_blank_column(&mut self) -> Result<()> { + let row = *self.window.read().await.cursor_row.read().await; + let buffer = self.buffer.write().await; + + if let Some(row_span) = buffer.line_span(row).await { + let mut offset = 0; + loop { + let sub_span = row_span.start + offset .. row_span.end; + let mut contents = buffer.contents.write().await; + let mut cursor = Cursor::new(&mut contents[sub_span]); + + if let Ok(decode) = encoding::read_utf8_char(&mut cursor).await { + offset += decode.skipped_bytes; + + if decode.c.is_whitespace() { + offset += decode.found_bytes; + } else { + break; + } + } else { + break; + } + } + + let window = self.window.write().await; + *window.cursor_column.write().await = offset; + *window.cursor_neutral_column.write().await = offset; + } else { + let window = self.window.write().await; + *window.cursor_column.write().await = 0; + *window.cursor_neutral_column.write().await = 0; + } + + Ok(()) + } + async fn scroll_to_cursor(&mut self) -> Result<()> { let old_scroll_top = *self.window.read().await.scroll_top.read().await; |