refactor: name configurable viewport scroll steps accurately

This commit is contained in:
Valdemar Lorenzen
2026-10-16 10:31:36 +02:00
parent 284ddd3713
commit d1048df345
12 changed files with 80 additions and 54 deletions
+1
View File
@@ -12,6 +12,7 @@
- [x] diff view: automatically detect changes and update the view when relevant bookmarks move change ids (regards bookmark-based diff view; i.e., user selected at least one bookmark in the before/after picker) - [x] diff view: automatically detect changes and update the view when relevant bookmarks move change ids (regards bookmark-based diff view; i.e., user selected at least one bookmark in the before/after picker)
- [x] action to scroll viewport without having to move the cursor (like vim's `ctrl+e|u`). - [x] action to scroll viewport without having to move the cursor (like vim's `ctrl+e|u`).
- [x] scroll viewport action (`ctrl+u|d`) scroll distance should be configurable. - [x] scroll viewport action (`ctrl+u|d`) scroll distance should be configurable.
- [ ] file picker file search should also be fuzzy (we should be able to use the same fuzzy implemented for diff view search, i believe)
- [ ] diff view: labels are currently attached also when scrolling the view port horizontally (such that e.g. a function name is not fully visible in the viewport), which is good, but it currently attaches at that line, thus pushing down the function by one line, which causes a one-line jump when scrolling horizontally. - [ ] diff view: labels are currently attached also when scrolling the view port horizontally (such that e.g. a function name is not fully visible in the viewport), which is good, but it currently attaches at that line, thus pushing down the function by one line, which causes a one-line jump when scrolling horizontally.
- we should in this case attach the label above the function instead of on the same line that it's defined such that it's not pushed down. - we should in this case attach the label above the function instead of on the same line that it's defined such that it's not pushed down.
- [ ] diff view: github integration (code review) - add comments on a pr, add review comments at specific lines of files, request changes, approve pr, etc. - [ ] diff view: github integration (code review) - add comments on a pr, add review comments at specific lines of files, request changes, approve pr, etc.
+17 -6
View File
@@ -5,18 +5,29 @@ Vamp.configure do |config|
"python" => true, "python" => true,
) )
# fallback tab spaces
config.tab_spaces = 4
# language specific tab spaces
config.language_tab_spaces.merge!(
"rust" => 4,
"ruby" => 2,
"makefile" => nil,
)
# 0 (default): scroll only at viewport edges # 0 (default): scroll only at viewport edges
# nil: center; oversized integer margins also center # nil: center; oversized integer margins also center
config.scroll_distance = 7 config.scroll_distance = 7
config.scroll_step_lines = 3
config.highlight_trailing_whitespace = true config.highlight_trailing_whitespace = true
config.whitespace_scope = "leading" # or "all" (default) #config.whitespace_scope = "leading" # or "all" (default)
config.whitespace_characters = { #config.whitespace_characters = {
" " => "·", # " " => "·",
"\t" => "→", # "\t" => "→",
"\n" => "↵", # "\n" => "↵",
} #}
# "bar" (default), "symbols", "line_numbers", "none" # "bar" (default), "symbols", "line_numbers", "none"
config.git_gutter = "bar" config.git_gutter = "bar"
+1 -1
View File
@@ -84,7 +84,7 @@ Resolution exposes its projected comparison through the same active DiffState an
`Editor::scroll_distance()` supplies the session-wide optional edge margin. The TUI applies it to document/diff visual rows and unwrapped text columns, and retains independent picker offsets across navigation frames. Picker windows exclude their fixed chrome and align whole multi-row entries. File projections carry a monotonic viewport revision, so catalog/filter/expansion changes invalidate picker identity without hashing the entire catalog on every redraw. Metadata-only status changes do not move the viewport. Explicit LSP/wrap-toggle anchors and detached wheel views retain precedence. `Editor::scroll_distance()` supplies the session-wide optional edge margin. The TUI applies it to document/diff visual rows and unwrapped text columns, and retains independent picker offsets across navigation frames. Picker windows exclude their fixed chrome and align whole multi-row entries. File projections carry a monotonic viewport revision, so catalog/filter/expansion changes invalidate picker identity without hashing the entire catalog on every redraw. Metadata-only status changes do not move the viewport. Explicit LSP/wrap-toggle anchors and detached wheel views retain precedence.
Keyboard viewport actions queue per-view row and half-page counts without moving carets. `Editor::take_viewport_scroll(height)` resolves half-page counts using the session-wide `scroll_half_page_lines` preference, or half the focused viewport when unset, and saturates multiplication before the frontend clamps movement to content bounds. Ordinary, diff, and conflict-history rendering share this resolution. Keyboard viewport actions queue per-view row and configured-step counts without moving carets. `Editor::take_viewport_scroll(height)` resolves step counts using the session-wide `scroll_step_lines` preference, or half the focused viewport when unset, and saturates multiplication before the frontend clamps movement to content bounds. Ordinary, diff, and conflict-history rendering share this resolution.
Mouse wheel input uses the last rendered document/panel rectangles for hit-testing. Document offsets and explorer scroll state remain frontend-owned; three-row wheel steps never become document cursor motions. Manual document views persist until logical caret-reveal, caret/selection, mode, revision, or buffer changes; offscreen primary and browsing-selection cursors are not placed. Hover wheel input reuses the existing editor-owned popup scrolling. Mouse wheel input uses the last rendered document/panel rectangles for hit-testing. Document offsets and explorer scroll state remain frontend-owned; three-row wheel steps never become document cursor motions. Manual document views persist until logical caret-reveal, caret/selection, mode, revision, or buffer changes; offscreen primary and browsing-selection cursors are not placed. Hover wheel input reuses the existing editor-owned popup scrolling.
+4 -4
View File
@@ -16,7 +16,7 @@ Vamp.configure do |config|
config.git_blame = true # false disables current-line attribution config.git_blame = true # false disables current-line attribution
config.git_gutter = "bar" # or "symbols", "line_numbers", "none" config.git_gutter = "bar" # or "symbols", "line_numbers", "none"
config.scroll_distance = 0 # nonnegative edge distance, or nil to center config.scroll_distance = 0 # nonnegative edge distance, or nil to center
config.scroll_half_page_lines = nil # Ctrl+u/d: half viewport, or a positive screen-row count config.scroll_step_lines = nil # Ctrl+u/d: half viewport, or a positive screen-row count
config.highlight_trailing_whitespace = false # true marks line-end whitespace in red config.highlight_trailing_whitespace = false # true marks line-end whitespace in red
config.whitespace_characters = {} # opt in: { " " => "·", "\t" => "→", "\n" => "↵" } config.whitespace_characters = {} # opt in: { " " => "·", "\t" => "→", "\n" => "↵" }
config.whitespace_scope = "all" # or "leading" for indentation only config.whitespace_scope = "all" # or "leading" for indentation only
@@ -58,7 +58,7 @@ Plugin paths resolve relative to the config file, not the shell's working direct
The setting applies to ordinary and historical buffers, split/unified diff panes, file pickers and explorers, command/buffer palettes, completion lists, and revision selectors. Wrapped text measures visual rows, not source lines; picker distances use result rows, excluding borders, search fields, and help. Manual mouse-wheel scrolling remains detached until keyboard navigation resumes. Only `nil` or integers from `0` through the platform's Rust `usize` maximum (`18446744073709551615` on 64-bit systems) are accepted; other values reject the configuration. The setting applies to ordinary and historical buffers, split/unified diff panes, file pickers and explorers, command/buffer palettes, completion lists, and revision selectors. Wrapped text measures visual rows, not source lines; picker distances use result rows, excluding borders, search fields, and help. Manual mouse-wheel scrolling remains detached until keyboard navigation resumes. Only `nil` or integers from `0` through the platform's Rust `usize` maximum (`18446744073709551615` on 64-bit systems) are accepted; other values reject the configuration.
`config.scroll_half_page_lines` controls Normal/Visual Ctrl+u/Ctrl+d (`core.scroll_half_up` / `core.scroll_half_down`). The default `nil` scrolls half the focused text viewport, rounded down with a minimum of one screen row. A positive integer, such as `config.scroll_half_page_lines = 3`, fixes the distance in screen rows, including wrapped continuations and diff display rows. Counts multiply that distance; `2` then Ctrl+d scrolls six rows with this example. This session-wide setting applies to ordinary, historical, diff, and conflict text panes without moving carets or selections. It does not change the cursor-edge margin, one-row scroll actions, mouse wheel, or picker navigation. Only `nil` or integers from `1` through the platform's Rust `usize` maximum are accepted; other values reject the configuration. Distances and count multiplication saturate at Rust's `isize` limit before the viewport clamps to content bounds. `config.scroll_step_lines` controls Normal/Visual Ctrl+u/Ctrl+d (`core.scroll_step_up` / `core.scroll_step_down`). The default `nil` scrolls half the focused text viewport, rounded down with a minimum of one screen row. A positive integer, such as `config.scroll_step_lines = 3`, fixes the distance in screen rows, including wrapped continuations and diff display rows. Counts multiply that distance; `2` then Ctrl+d scrolls six rows with this example. This session-wide setting applies to ordinary, historical, diff, and conflict text panes without moving carets or selections. It does not change the cursor-edge margin, one-row scroll actions, mouse wheel, or picker navigation. Only `nil` or integers from `1` through the platform's Rust `usize` maximum are accepted; other values reject the configuration. Distances and count multiplication saturate at Rust's `isize` limit before the viewport clamps to content bounds.
`config.highlight_trailing_whitespace` defaults to `false`. Set it to `true` to give trailing whitespace a red background in ordinary and historical buffers and both sides of split/unified diffs, including wrapped and horizontally scrolled text. Spaces, expanded tab cells, and Unicode whitespace at the end of a source line are highlighted; whitespace-only lines are included, but empty lines and padding beyond the text are not. Wrapping does not make interior whitespace trailing. Search, selections, and secondary carets retain their background precedence; diagnostic underlines remain visible. This changes display only, not file contents. Only literal Ruby booleans are accepted; other values reject the configuration. `config.highlight_trailing_whitespace` defaults to `false`. Set it to `true` to give trailing whitespace a red background in ordinary and historical buffers and both sides of split/unified diffs, including wrapped and horizontally scrolled text. Spaces, expanded tab cells, and Unicode whitespace at the end of a source line are highlighted; whitespace-only lines are included, but empty lines and padding beyond the text are not. Wrapping does not make interior whitespace trailing. Search, selections, and secondary carets retain their background precedence; diagnostic underlines remain visible. This changes display only, not file contents. Only literal Ruby booleans are accepted; other values reject the configuration.
@@ -211,7 +211,7 @@ Normal `C` / `D` / `Y` change/delete/copy the cursor-to-line-end suffix through
`f<char>` / `F<char>` seeks forward/backward on the current line in Normal and Visual, with counts such as `2fx`. Rebind `config.core.seek_forward` / `config.core.seek_backward` to a single key or leader sequence; the action then waits for a literal target character. For example, `config.bind "s", config.core.seek_forward` makes `2sx` seek the second following `x`. Pass `mode: config.modes.visual` to install a Visual route. These native-only actions do not have Ex commands. `f<char>` / `F<char>` seeks forward/backward on the current line in Normal and Visual, with counts such as `2fx`. Rebind `config.core.seek_forward` / `config.core.seek_backward` to a single key or leader sequence; the action then waits for a literal target character. For example, `config.bind "s", config.core.seek_forward` makes `2sx` seek the second following `x`. Pass `mode: config.modes.visual` to install a Visual route. These native-only actions do not have Ex commands.
Normal and Visual Ctrl+u/Ctrl+d invoke `config.core.scroll_half_up` / `config.core.scroll_half_down` without moving carets or selection endpoints. Counts multiply the configured `scroll_half_page_lines` distance (half a viewport by default). Bind `config.core.scroll_up` / `config.core.scroll_down` for one screen row per count, for example `config.bind "ctrl+e", config.core.scroll_down` to replace clone-below in Normal. Pass `mode: config.modes.visual` to rebind Visual too. These actions work in ordinary and diff text panes, use wrapped screen rows, and have no Ex commands. Cursor navigation or editing resumes caret following. Normal and Visual Ctrl+u/Ctrl+d invoke `config.core.scroll_step_up` / `config.core.scroll_step_down` without moving carets or selection endpoints. Counts multiply the configured `scroll_step_lines` distance (half a viewport by default). Bind `config.core.scroll_up` / `config.core.scroll_down` for one screen row per count, for example `config.bind "ctrl+e", config.core.scroll_down` to replace clone-below in Normal. Pass `mode: config.modes.visual` to rebind Visual too. These actions work in ordinary and diff text panes, use wrapped screen rows, and have no Ex commands. Cursor navigation or editing resumes caret following.
## Action reference ## Action reference
@@ -243,7 +243,7 @@ Normal and Visual Ctrl+u/Ctrl+d invoke `config.core.scroll_half_up` / `config.co
| Normal, Visual | `file_picker`, `file_explorer` | | Normal, Visual | `file_picker`, `file_explorer` |
| Normal, Visual | `document_start`, `document_end`, `go_to_line` | | Normal, Visual | `document_start`, `document_end`, `go_to_line` |
| Normal, Visual | `seek_forward`, `seek_backward` | | Normal, Visual | `seek_forward`, `seek_backward` |
| Normal, Visual | `scroll_up`, `scroll_down`, `scroll_half_up`, `scroll_half_down` | | Normal, Visual | `scroll_up`, `scroll_down`, `scroll_step_up`, `scroll_step_down` |
| Normal, Insert, Visual | `line_start`, `line_end` | | Normal, Insert, Visual | `line_start`, `line_end` |
| Normal, Insert, Visual, Tree | `left`, `right` | | Normal, Insert, Visual, Tree | `left`, `right` |
| Normal, Insert, Visual, Selection, Tree | `down`, `up` | | Normal, Insert, Visual, Selection, Tree | `down`, `up` |
+1 -1
View File
@@ -76,7 +76,7 @@ Diff styling checks cover independent snapshot language/shebang/injection detect
- Undo uses full-buffer snapshots and movement scans the current line. This favors straightforward correctness over large-file performance. - Undo uses full-buffer snapshots and movement scans the current line. This favors straightforward correctness over large-file performance.
- Rapid-wheel verification must replay bursts against the running editor and measure arrival at the final viewport after input stops, not just individual frame duration. Cheap frames can still accumulate a visible backlog if each queued report requires its own redraw. - Rapid-wheel verification must replay bursts against the running editor and measure arrival at the final viewport after input stops, not just individual frame duration. Cheap frames can still accumulate a visible backlog if each queued report requires its own redraw.
- Keyboard viewport scrolling coalesces up to 64 consecutive inputs for the same resolved scroll action into one redraw. Reversals, changes between row/half-page scrolling, and cursor motions end the batch so boundary clamping and caret reveal happen before the next action. Burst checks must include direction changes at the start/end of the buffer, not just throughput. - Keyboard viewport scrolling coalesces up to 64 consecutive inputs for the same resolved scroll action into one redraw. Reversals, changes between row/configured-step scrolling, and cursor motions end the batch so boundary clamping and caret reveal happen before the next action. Burst checks must include direction changes at the start/end of the buffer, not just throughput.
- PTY throughput alone cannot prove terminal presentation stays smooth: capture the editor and terminal while the user reproduces a pause. Synchronized-output commands must not reach the terminal during frame computation, even when individual frames and queued-input catch-up appear fast. - PTY throughput alone cannot prove terminal presentation stays smooth: capture the editor and terminal while the user reproduces a pause. Synchronized-output commands must not reach the terminal during frame computation, even when individual frames and queued-input catch-up appear fast.
- Annotation coordinate validation fast-paths ASCII grapheme lengths using byte lengths with CRLF pair accounting, retaining Unicode segmentation for non-ASCII text. Validation remains atomic and still checks every token; large Unicode buffers or diagnostic batches can require foreground work. - Annotation coordinate validation fast-paths ASCII grapheme lengths using byte lengths with CRLF pair accounting, retaining Unicode segmentation for non-ASCII text. Validation remains atomic and still checks every token; large Unicode buffers or diagnostic batches can require foreground work.
- Legacy synchronous plugin invocation may pause input for up to 500 ms before a host timeout. Async plugins opt into background replies; pending requests and transport queues are bounded. Config/plugin startup has a 2-second deadline per host. Buffer context/document snapshots must fit within a 4 MiB JSON frame; a too-large frame disables that plugin, not editing. - Legacy synchronous plugin invocation may pause input for up to 500 ms before a host timeout. Async plugins opt into background replies; pending requests and transport queues are bounded. Config/plugin startup has a 2-second deadline per host. Buffer context/document snapshots must fit within a 4 MiB JSON frame; a too-large frame disables that plugin, not editing.
+1 -1
View File
@@ -31,7 +31,7 @@ Indicators refresh asynchronously. Typing and undo/redo retain the last complete
| Normal, Visual | `<line>g` jumps immediately to that one-based source line at first nonblank (`42g`); clamps at EOF and extends Visual selections | | Normal, Visual | `<line>g` jumps immediately to that one-based source line at first nonblank (`42g`); clamps at EOF and extends Visual selections |
| Normal | Ctrl+o goes to an older jump location; Ctrl+i/Tab goes forward; counts select multiple locations | | Normal | Ctrl+o goes to an older jump location; Ctrl+i/Tab goes forward; counts select multiple locations |
| Normal, Visual | Decimal prefixes repeat cursor/word/search motions: `5j`, `3k`, `2w`, `4e`, `2n`; Esc cancels a pending count | | Normal, Visual | Decimal prefixes repeat cursor/word/search motions: `5j`, `3k`, `2w`, `4e`, `2n`; Esc cancels a pending count |
| Normal, Visual | Ctrl+u/Ctrl+d scroll up/down without moving carets or selections; half a viewport by default, configurable with `config.scroll_half_page_lines`; counts multiply the distance | | Normal, Visual | Ctrl+u/Ctrl+d scroll up/down without moving carets or selections; half a viewport by default, configurable with `config.scroll_step_lines`; counts multiply the distance |
| Normal | `J` (Shift+j) joins each targeted line with the next line | | Normal | `J` (Shift+j) joins each targeted line with the next line |
| Normal | `x`/Delete delete; `u` undo; `U` (Shift+u) redo; `:` enter command mode | | Normal | `x`/Delete delete; `u` undo; `U` (Shift+u) redo; `:` enter command mode |
| Normal | `yy` copies whole lines without moving; `dd` deletes them; `cc` changes them and enters Insert; counts work (`3yy`, `2dd`, `2cc`) | | Normal | `yy` copies whole lines without moving; `dd` deletes them; `cc` changes them and enters Insert; counts work (`3yy`, `2dd`, `2cc`) |
+1 -1
View File
@@ -6,7 +6,7 @@ Vamp.configure do |config|
config.clipboard_mode = "system" # "separate" keeps ordinary y/d/c/p/P internal config.clipboard_mode = "system" # "separate" keeps ordinary y/d/c/p/P internal
config.git_blame = true # false disables current-line inline blame config.git_blame = true # false disables current-line inline blame
config.scroll_distance = 0 # nonnegative edge distance, or nil to center config.scroll_distance = 0 # nonnegative edge distance, or nil to center
config.scroll_half_page_lines = nil # Ctrl+u/d: half viewport, or a positive screen-row count config.scroll_step_lines = nil # Ctrl+u/d: half viewport, or a positive screen-row count
config.highlight_trailing_whitespace = false # true marks line-end whitespace in red config.highlight_trailing_whitespace = false # true marks line-end whitespace in red
config.whitespace_characters = {} # opt in: { " " => "·", "\t" => "→", "\n" => "↵" } config.whitespace_characters = {} # opt in: { " " => "·", "\t" => "→", "\n" => "↵" }
config.whitespace_scope = "all" # or "leading" for indentation only; newline markers remain independent config.whitespace_scope = "all" # or "leading" for indentation only; newline markers remain independent
+24 -24
View File
@@ -644,13 +644,13 @@ core_actions![
&[Mode::Normal, Mode::Visual] &[Mode::Normal, Mode::Visual]
), ),
( (
"core.scroll_half_up", "core.scroll_step_up",
Action::ScrollHalfUp, Action::ScrollStepUp,
&[Mode::Normal, Mode::Visual] &[Mode::Normal, Mode::Visual]
), ),
( (
"core.scroll_half_down", "core.scroll_step_down",
Action::ScrollHalfDown, Action::ScrollStepDown,
&[Mode::Normal, Mode::Visual] &[Mode::Normal, Mode::Visual]
), ),
( (
@@ -1342,8 +1342,8 @@ fn default_bindings() -> HashMap<Mode, HashMap<Input, Binding>> {
for (key, action) in [ for (key, action) in [
(Input::Ctrl('k'), CloneAbove), (Input::Ctrl('k'), CloneAbove),
(Input::Ctrl('e'), CloneBelow), (Input::Ctrl('e'), CloneBelow),
(Input::Ctrl('u'), ScrollHalfUp), (Input::Ctrl('u'), ScrollStepUp),
(Input::Ctrl('d'), ScrollHalfDown), (Input::Ctrl('d'), ScrollStepDown),
(Input::Escape, Normal), (Input::Escape, Normal),
] { ] {
bindings bindings
@@ -2009,7 +2009,7 @@ pub struct Editor {
popup: PopupConfig, popup: PopupConfig,
labels: BTreeMap<String, String>, labels: BTreeMap<String, String>,
scroll_distance: Option<usize>, scroll_distance: Option<usize>,
scroll_half_page_lines: Option<NonZeroUsize>, scroll_step_lines: Option<NonZeroUsize>,
highlight_trailing_whitespace: bool, highlight_trailing_whitespace: bool,
whitespace_characters: BTreeMap<char, char>, whitespace_characters: BTreeMap<char, char>,
whitespace_scope: WhitespaceScope, whitespace_scope: WhitespaceScope,
@@ -2120,7 +2120,7 @@ impl Editor {
popup: PopupConfig::default(), popup: PopupConfig::default(),
labels: BTreeMap::new(), labels: BTreeMap::new(),
scroll_distance: Some(0), scroll_distance: Some(0),
scroll_half_page_lines: None, scroll_step_lines: None,
highlight_trailing_whitespace: false, highlight_trailing_whitespace: false,
whitespace_characters: BTreeMap::new(), whitespace_characters: BTreeMap::new(),
whitespace_scope: WhitespaceScope::All, whitespace_scope: WhitespaceScope::All,
@@ -4118,8 +4118,8 @@ impl Editor {
self.scroll_distance = distance; self.scroll_distance = distance;
} }
pub(crate) fn set_scroll_half_page_lines(&mut self, lines: Option<NonZeroUsize>) { pub(crate) fn set_scroll_step_lines(&mut self, lines: Option<NonZeroUsize>) {
self.scroll_half_page_lines = lines; self.scroll_step_lines = lines;
} }
pub(crate) fn highlight_trailing_whitespace(&self) -> bool { pub(crate) fn highlight_trailing_whitespace(&self) -> bool {
@@ -4465,8 +4465,8 @@ impl Editor {
| Action::Up | Action::Up
| Action::ScrollUp | Action::ScrollUp
| Action::ScrollDown | Action::ScrollDown
| Action::ScrollHalfUp | Action::ScrollStepUp
| Action::ScrollHalfDown | Action::ScrollStepDown
| Action::LineStart | Action::LineStart
| Action::LineEnd | Action::LineEnd
| Action::DocumentStart | Action::DocumentStart
@@ -4487,8 +4487,8 @@ impl Editor {
Binding::Action( Binding::Action(
action @ (Action::ScrollUp action @ (Action::ScrollUp
| Action::ScrollDown | Action::ScrollDown
| Action::ScrollHalfUp | Action::ScrollStepUp
| Action::ScrollHalfDown), | Action::ScrollStepDown),
) => Some(action.entry().name), ) => Some(action.entry().name),
_ => None, _ => None,
} }
@@ -5834,12 +5834,12 @@ impl Editor {
} }
pub(crate) fn take_viewport_scroll(&self, height: usize) -> isize { pub(crate) fn take_viewport_scroll(&self, height: usize) -> isize {
let (rows, halves) = self.active.view.viewport_scroll.take(); let (rows, steps) = self.active.view.viewport_scroll.take();
let lines = self let lines = self
.scroll_half_page_lines .scroll_step_lines
.map_or((height / 2).max(1), NonZeroUsize::get); .map_or((height / 2).max(1), NonZeroUsize::get);
let lines = isize::try_from(lines).unwrap_or(isize::MAX); let lines = isize::try_from(lines).unwrap_or(isize::MAX);
rows.saturating_add(halves.saturating_mul(lines)) rows.saturating_add(steps.saturating_mul(lines))
} }
fn reveal_caret(&mut self) { fn reveal_caret(&mut self) {
@@ -8305,22 +8305,22 @@ impl Editor {
Action::JumpForward => self.traverse_jumps(true, count), Action::JumpForward => self.traverse_jumps(true, count),
Action::ScrollUp Action::ScrollUp
| Action::ScrollDown | Action::ScrollDown
| Action::ScrollHalfUp | Action::ScrollStepUp
| Action::ScrollHalfDown => { | Action::ScrollStepDown => {
if self.document_actions_available() { if self.document_actions_available() {
let amount = isize::try_from(count).unwrap_or(isize::MAX); let amount = isize::try_from(count).unwrap_or(isize::MAX);
let amount = if matches!(action, Action::ScrollUp | Action::ScrollHalfUp) { let amount = if matches!(action, Action::ScrollUp | Action::ScrollStepUp) {
-amount -amount
} else { } else {
amount amount
}; };
let (mut rows, mut halves) = self.active.view.viewport_scroll.get(); let (mut rows, mut steps) = self.active.view.viewport_scroll.get();
if matches!(action, Action::ScrollHalfUp | Action::ScrollHalfDown) { if matches!(action, Action::ScrollStepUp | Action::ScrollStepDown) {
halves = halves.saturating_add(amount); steps = steps.saturating_add(amount);
} else { } else {
rows = rows.saturating_add(amount); rows = rows.saturating_add(amount);
} }
self.active.view.viewport_scroll.set((rows, halves)); self.active.view.viewport_scroll.set((rows, steps));
if let Some(diff) = self.interaction_diff_mut() { if let Some(diff) = self.interaction_diff_mut() {
diff.follow_caret = false; diff.follow_caret = false;
} }
+24 -10
View File
@@ -46,7 +46,7 @@ pub struct Config {
pub popup: PopupConfig, pub popup: PopupConfig,
pub labels: BTreeMap<String, String>, pub labels: BTreeMap<String, String>,
pub scroll_distance: Option<usize>, pub scroll_distance: Option<usize>,
pub scroll_half_page_lines: Option<NonZeroUsize>, pub scroll_step_lines: Option<NonZeroUsize>,
pub highlight_trailing_whitespace: bool, pub highlight_trailing_whitespace: bool,
pub whitespace_characters: BTreeMap<char, char>, pub whitespace_characters: BTreeMap<char, char>,
pub whitespace_scope: crate::editor::WhitespaceScope, pub whitespace_scope: crate::editor::WhitespaceScope,
@@ -98,7 +98,7 @@ impl Default for Config {
popup: PopupConfig::default(), popup: PopupConfig::default(),
labels: BTreeMap::new(), labels: BTreeMap::new(),
scroll_distance: Some(0), scroll_distance: Some(0),
scroll_half_page_lines: None, scroll_step_lines: None,
highlight_trailing_whitespace: false, highlight_trailing_whitespace: false,
whitespace_characters: BTreeMap::new(), whitespace_characters: BTreeMap::new(),
whitespace_scope: crate::editor::WhitespaceScope::All, whitespace_scope: crate::editor::WhitespaceScope::All,
@@ -137,7 +137,7 @@ pub fn load_config(path: &Path) -> Result<Config, String> {
popup, popup,
labels, labels,
scroll_distance, scroll_distance,
scroll_half_page_lines, scroll_step_lines,
highlight_trailing_whitespace, highlight_trailing_whitespace,
whitespace_characters, whitespace_characters,
whitespace_scope, whitespace_scope,
@@ -177,7 +177,7 @@ pub fn load_config(path: &Path) -> Result<Config, String> {
config.popup = popup; config.popup = popup;
config.labels = labels; config.labels = labels;
config.scroll_distance = scroll_distance; config.scroll_distance = scroll_distance;
config.scroll_half_page_lines = scroll_half_page_lines; config.scroll_step_lines = scroll_step_lines;
config.highlight_trailing_whitespace = highlight_trailing_whitespace; config.highlight_trailing_whitespace = highlight_trailing_whitespace;
config.whitespace_characters = whitespace_characters; config.whitespace_characters = whitespace_characters;
config.whitespace_scope = whitespace_scope; config.whitespace_scope = whitespace_scope;
@@ -516,7 +516,7 @@ struct ConfigFrame {
labels: BTreeMap<String, String>, labels: BTreeMap<String, String>,
#[serde(deserialize_with = "scroll_distance")] #[serde(deserialize_with = "scroll_distance")]
scroll_distance: Option<usize>, scroll_distance: Option<usize>,
scroll_half_page_lines: Option<NonZeroUsize>, scroll_step_lines: Option<NonZeroUsize>,
highlight_trailing_whitespace: bool, highlight_trailing_whitespace: bool,
#[serde(deserialize_with = "whitespace_characters")] #[serde(deserialize_with = "whitespace_characters")]
whitespace_characters: BTreeMap<char, char>, whitespace_characters: BTreeMap<char, char>,
@@ -1935,7 +1935,7 @@ end
} }
#[test] #[test]
fn config_scroll_half_page_lines_controls_counted_scrolling_and_rejects_invalid_values() { fn config_scroll_step_lines_controls_counted_scrolling_and_rejects_invalid_values() {
let fixture = Fixture::new(); let fixture = Fixture::new();
for (value, expected) in [ for (value, expected) in [
("nil", 30), ("nil", 30),
@@ -1945,17 +1945,31 @@ end
] { ] {
let path = fixture.file( let path = fixture.file(
"config.rb", "config.rb",
&format!("Vamp.configure {{ |config| config.scroll_half_page_lines = {value} }}"), &format!(
"Vamp.configure do |config|
config.scroll_step_lines = {value}
config.bind \"alt+j\", config.core.scroll_step_down
config.bind \"alt+k\", config.core.scroll_step_up
end"
),
); );
let mut session = let mut session =
crate::session::EditorSession::open(None, vec![], Some(&path)).unwrap(); crate::session::EditorSession::open(None, vec![], Some(&path)).unwrap();
session.handle(crate::editor::Input::Char('3')); session.handle(crate::editor::Input::Char('3'));
session.handle(crate::editor::Input::Ctrl('d')); session.handle(crate::editor::Input::Alt('j'));
assert_eq!( assert_eq!(
session.editor().take_viewport_scroll(20), session.editor().take_viewport_scroll(20),
expected, expected,
"{value}" "{value}"
); );
session.handle(crate::editor::Input::Char('3'));
session.handle(crate::editor::Input::Alt('k'));
let up = if expected == isize::MAX {
isize::MIN
} else {
-expected
};
assert_eq!(session.editor().take_viewport_scroll(20), up, "{value}");
} }
for value in [ for value in [
"0", "0",
@@ -1968,11 +1982,11 @@ end
] { ] {
let path = fixture.file( let path = fixture.file(
"config.rb", "config.rb",
&format!("Vamp.configure {{ |config| config.scroll_half_page_lines = {value} }}"), &format!("Vamp.configure {{ |config| config.scroll_step_lines = {value} }}"),
); );
let error = load_config(&path).unwrap_err(); let error = load_config(&path).unwrap_err();
assert!( assert!(
error.contains("scroll_half_page_lines") && error.contains("must be"), error.contains("scroll_step_lines") && error.contains("must be"),
"{value}: {error}" "{value}: {error}"
); );
} }
+4 -4
View File
@@ -145,7 +145,7 @@ module Vamp
@popup = PopupConfig.new @popup = PopupConfig.new
@labels = {} @labels = {}
@scroll_distance = 0 @scroll_distance = 0
@scroll_half_page_lines = nil @scroll_step_lines = nil
@highlight_trailing_whitespace = false @highlight_trailing_whitespace = false
@whitespace_characters = {} @whitespace_characters = {}
@whitespace_scope = "all" @whitespace_scope = "all"
@@ -232,8 +232,8 @@ module Vamp
# @param lines [Integer, nil] positive screen rows, or nil for half the viewport # @param lines [Integer, nil] positive screen rows, or nil for half the viewport
# @return [Integer, nil] # @return [Integer, nil]
def scroll_half_page_lines=(lines) def scroll_step_lines=(lines)
@scroll_half_page_lines = lines.nil? ? nil : diff_line_count!(lines, "scroll_half_page_lines", 1) @scroll_step_lines = lines.nil? ? nil : diff_line_count!(lines, "scroll_step_lines", 1)
end end
# @param enabled [Boolean] # @param enabled [Boolean]
@@ -350,7 +350,7 @@ module Vamp
"git_blame" => @git_blame, "git_blame" => @git_blame,
"git_gutter" => @git_gutter, "git_gutter" => @git_gutter,
"scroll_distance" => @scroll_distance, "scroll_distance" => @scroll_distance,
"scroll_half_page_lines" => @scroll_half_page_lines, "scroll_step_lines" => @scroll_step_lines,
"highlight_trailing_whitespace" => @highlight_trailing_whitespace, "highlight_trailing_whitespace" => @highlight_trailing_whitespace,
"whitespace_characters" => @whitespace_characters, "whitespace_characters" => @whitespace_characters,
"whitespace_scope" => @whitespace_scope, "whitespace_scope" => @whitespace_scope,
+1 -1
View File
@@ -512,7 +512,7 @@ impl EditorSession {
editor.set_git_blame(config.git_blame); editor.set_git_blame(config.git_blame);
editor.set_gutter_style(config.git_gutter); editor.set_gutter_style(config.git_gutter);
editor.set_scroll_distance(config.scroll_distance); editor.set_scroll_distance(config.scroll_distance);
editor.set_scroll_half_page_lines(config.scroll_half_page_lines); editor.set_scroll_step_lines(config.scroll_step_lines);
editor.set_highlight_trailing_whitespace(config.highlight_trailing_whitespace); editor.set_highlight_trailing_whitespace(config.highlight_trailing_whitespace);
editor.set_whitespace_characters(config.whitespace_characters); editor.set_whitespace_characters(config.whitespace_characters);
editor.set_whitespace_scope(config.whitespace_scope); editor.set_whitespace_scope(config.whitespace_scope);
+1 -1
View File
@@ -12617,7 +12617,7 @@ end
editor.handle(Input::Ctrl('u')); editor.handle(Input::Ctrl('u'));
render(&mut terminal, &mut views, &mut editor); render(&mut terminal, &mut views, &mut editor);
assert_eq!(offset(&views), initial); assert_eq!(offset(&views), initial);
editor.set_scroll_half_page_lines(std::num::NonZeroUsize::new(3)); editor.set_scroll_step_lines(std::num::NonZeroUsize::new(3));
editor.handle(Input::Char('2')); editor.handle(Input::Char('2'));
editor.handle(Input::Ctrl('d')); editor.handle(Input::Ctrl('d'));
render(&mut terminal, &mut views, &mut editor); render(&mut terminal, &mut views, &mut editor);