Further speedups of redisplay of long and truncated lines

* src/xdisp.c (mode_line_update_needed, redisplay_window)
(decode_mode_spec): Don't avoid calling current_column, as it is
now fast enough.
(redisplay_window) <optional_new_start>: Don't call 'move_it_to'
if its result will not be used.
(Flong_line_optimizations_p): New primitive.
* src/indent.c (Fcurrent_column): Doc fix.
(current_column, scan_for_column): When in a buffer with long
and/or truncated lines, quickly return an approximate value.
* src/window.c (Frecenter): Use the old text-mode code when the
buffer has very long lines.

* lisp/simple.el (line-move): Avoid costly calls to
'line-move-partial' and 'line-move-visual' when lines are
truncated and/or very long.
(move-beginning-of-line): Call 'line-beginning-position' instead
of the slower 'skip-chars-backward'.

* etc/NEWS: Announce 'long-line-optimizations-p'.
This commit is contained in:
Eli Zaretskii 2022-08-14 15:47:59 +03:00
parent b93e14fa0f
commit a71c05b44d
5 changed files with 128 additions and 52 deletions

View file

@ -400,6 +400,9 @@ and the major mode with 'M-x so-long-mode', or visit the file with
Note that the display optimizations in these cases may cause the
buffer to be occasionally mis-fontified.
The new function 'long-line-optimizations-p' returns non-nil when
these optimizations are in effect in the current buffer.
+++
** New command to change the font size globally.
To increase the font size, type 'C-x C-M-+' or 'C-x C-M-='; to

View file

