From a71c05b44de74fe16691f680df34c4534992e472 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sun, 14 Aug 2022 15:47:59 +0300 Subject: [PATCH] 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) : 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'. --- etc/NEWS | 3 ++ lisp/simple.el | 31 +++++++++++++++++--- src/indent.c | 60 +++++++++++++++++++++++++++++++++----- src/window.c | 9 ++++-- src/xdisp.c | 79 +++++++++++++++++++++++++------------------------- 5 files changed, 129 insertions(+), 53 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 2b942f67b01..8fc3df63ebd 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -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 diff --git a/lisp/simple.el b/lisp/simple.el index ce3895176e1..1e6e5e11e00 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -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))) diff --git a/src/indent.c b/src/indent.c index d2dfaee254e..cb368024d97 100644 --- a/src/indent.c +++ b/src/indent.c @@ -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); diff --git a/src/window.c b/src/window.c index afb8f75537b..c8fcb3a607f 100644 --- a/src/window.c +++ b/src/window.c @@ -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 (); diff --git a/src/xdisp.c b/src/xdisp.c index 7ee42918eb6..0248e8e53f1 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -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 (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; + 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 (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"); - } + 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"); -- 2.39.5