feat: configure half-page viewport scroll distance

This commit is contained in:
Valdemar Lorenzen
2026-10-16 10:25:27 +02:00
parent 5a8c37b2f2
commit 284ddd3713
11 changed files with 96 additions and 7 deletions
+3 -1
View File
@@ -11,7 +11,9 @@
- [x] diff view: annotate folder with checkmark if all items under given folder has been checked AND the folder is collapsed. - [x] diff view: annotate folder with checkmark if all items under given folder has been checked AND the folder is collapsed.
- [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`).
- [ ] scroll viewport action (`ctrl+u|d`) scroll distance should be configurable. - [x] scroll viewport action (`ctrl+u|d`) scroll distance should be configurable.
- [ ] 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.
- [ ] 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.
- [ ] diff view: local review comments (same features as for github reviews) that allows review of local changes to deliver specific reviews to ai agents used for coding. - [ ] diff view: local review comments (same features as for github reviews) that allows review of local changes to deliver specific reviews to ai agents used for coding.
- design question: would this require a server that the agent can interact with, or would local review files be fine? don't want to use server client stuff, if it would be easier and better to just use local files. - design question: would this require a server that the agent can interact with, or would local review files be fine? don't want to use server client stuff, if it would be easier and better to just use local files.
+2
View File
@@ -84,6 +84,8 @@ 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.
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.
Already-ready mouse reports share a redraw: at most 64 reports are applied in their original order before painting, without dropping or summing away wheel steps. Readiness checks do not wait. The first non-mouse event is retained for the next iteration after rendering, so keys, paste, and resizes cannot reuse geometry from before their layout changes. The batch bound keeps continuous mouse input from starving drawing or background polling. Already-ready mouse reports share a redraw: at most 64 reports are applied in their original order before painting, without dropping or summing away wheel steps. Readiness checks do not wait. The first non-mouse event is retained for the next iteration after rendering, so keys, paste, and resizes cannot reuse geometry from before their layout changes. The batch bound keeps continuous mouse input from starving drawing or background polling.
+4 -1
View File
@@ -16,6 +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.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
@@ -57,6 +58,8 @@ 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.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.
`config.whitespace_characters` defaults to `{}` (disabled). Assign a hash mapping literal whitespace characters to dim display markers, for example `{ " " => "·", "\t" => "→", "\u00a0" => "⍽", "\n" => "↵" }` for spaces, tabs, nonbreaking spaces, and line breaks. Unlisted characters keep their ordinary appearance. Keys and values must be single-character strings; keys must be whitespace with visible terminal width, tabs, or `"\n"`, and values must be printable one-cell characters. Carriage returns and other control characters, zero-width markers, and wide markers reject the configuration. Only indicator cells receive terminal dim styling; source-text colors, diagnostic underlines, and highlighted backgrounds remain intact. `config.whitespace_characters` defaults to `{}` (disabled). Assign a hash mapping literal whitespace characters to dim display markers, for example `{ " " => "·", "\t" => "→", "\u00a0" => "⍽", "\n" => "↵" }` for spaces, tabs, nonbreaking spaces, and line breaks. Unlisted characters keep their ordinary appearance. Keys and values must be single-character strings; keys must be whitespace with visible terminal width, tabs, or `"\n"`, and values must be printable one-cell characters. Carriage returns and other control characters, zero-width markers, and wide markers reject the configuration. Only indicator cells receive terminal dim styling; source-text colors, diagnostic underlines, and highlighted backgrounds remain intact.
@@ -208,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 half-viewport distance. 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_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.
## Action reference ## Action reference
+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 half a viewport up/down without moving carets or selections; 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_half_page_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
View File
@@ -6,6 +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.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
+12 -2
View File
@@ -13,7 +13,7 @@ use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::fmt; use std::fmt;
use std::fs::{self, OpenOptions}; use std::fs::{self, OpenOptions};
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use std::num::NonZeroU8; use std::num::{NonZeroU8, NonZeroUsize};
use std::ops::Range; use std::ops::Range;
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::fs::{MetadataExt, OpenOptionsExt}; use std::os::unix::fs::{MetadataExt, OpenOptionsExt};
@@ -2009,6 +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>,
highlight_trailing_whitespace: bool, highlight_trailing_whitespace: bool,
whitespace_characters: BTreeMap<char, char>, whitespace_characters: BTreeMap<char, char>,
whitespace_scope: WhitespaceScope, whitespace_scope: WhitespaceScope,
@@ -2119,6 +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,
highlight_trailing_whitespace: false, highlight_trailing_whitespace: false,
whitespace_characters: BTreeMap::new(), whitespace_characters: BTreeMap::new(),
whitespace_scope: WhitespaceScope::All, whitespace_scope: WhitespaceScope::All,
@@ -4116,6 +4118,10 @@ impl Editor {
self.scroll_distance = distance; self.scroll_distance = distance;
} }
pub(crate) fn set_scroll_half_page_lines(&mut self, lines: Option<NonZeroUsize>) {
self.scroll_half_page_lines = lines;
}
pub(crate) fn highlight_trailing_whitespace(&self) -> bool { pub(crate) fn highlight_trailing_whitespace(&self) -> bool {
self.highlight_trailing_whitespace self.highlight_trailing_whitespace
} }
@@ -5829,7 +5835,11 @@ 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, halves) = self.active.view.viewport_scroll.take();
rows.saturating_add(halves.saturating_mul((height / 2).max(1) as isize)) let lines = self
.scroll_half_page_lines
.map_or((height / 2).max(1), NonZeroUsize::get);
let lines = isize::try_from(lines).unwrap_or(isize::MAX);
rows.saturating_add(halves.saturating_mul(lines))
} }
fn reveal_caret(&mut self) { fn reveal_caret(&mut self) {
+1 -1
View File
@@ -28,7 +28,7 @@ Normal: i/a insert, I/A insert at first nonblank/end, o/O new line
_ splits above/below, | left/right; Option/Alt+h/j/k/l focuses neighboring panes _ splits above/below, | left/right; Option/Alt+h/j/k/l focuses neighboring panes
Ctrl+Option/Alt+h/l previous/next tab; :tab.new opens current buffer in another tab Ctrl+Option/Alt+h/l previous/next tab; :tab.new opens current buffer in another tab
Normal/Visual: prefix motions with counts (5j, 2w, 3n); Esc cancels count Normal/Visual: prefix motions with counts (5j, 2w, 3n); Esc cancels count
Normal/Visual: Ctrl+u/d scroll half a viewport up/down without moving carets; counts supported Normal/Visual: Ctrl+u/d scroll up/down without moving carets; configurable distance (default half viewport), counts supported
u undo, U redo, : command, / search, * search word, n/N next/previous match u undo, U redo, : command, / search, * search word, n/N next/previous match
yy copies whole lines; dd deletes them; cc changes them and enters Insert; counts: 3yy, 2dd, 2cc yy copies whole lines; dd deletes them; cc changes them and enters Insert; counts: 3yy, 2dd, 2cc
dw/db cut forward/backward words on the current line; cw/cb cut the same spans and enter Insert dw/db cut forward/backward words on the current line; cw/cb cut the same spans and enter Insert
+50 -1
View File
@@ -3,7 +3,7 @@ use crate::editor::{Context, Effect, Feature};
use crate::plugin::{DocumentEvent, PluginEvent, PluginUpdate, UpdateEffect}; use crate::plugin::{DocumentEvent, PluginEvent, PluginUpdate, UpdateEffect};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::io::{self, BufRead, BufReader, Write}; use std::io::{self, BufRead, BufReader, Write};
use std::num::NonZeroU8; use std::num::{NonZeroU8, NonZeroUsize};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio}; use std::process::{Child, Command, Stdio};
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, SyncSender}; use std::sync::mpsc::{self, Receiver, RecvTimeoutError, SyncSender};
@@ -46,6 +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 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,
@@ -97,6 +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,
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,
@@ -135,6 +137,7 @@ pub fn load_config(path: &Path) -> Result<Config, String> {
popup, popup,
labels, labels,
scroll_distance, scroll_distance,
scroll_half_page_lines,
highlight_trailing_whitespace, highlight_trailing_whitespace,
whitespace_characters, whitespace_characters,
whitespace_scope, whitespace_scope,
@@ -174,6 +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.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;
@@ -512,6 +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>,
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>,
@@ -1929,6 +1934,50 @@ end
} }
} }
#[test]
fn config_scroll_half_page_lines_controls_counted_scrolling_and_rejects_invalid_values() {
let fixture = Fixture::new();
for (value, expected) in [
("nil", 30),
("1", 3),
("7", 21),
("18446744073709551615", isize::MAX),
] {
let path = fixture.file(
"config.rb",
&format!("Vamp.configure {{ |config| config.scroll_half_page_lines = {value} }}"),
);
let mut session =
crate::session::EditorSession::open(None, vec![], Some(&path)).unwrap();
session.handle(crate::editor::Input::Char('3'));
session.handle(crate::editor::Input::Ctrl('d'));
assert_eq!(
session.editor().take_viewport_scroll(20),
expected,
"{value}"
);
}
for value in [
"0",
"-1",
"1.5",
"\"3\"",
"true",
"false",
"18446744073709551616",
] {
let path = fixture.file(
"config.rb",
&format!("Vamp.configure {{ |config| config.scroll_half_page_lines = {value} }}"),
);
let error = load_config(&path).unwrap_err();
assert!(
error.contains("scroll_half_page_lines") && error.contains("must be"),
"{value}: {error}"
);
}
}
#[test] #[test]
fn config_scroll_distance_accepts_nil_and_counts_and_rejects_invalid_values() { fn config_scroll_distance_accepts_nil_and_counts_and_rejects_invalid_values() {
let fixture = Fixture::new(); let fixture = Fixture::new();
+8
View File
@@ -145,6 +145,7 @@ module Vamp
@popup = PopupConfig.new @popup = PopupConfig.new
@labels = {} @labels = {}
@scroll_distance = 0 @scroll_distance = 0
@scroll_half_page_lines = nil
@highlight_trailing_whitespace = false @highlight_trailing_whitespace = false
@whitespace_characters = {} @whitespace_characters = {}
@whitespace_scope = "all" @whitespace_scope = "all"
@@ -229,6 +230,12 @@ module Vamp
@scroll_distance = distance.nil? ? nil : diff_line_count!(distance, "scroll_distance", 0) @scroll_distance = distance.nil? ? nil : diff_line_count!(distance, "scroll_distance", 0)
end end
# @param lines [Integer, nil] positive screen rows, or nil for half the viewport
# @return [Integer, nil]
def scroll_half_page_lines=(lines)
@scroll_half_page_lines = lines.nil? ? nil : diff_line_count!(lines, "scroll_half_page_lines", 1)
end
# @param enabled [Boolean] # @param enabled [Boolean]
# @return [Boolean] # @return [Boolean]
def highlight_trailing_whitespace=(enabled) def highlight_trailing_whitespace=(enabled)
@@ -343,6 +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,
"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
View File
@@ -512,6 +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_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);
+13
View File
@@ -12617,6 +12617,19 @@ 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.handle(Input::Char('2'));
editor.handle(Input::Ctrl('d'));
render(&mut terminal, &mut views, &mut editor);
if !wrapped {
assert_eq!(offset(&views), (initial.0 + 6, 0));
}
assert_eq!(editor.carets(), carets);
assert_eq!(editor.selections(), selections);
editor.handle(Input::Char('2'));
editor.handle(Input::Ctrl('u'));
render(&mut terminal, &mut views, &mut editor);
assert_eq!(offset(&views), initial);
if !visual { if !visual {
editor.handle(Input::Char('3')); editor.handle(Input::Char('3'));
editor.handle(Input::Alt('j')); editor.handle(Input::Alt('j'));