diff options
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 255 |
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), } } } |