From 4caf65d4de591089c82ccf542a31ea5009a3c717 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Fri, 7 Jul 2017 12:21:10 +0300 Subject: [PATCH] Fix vertical-motion across the place where line-number width changes * src/indent.c (line_number_display_width): New function, refactored from line-number width calculations in vertical-motion. (Fvertical_motion): Call line_number_display_width when the width of line-number display is needed. (Fline_number_display_width): New defun. (syms_of_indent): Defsubr it. * doc/lispref/display.texi (Size of Displayed Text): Document line-number-display-width. * etc/NEWS: Mention line-number-display-width. * lisp/simple.el (last--line-number-width): New internal variable. (line-move-visual): Use it to adjust temporary-goal-column when line-number display changes its width. --- doc/lispref/display.texi | 17 ++++++++ etc/NEWS | 4 ++ lisp/simple.el | 21 ++++++++-- src/indent.c | 84 +++++++++++++++++++++++++++++----------- 4 files changed, 101 insertions(+), 25 deletions(-) diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 4de55fd3fb2..08b2b4671de 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -1980,6 +1980,23 @@ selected window. The value includes the line spacing of the line (@pxref{Line Height}). @end defun +When a buffer is displayed with line numbers (@pxref{Display Custom,,, +emacs, The GNU Emacs Manual}), it is sometimes useful to know the +width taken for displaying the line numbers. The following function +is for Lisp programs which need this information for layout +calculations. + +@defun line-number-display-width &optional pixelwise +This function returns the width used for displaying the line numbers +in the selected window. Optional argument @var{pixelwise}, if +non-@code{nil}, means return the value in pixels; otherwise the value +is returned in column units of the font defined for the +@code{line-number} face. If line numbers are not displayed in the +selected window, the value is zero. Use @code{with-selected-window} +(@pxref{Selecting Windows}) if you need this information about another +window. +@end defun + @node Line Height @section Line Height diff --git a/etc/NEWS b/etc/NEWS index b50c770c505..79eb3919e6d 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -420,6 +420,10 @@ line by putting the 'display-line-numbers-disable' text property or overlay property on the first character of that screen line. This is intended for add-on packages that need a finer control of the display. +Lisp programs that need to know how much screen estate is used up for +line-number display in a window can use the new function +'line-number-display-width'. + Linum mode and all similar packages are henceforth becoming obsolete. Users and developers are encouraged to switch to this new feature instead. diff --git a/lisp/simple.el b/lisp/simple.el index df664fc0503..5d1a6dbfb84 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -5931,6 +5931,10 @@ columns by which window is scrolled from left margin. When the `track-eol' feature is doing its job, the value is `most-positive-fixnum'.") +(defvar last--line-number-width 0 + "Last value of width used for displaying line numbers. +Used internally by `line-move-visual'.") + (defcustom line-move-ignore-invisible t "Non-nil means commands that move by lines ignore invisible newlines. When this option is non-nil, \\[next-line], \\[previous-line], \\[move-end-of-line], and \\[move-beginning-of-line] behave @@ -6201,6 +6205,7 @@ not vscroll." If NOERROR, don't signal an error if we can't move that many lines." (let ((opoint (point)) (hscroll (window-hscroll)) + (lnum-width (line-number-display-width t)) target-hscroll) ;; Check if the previous command was a line-motion command, or if ;; we were called from some other command. @@ -6208,9 +6213,19 @@ If NOERROR, don't signal an error if we can't move that many lines." (memq last-command `(next-line previous-line ,this-command))) ;; If so, there's no need to reset `temporary-goal-column', ;; but we may need to hscroll. - (if (or (/= (cdr temporary-goal-column) hscroll) - (> (cdr temporary-goal-column) 0)) - (setq target-hscroll (cdr temporary-goal-column))) + (progn + (if (or (/= (cdr temporary-goal-column) hscroll) + (> (cdr temporary-goal-column) 0)) + (setq target-hscroll (cdr temporary-goal-column))) + ;; Update the COLUMN part of temporary-goal-column if the + ;; line-number display changed its width since the last + ;; time. + (setq temporary-goal-column + (cons (+ (car temporary-goal-column) + (/ (float (- lnum-width last--line-number-width)) + (frame-char-width))) + (cdr temporary-goal-column))) + (setq last--line-number-width lnum-width)) ;; Otherwise, we should reset `temporary-goal-column'. (let ((posn (posn-at-point)) x-pos) diff --git a/src/indent.c b/src/indent.c index 70351f90466..ba936509934 100644 --- a/src/indent.c +++ b/src/indent.c @@ -1947,6 +1947,59 @@ vmotion (register ptrdiff_t from, register ptrdiff_t from_byte, -1, hscroll, 0, w); } +/* Return the width taken by line-number display in window W. */ +static void +line_number_display_width (struct window *w, int *width, int *pixel_width) +{ + if (NILP (Vdisplay_line_numbers)) + { + *width = 0; + *pixel_width = 0; + } + else + { + struct it it; + struct text_pos wstart; + bool saved_restriction = false; + ptrdiff_t count = SPECPDL_INDEX (); + SET_TEXT_POS_FROM_MARKER (wstart, w->start); + void *itdata = bidi_shelve_cache (); + /* We must start from window's start point, but it could be + outside the accessible region. */ + if (wstart.charpos < BEGV || wstart.charpos > ZV) + { + record_unwind_protect (save_restriction_restore, + save_restriction_save ()); + Fwiden (); + saved_restriction = true; + } + start_display (&it, w, wstart); + move_it_by_lines (&it, 1); + *width = it.lnum_width; + *pixel_width = it.lnum_pixel_width; + if (saved_restriction) + unbind_to (count, Qnil); + bidi_unshelve_cache (itdata, 0); + } +} + +DEFUN ("line-number-display-width", Fline_number_display_width, + Sline_number_display_width, 0, 1, 0, + doc: /* Return the width used for displaying line numbers in the selected window. +If optional argument PIXELWISE is non-nil, return the width in pixels, +otherwise return the width in columns of the face used to display +line numbers, `line-number'. */) + (Lisp_Object pixelwise) +{ + int width, pixel_width; + line_number_display_width (XWINDOW (selected_window), &width, &pixel_width); + if (!NILP (pixelwise)) + return make_number (pixel_width); + /* FIXME: The "+ 2" part knows that we add a blank on each side of + the line number when producing glyphs for display. */ + return make_number (width + 2); +} + /* In window W (derived from WINDOW), return x coordinate for column COL (derived from COLUMN). */ static int @@ -2073,30 +2126,10 @@ whether or not it is currently displayed in some window. */) that's what normal window redisplay does. Otherwise C-n/C-p will sometimes err by one column. */ int lnum_width = 0; + int lnum_pixel_width = 0; if (!NILP (Vdisplay_line_numbers) && !EQ (Vdisplay_line_numbers, Qvisual)) - { - struct text_pos wstart; - bool saved_restriction = false; - ptrdiff_t count1 = SPECPDL_INDEX (); - SET_TEXT_POS_FROM_MARKER (wstart, w->start); - itdata = bidi_shelve_cache (); - /* We must start from window's start point, but it could be - outside the accessible region. */ - if (wstart.charpos < BEGV || wstart.charpos > ZV) - { - record_unwind_protect (save_restriction_restore, - save_restriction_save ()); - Fwiden (); - saved_restriction = true; - } - start_display (&it, w, wstart); - move_it_by_lines (&it, 1); - lnum_width = it.lnum_width; - if (saved_restriction) - unbind_to (count1, Qnil); - bidi_unshelve_cache (itdata, 0); - } + line_number_display_width (w, &lnum_width, &lnum_pixel_width); SET_TEXT_POS (pt, PT, PT_BYTE); itdata = bidi_shelve_cache (); start_display (&it, w, pt); @@ -2277,6 +2310,12 @@ whether or not it is currently displayed in some window. */) an addition to the hscroll amount. */ if (lcols_given) { + /* If we are displaying line numbers, we could cross the + line where the width of the line-number display changes, + in which case we need to fix up the pixel coordinate + accordingly. */ + if (lnum_pixel_width > 0) + to_x += it.lnum_pixel_width - lnum_pixel_width; move_it_in_display_line (&it, ZV, first_x + to_x, MOVE_TO_X); /* If we find ourselves in the middle of an overlay string which includes a newline after current string position, @@ -2322,6 +2361,7 @@ syms_of_indent (void) defsubr (&Sindent_to); defsubr (&Scurrent_column); defsubr (&Smove_to_column); + defsubr (&Sline_number_display_width); defsubr (&Svertical_motion); defsubr (&Scompute_motion); } -- 2.39.2