@ -7692,11 +7692,33 @@ not vscroll."
;; But don't vscroll in a keyboard macro.
(not defining-kbd-macro)
(not executing-kbd-macro)
;; Lines are not truncated...
(not
(and
(or truncate-lines
(and (integerp truncate-partial-width-windows)
(< (window-total-width)
truncate-partial-width-windows))
(and truncate-partial-width-windows
(not (integerp truncate-partial-width-windows))
(not (window-full-width-p))))
;; ...or if lines are truncated, this buffer
;; doesn't have very long lines.
(long-line-optimizations-p)))
(line-move-partial arg noerror))
(set-window-vscroll nil 0 t)
(if (and line-move-visual
;; Display-based column are incompatible with goal-column.
(not goal-column)
;; Lines aren't truncated.
(not
(or truncate-lines
(and (integerp truncate-partial-width-windows)
(< (window-width)
truncate-partial-width-windows))
(and truncate-partial-width-windows
(not (integerp truncate-partial-width-windows))
(not (window-full-width-p)))))
;; When the text in the window is scrolled to the left,
;; display-based motion doesn't make sense (because each
;; logical line occupies exactly one screen line).
@ -8133,10 +8155,11 @@ For motion by visual lines, see `beginning-of-visual-line'."
(line-move (1- arg) t)))
;; Move to beginning-of-line, ignoring fields and invisible text.
(skip-chars-backward "^\n")
(while (and (not (bobp)) (invisible-p (1- (point))))
(goto-char (previous-char-property-change (point)))
(skip-chars-backward "^\n"))
(let ((inhibit-field-text-motion t))
(goto-char (line-beginning-position))
(while (and (not (bobp)) (invisible-p (1- (point))))
(goto-char (previous-char-property-change (point)))
(goto-char (line-beginning-position))))
;; Now find first visible char in the line.
(while (and (< (point) orig) (invisible-p (point)))

View file

@ -306,8 +306,8 @@ and point (e.g., control characters will have a width of 2 or 4, tabs
will have a variable width).
Ignores finite width of frame, which means that this function may return
values greater than (frame-width).
In a buffer with very long lines, the value can be zero, because calculating
the exact number is very expensive.
In a buffer with very long lines, the value will be an approximation,
because calculating the exact number is very expensive.
Whether the line is visible (if `selective-display' is t) has no effect;
however, ^M is treated as end of line when `selective-display' is t.
Text that has an invisible property is considered as having width 0, unless
@ -316,8 +316,6 @@ Text that has an invisible property is considered as having width 0, unless
{
Lisp_Object temp;
if (current_buffer->long_line_optimizations_p)
return make_fixnum (0);
XSETFASTINT (temp, current_column ());
return temp;
}
@ -346,6 +344,14 @@ current_column (void)
&& MODIFF == last_known_column_modified)
return last_known_column;
ptrdiff_t line_beg = find_newline (PT, PT_BYTE, BEGV, BEGV_BYTE, -1,
NULL, NULL, 1);
/* Avoid becoming abysmally slow for very long lines. */
if (current_buffer->long_line_optimizations_p
&& !NILP (Vlong_line_threshold)
&& PT - line_beg > XFIXNUM (Vlong_line_threshold))
return PT - line_beg; /* this is an approximation! */
/* If the buffer has overlays, text properties,
or multibyte characters, use a more general algorithm. */
if (buffer_intervals (current_buffer)
@ -561,13 +567,53 @@ scan_for_column (ptrdiff_t *endpos, EMACS_INT *goalcol,
ptrdiff_t scan, scan_byte, next_boundary, prev_pos, prev_bpos;
scan = find_newline (PT, PT_BYTE, BEGV, BEGV_BYTE, -1, NULL, &scan_byte, 1);
next_boundary = scan;
prev_pos = scan;
prev_bpos = scan_byte;
window = Fget_buffer_window (Fcurrent_buffer (), Qnil);
w = ! NILP (window) ? XWINDOW (window) : NULL;
if (current_buffer->long_line_optimizations_p)
{
bool lines_truncated = false;
if (!NILP (BVAR (current_buffer, truncate_lines)))
lines_truncated = true;
else if (w && FIXNUMP (Vtruncate_partial_width_windows))
lines_truncated =
w->total_cols < XFIXNAT (Vtruncate_partial_width_windows);
else if (w && !NILP (Vtruncate_partial_width_windows))
lines_truncated =
w->total_cols < FRAME_COLS (XFRAME (WINDOW_FRAME (w)));
/* Special optimization for buffers with long and truncated
lines: assumes that each character is a single column. */
if (lines_truncated)
{
ptrdiff_t bolpos = scan;
/* The newline which ends this line or ZV. */
ptrdiff_t eolpos =
find_newline (PT, PT_BYTE, ZV, ZV_BYTE, 1, NULL, NULL, 1);
scan = bolpos + goal;
if (scan > end)
scan = end;
if (scan > eolpos)
scan = (eolpos == ZV ? ZV : eolpos - 1);
col = scan - bolpos;
if (col > large_hscroll_threshold)
{
prev_col = col - 1;
prev_pos = scan - 1;
prev_bpos = CHAR_TO_BYTE (scan);
goto endloop;
}
/* Restore the values we've overwritten above. */
scan = bolpos;
col = 0;
}
}
next_boundary = scan;
prev_pos = scan;
prev_bpos = scan_byte;
memset (&cmp_it, 0, sizeof cmp_it);
cmp_it.id = -1;
composition_compute_stop_pos (&cmp_it, scan, scan_byte, end, Qnil);

View file

@ -6575,9 +6575,12 @@ and redisplay normally--don't erase and redraw the frame. */)
in case scroll_margin is buffer-local. */
this_scroll_margin = window_scroll_margin (w, MARGIN_IN_LINES);
/* Don't use redisplay code for initial frames, as the necessary
data structures might not be set up yet then. */
if (!FRAME_INITIAL_P (XFRAME (w->frame)))
/* Don't use the display code for initial frames, as the necessary
data structures might not be set up yet then. Also don't use it
for buffers with very long lines, as it tremdously slows down
redisplay, especially when lines are truncated. */
if (!FRAME_INITIAL_P (XFRAME (w->frame))
&& !current_buffer->long_line_optimizations_p)
{
specpdl_ref count = SPECPDL_INDEX ();

View file

@ -13174,8 +13174,7 @@ mode_line_update_needed (struct window *w)
{
return (w->column_number_displayed != -1
&& !(PT == w->last_point && !window_outdated (w))
&& (!current_buffer->long_line_optimizations_p
&& w->column_number_displayed != current_column ()));
&& (w->column_number_displayed != current_column ()));
}
/* True if window start of W is frozen and may not be changed during
@ -19331,6 +19330,16 @@ window_start_acceptable_p (Lisp_Object window, ptrdiff_t startp)
return true;
}
DEFUN ("long-line-optimizations-p", Flong_line_optimizations_p, Slong_line_optimizations_p,
0, 0, 0,
doc: /* Return non-nil if long-line optimizations are in effect in current buffer.
See `long-line-threshold' and `large-hscroll-threshold' for what these
optimizations mean and when they are in effect. */)
(void)
{
return current_buffer->long_line_optimizations_p ? Qt : Qnil;
}
/* Redisplay leaf window WINDOW. JUST_THIS_ONE_P means only
selected_window is redisplayed.
@ -19606,33 +19615,36 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
ptrdiff_t it_charpos;
w->optional_new_start = false;
start_display (&it, w, startp);
move_it_to (&it, PT, 0, it.last_visible_y, -1,
MOVE_TO_POS | MOVE_TO_X | MOVE_TO_Y);
/* Record IT's position now, since line_bottom_y might change
that. */
it_charpos = IT_CHARPOS (it);
/* Make sure we set the force_start flag only if the cursor row
will be fully visible. Otherwise, the code under force_start
label below will try to move point back into view, which is
not what the code which sets optional_new_start wants. */
if ((it.current_y == 0 || line_bottom_y (&it) < it.last_visible_y)
&& !w->force_start)
if (!w->force_start)
{
if (it_charpos == PT)
w->force_start = true;
/* IT may overshoot PT if text at PT is invisible. */
else if (it_charpos > PT && CHARPOS (startp) <= PT)
w->force_start = true;
#ifdef GLYPH_DEBUG
if (w->force_start)
start_display (&it, w, startp);
move_it_to (&it, PT, 0, it.last_visible_y, -1,
MOVE_TO_POS | MOVE_TO_X | MOVE_TO_Y);
/* Record IT's position now, since line_bottom_y might
change that. */
it_charpos = IT_CHARPOS (it);
/* Make sure we set the force_start flag only if the cursor
row will be fully visible. Otherwise, the code under
force_start label below will try to move point back into
view, which is not what the code which sets
optional_new_start wants. */
if (it.current_y == 0 || line_bottom_y (&it) < it.last_visible_y)
{
if (window_frozen_p (w))
debug_method_add (w, "set force_start from frozen window start");
else
debug_method_add (w, "set force_start from optional_new_start");
}
if (it_charpos == PT)
w->force_start = true;
/* IT may overshoot PT if text at PT is invisible. */
else if (it_charpos > PT && CHARPOS (startp) <= PT)
w->force_start = true;
#ifdef GLYPH_DEBUG
if (w->force_start)
{
if (window_frozen_p (w))
debug_method_add (w, "set force_start from frozen window start");
else
debug_method_add (w, "set force_start from optional_new_start");
}
#endif
}
}
}
@ -20358,7 +20370,6 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
|| w->base_line_pos > 0
/* Column number is displayed and different from the one displayed. */
|| (w->column_number_displayed != -1
&& !current_buffer->long_line_optimizations_p
&& (w->column_number_displayed != current_column ())))
/* This means that the window has a mode line. */
&& (window_wants_mode_line (w)
@ -27878,17 +27889,6 @@ decode_mode_spec (struct window *w, register int c, int field_width,
even crash emacs.) */
if (mode_line_target == MODE_LINE_TITLE)
return "";
else if (b->long_line_optimizations_p)
{
char *p = decode_mode_spec_buf;
int pad = width - 2;
while (pad-- > 0)
*p++ = ' ';
*p++ = '?';
*p++ = '?';
*p = '\0';
return decode_mode_spec_buf;
}
else
{
ptrdiff_t col = current_column ();
@ -36232,6 +36232,7 @@ be let-bound around code that needs to disable messages temporarily. */);
defsubr (&Sbidi_find_overridden_directionality);
defsubr (&Sdisplay__line_is_continued_p);
defsubr (&Sget_display_property);
defsubr (&Slong_line_optimizations_p);
DEFSYM (Qmenu_bar_update_hook, "menu-bar-update-hook");
DEFSYM (Qoverriding_terminal_local_map, "overriding-terminal-local-map");