diff --git a/TODO.md b/TODO.md index 9e24e30..797318e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,3 @@ -- [ ] git gutter flashes on every keystroke/change; this should be fixed. - [ ] make all buffers (including pickers) scrolls the window when the cursor reaches a certain threshold. setting could something like "distance from frame edges", and the value could be an integer or none: - "none" would mean "always keep the cursor in the center of the frame" (i.e. scroll the viewport whenever the cursor moves to a different line) - an integer value would be the distance from the edge of the viewport that the cursor must hit before the viewport begins to scroll. a value of 0 means the viewport scrolls only right before the cursor would otherwise have left the viewport; a value greater than half the width of the viewport behaves similarly to "none" (at least until the viewport is resized to support that range in a meaningful way). diff --git a/docs/architecture.md b/docs/architecture.md index bc590e3..fb161fc 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -86,7 +86,7 @@ Ratatui's bounded layout cache is enabled: unchanged geometry and constraints re The buffer viewport includes a fixed left gutter with right-aligned hybrid line numbers: the highlighted primary cursor line shows its one-based absolute number; every other line shows its distance from the primary. Secondary carets do not establish separate numbering origins. Width follows the total line count plus one separator cell, including an empty final row after a trailing newline; `git_gutter` bar/symbol modes add a blank cell and a marker cell between the number and separator. Number-color/none modes add no width. Horizontal scrolling affects only text; search/selection highlights and carets never paint over the gutter. Wrapped continuations and rows beyond the buffer have no number or marker. The gutter is hidden when it would leave no text column. -Repository gutter work runs off the render/editor thread. Revision/path-validated marker vectors belong to ordinary source buffers, not historical snapshots; panes and tabs borrow their own buffer's markers. The worker compares editor text with Git HEAD or Git-backed jj's single parent, independently of save baselines and index state. The frontend paints green additions, red deletion anchors, and yellow modifications as `▌`, symbols, or number colors; current-line cyan/bold overrides repository colors in number-color mode. +Repository gutter work runs off the render/editor thread. Revision/path-validated worker results update marker vectors belonging to ordinary source buffers, not historical snapshots; panes and tabs borrow their own buffer's markers. After text edits, the last completed markers for the same path remain visible until a current-revision result replaces them, rather than disappearing during each asynchronous refresh. The worker compares editor text with Git HEAD or Git-backed jj's single parent, independently of save baselines and index state. The frontend paints green additions, red deletion anchors, and yellow modifications as `▌`, symbols, or number colors; current-line cyan/bold overrides repository colors in number-color mode. The same cancellable worker optionally collects normal-picker status, including when line-gutter rendering is disabled. Disk metadata and dirty-buffer overlays use Git HEAD or jj's single parent without snapshotting jj or writing active refs, objects, or indexes. A private baseline index retains its stat cache across polls; conflicted jj parents are projected once per root/parent. Root, baseline, store, or builtin Git conversion-setting changes invalidate that cache. Private queries disable user filters, external diff, and textconv. Request/tab/root checks reject stale picker results; unchanged catalogs retain their ordinary scanner and filtering rules. diff --git a/docs/editing.md b/docs/editing.md index 2863c41..17d316c 100644 --- a/docs/editing.md +++ b/docs/editing.md @@ -8,6 +8,8 @@ Ordinary documents show repository changes beside their hybrid line numbers by d The baseline is Git `HEAD` or Git-backed jj's parent `@-`, so indicators include unsaved edits and remain after saving until the repository baseline changes. A deletion marks the next surviving source row; at EOF it marks the last editor row, including the final empty row after a trailing newline. An empty file anchors removals on row 0. Replacements mark paired rows modified and extra current rows added. Historical snapshots do not show live changes. Eligible named files not yet on disk show nonempty unsaved text as added; tracked files missing on disk still compare their buffer text against the baseline. Non-repository/ignored/binary/symlink files, files over 16 MiB, and jj changes with multiple parents have no indicators. +Indicators refresh asynchronously. Typing and undo/redo retain the last completed indicators until the updated comparison arrives, avoiding flashes; indicator values and row positions can briefly lag edits. + ## Default editing keys | Mode | Keys | diff --git a/src/editor.rs b/src/editor.rs index c456cc5..62613f4 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -4932,9 +4932,8 @@ impl Editor { return None; } let changes = self.active.gutter_changes.as_ref()?; - if changes.revision != self.active.text_revision - || Some(changes.path.as_path()) != self.path() - { + // Keep the last completed markers visible while the worker recomputes after edits. + if Some(changes.path.as_path()) != self.path() { return None; } changes.lines.get(row).copied().flatten() diff --git a/src/session.rs b/src/session.rs index 2a1b5d0..d35d158 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1610,17 +1610,34 @@ mod tests { assert_eq!(session.editor.gutter_change(1), Some(Change::Modified)); session.editor.handle(Input::Char('i')); session.editor.handle(Input::Char('x')); - assert_eq!(session.editor.gutter_change(1), None); + assert_eq!(session.editor.gutter_change(1), Some(Change::Modified)); + session.editor.handle(Input::Char('y')); + assert_eq!(session.editor.gutter_change(1), Some(Change::Modified)); + session.editor.handle(Input::Backspace); + assert_eq!(session.editor.gutter_change(1), Some(Change::Modified)); + session.editor.handle(Input::Escape); + session.editor.handle(Input::Char('u')); + assert_eq!(session.editor.gutter_change(1), Some(Change::Modified)); + session.editor.handle(Input::Char('U')); + assert_eq!(session.editor.gutter_change(1), Some(Change::Modified)); assert!(!session.apply_gutter_result( second_request, vec![(first_buffer, Ok(vec![None, Some(Change::Removed)]))], )); + assert_eq!(session.editor.gutter_change(1), Some(Change::Modified)); session.poll_gutter_at(now); + assert_eq!(session.editor.gutter_change(1), Some(Change::Modified)); assert!(session.apply_gutter_result( session.gutter_request_id, vec![(first_buffer, Ok(vec![None, Some(Change::Added)]))], )); assert_eq!(session.editor.gutter_change(1), Some(Change::Added)); + session.poll_gutter_at(now + Duration::from_secs(1)); + assert!(session.apply_gutter_result( + session.gutter_request_id, + vec![(first_buffer, Ok(vec![None, None]))], + )); + assert_eq!(session.editor.gutter_change(1), None); session.editor.execute(&format!("buffer {second_buffer}")); assert_eq!(session.editor.gutter_change(0), Some(Change::Added)); }