Remote control: scroll-window: Allow fractional scrolling since we now have pixel scroll

This commit is contained in:
Kovid Goyal 2026-01-22 13:05:22 +05:30
parent 82d3364e4a
commit 75ce50400e
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
5 changed files with 69 additions and 18 deletions

View file

@ -1317,6 +1317,9 @@ class Screen:
def scroll(self, amt: int, upwards: bool) -> bool:
pass
def fractional_scroll(self, amt: float) -> bool:
pass
def scroll_to_next_mark(self, mark: int = 0, backwards: bool = True) -> bool:
pass

View file

@ -16,7 +16,7 @@ class ScrollWindow(RemoteCommand):
amount+/list.scroll_amount: The amount to scroll, a two item list with the first item being \
either a number or the keywords, start and end. \
And the second item being either 'p' for pages or 'l' for lines or 'u'
for unscrolling by lines, or 'r' for scrolling ot prompt.
for unscrolling by lines, or 'r' for scrolling to prompt.
match/str: The window to scroll
'''
@ -26,7 +26,7 @@ class ScrollWindow(RemoteCommand):
' :italic:`SCROLL_AMOUNT` can be either the keywords :code:`start` or :code:`end` or an'
' argument of the form :italic:`<number>[unit][+-]`. :code:`unit` can be :code:`l` for lines, :code:`p` for pages,'
' :code:`u` for unscroll and :code:`r` for scroll to prompt. If unspecified, :code:`l` is the default.'
' For example, :code:`30` will scroll down 30 lines, :code:`2p-`'
' For example, :code:`30.5` will scroll down 30 and a half lines, :code:`2p-`'
' will scroll up 2 pages and :code:`0.5p` will scroll down half page.'
' :code:`3u` will *unscroll* by 3 lines, which means that 3 lines will move from the'
' scrollback buffer onto the top of the screen. :code:`1r-` will scroll to the previous prompt and :code:`1r` to the next prompt.'
@ -52,7 +52,7 @@ def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: Arg
prompt = 'r' in amt
mult = -1 if amt.endswith('-') and not unscroll else 1
q = float(amt.rstrip('+-plur'))
if not pages and not q.is_integer():
if (unscroll or prompt) and not q.is_integer():
self.fatal('The number must be an integer')
amount = q * mult, 'p' if pages else ('u' if unscroll else ('r' if prompt else 'l'))
@ -64,22 +64,23 @@ def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: Pa
for window in self.windows_for_match_payload(boss, window, payload_get):
if window:
if amt[0] in ('start', 'end'):
getattr(window, {'start': 'scroll_home'}.get(amt[0], 'scroll_end'))()
(window.scroll_home if amt[0] == 'start' else window.scroll_end)()
else:
amt, unit = amt
if unit == 'u':
window.screen.reverse_scroll(int(abs(amt)), True)
elif unit == 'r':
window.scroll_to_prompt(int(amt))
else:
unit = 'page' if unit == 'p' else 'line'
if unit == 'page' and not isinstance(amt, int) and not amt.is_integer():
amt = round(window.screen.lines * amt)
unit = 'line'
direction = 'up' if amt < 0 else 'down'
func = getattr(window, f'scroll_{unit}_{direction}')
for i in range(int(abs(amt))):
func()
match unit:
case 'u':
window.screen.reverse_scroll(int(abs(amt)), True)
case 'r':
window.scroll_to_prompt(int(amt))
case 'l':
window.scroll_fractional_lines(amt)
case 'p':
if not isinstance(amt, int) and not amt.is_integer():
amt = round(window.screen.lines * amt)
unit = 'line'
func = window.scroll_page_up if amt < 0 else window.scroll_page_down
for i in range(int(abs(amt))):
func()
return None

View file

@ -5066,6 +5066,45 @@ screen_history_scroll(Screen *self, int amt, bool upwards) {
return false;
}
static bool
screen_fractional_scroll(Screen *self, double amt) {
if (amt == 0) return false;
index_type before_scrolled_by = self->scrolled_by;
double before_pixels = self->pixel_scroll_offset_y;
double integral_part, fractional_part = modf(amt, &integral_part);
int lines = (int)integral_part;
double pixels = fractional_part * self->cell_size.height;
if (amt > 0) { // downwards
pixels = pixels > self->pixel_scroll_offset_y ? pixels - self->pixel_scroll_offset_y : 0;
self->pixel_scroll_offset_y = 0;
self->scrolled_by = self->scrolled_by > (unsigned)lines ? self->scrolled_by - lines : 0;
if (pixels > 0 && self->scrolled_by > 0) {
self->scrolled_by--; self->pixel_scroll_offset_y = self->cell_size.height - pixels;
}
} else {
self->pixel_scroll_offset_y -= pixels; // pixels is negative
if (self->pixel_scroll_offset_y >= self->cell_size.height) {
self->pixel_scroll_offset_y = 0; self->scrolled_by++;
}
self->scrolled_by = MIN(self->scrolled_by - lines, self->historybuf->count);
if (self->scrolled_by >= self->historybuf->count) self->pixel_scroll_offset_y = 0;
}
if (self->scrolled_by != before_scrolled_by || self->pixel_scroll_offset_y != before_pixels) {
dirty_scroll(self);
return true;
}
return false;
}
static PyObject*
fractional_scroll(Screen *self, PyObject *amt) {
double y;
if (PyFloat_Check(amt)) y = PyFloat_AS_DOUBLE(amt);
else if (PyLong_Check(amt)) y = PyLong_AsDouble(amt);
else { PyErr_SetString(PyExc_TypeError, "amt must be a float"); return NULL; }
return Py_NewRef(screen_fractional_scroll(self, y) ? Py_True : Py_False);
}
static PyObject*
scroll(Screen *self, PyObject *args) {
int amt, upwards;
@ -5939,6 +5978,7 @@ static PyMethodDef methods[] = {
MND(text_for_marked_url, METH_VARARGS)
MND(is_rectangle_select, METH_NOARGS)
MND(scroll, METH_VARARGS)
MND(fractional_scroll, METH_O)
MND(scroll_to_prompt, METH_VARARGS)
MND(set_last_visited_prompt, METH_VARARGS)
MND(send_escape_code_to_child, METH_VARARGS)

View file

@ -2234,6 +2234,13 @@ def pass_selection_to_program(self, *args: str) -> None:
def clear_selection(self) -> None:
self.screen.clear_selection()
def scroll_fractional_lines(self, amt: float) -> bool | None:
' Scroll fractionally, negative values are up and positive values are down '
if self.screen.is_main_linebuf():
self.screen.fractional_scroll(amt)
return None
return True
@ac('sc', 'Scroll up by one line when in main screen. To scroll by different amounts, you can map the remote_control scroll-window action.')
def scroll_line_up(self) -> bool | None:
if self.screen.is_main_linebuf():

View file

@ -25,7 +25,7 @@ func parse_scroll_amount(amt string) ([]any, error) {
if err != nil {
return ans, err
}
if !pages && q != float64(int(q)) {
if (unscroll || prompt) && q != float64(int(q)) {
return ans, fmt.Errorf("The number must be an integer")
}
ans[0] = q * mult