Subject: Re: bug#56393: Actually fix the long lines display bug

From: Gregory Heytings <[email-masked]>
Date: Tue, 05 Jul 2022 10:17:37 +0000

>
> This one seems to include the last change to x-dnd.el.
>

Gosh!  After including too little, I included too much.  The attached one 
is correct (I hope).

From 55cdec5d60025451826dae21c20d1e61244bc6aa Mon Sep 17 00:00:00 2001
From: Gregory Heytings <[email-masked]>
Date: Tue, 5 Jul 2022 10:14:24 +0000
Subject: [PATCH] Actually fix the long lines display bug (bug#56393).

* lisp/files.el (auto-narrow-mode): New minor mode.
(auto-narrow-pre-command-function, auto-narrow-post-command-function):
New auxiliary functions for the minor mode.
(auto-narrow-display-length, auto-narrow-widen-automatically): New
defcustoms for the minor mode.
(auto-narrow--widen-automatically, auto-narrow--isearch-widen-automatically,
auto-narrow--initialized): New internal variables for the minor mode.

* src/buffer.h (struct buffer): New internal variable for the minor mode.

* src/buffer.c (init_buffer_once, syms_of_buffer): New internal variable
for the minor mode.

* src/fileio.c (Finsert_file_contents): Automatically enter the minor mode
when appropriate.
(syms_of_fileio): New defcustom for the minor mode.

* src/keyboard.c (syms_of_keyboard): New hook functions for the minor mode.
(command_loop_1): Execute the hook functions.

* src/search.c (find_newline1): Make it externally visible.

* src/lisp.h: Make find_newline1 externally visible.

* src/xdisp.c (decode_mode_spec): Indicate when the minor mode is active
in the modeline.

* lisp/isearch.el (isearch-widen-automatically): New defcustom.
(isearch-search): Use the new defcustom.

* lisp/bindings.el (mode-line-modes): Do not propertize the indication
in the modeline when the new minor mode is active.

* etc/NEWS: Announce the new minor mode, and remove the unobsoletion
indication for 'longlines-mode'.

* lisp/longlines.el: Reobsolete longlines-mode.

* doc/emacs/display.texi (Auto-Narrowing): New section, describing the new
minor mode.
(Display): Entry for the new section.

* doc/emacs/trouble.texi (Long Lines): Remove the section.
(Lossage): Remove the entry for the Long Lines section.

* doc/emacs/emacs.texi (Top): Remove the entry for the Long Lines section.
---
 doc/emacs/display.texi           | 25 +++++++++
 doc/emacs/emacs.texi             |  1 -
 doc/emacs/trouble.texi           | 59 --------------------
 etc/NEWS                         | 15 ++++--
 lisp/bindings.el                 | 12 +++--
 lisp/files.el                    | 92 ++++++++++++++++++++++++++++++++
 lisp/isearch.el                  | 32 +++++++----
 lisp/{ => obsolete}/longlines.el |  1 +
 src/buffer.c                     |  5 ++
 src/buffer.h                     |  3 ++
 src/fileio.c                     | 25 +++++++++
 src/keyboard.c                   | 28 ++++++++++
 src/lisp.h                       |  2 +
 src/search.c                     |  2 +-
 src/xdisp.c                      |  7 ++-
 15 files changed, 229 insertions(+), 80 deletions(-)
 rename lisp/{ => obsolete}/longlines.el (99%)

diff --git a/doc/emacs/display.texi b/doc/emacs/display.texi
index cadac7e453..9fe9533e88 100644
--- a/doc/emacs/display.texi
+++ b/doc/emacs/display.texi
@@ -19,6 +19,8 @@ Display
 * Horizontal Scrolling::   Moving text left and right in a window.
 * Narrowing::              Restricting display and editing to a portion
                              of the buffer.
+* Auto-Narrowing::         Automatically restrict display to a portion of
+                             the buffer.
 * View Mode::              Viewing read-only buffers.
 * Follow Mode::            Follow mode lets two windows scroll as one.
 * Faces::                  How to change the display style using faces.
@@ -467,6 +469,29 @@ Narrowing
 if you enable the command, confirmation will no longer be required for
 it.  @xref{Disabling}.
 
+@node Auto-Narrowing
+@findex auto-narrow-mode
+@vindex auto-narrow-long-line-threshold
+@vindex auto-narrow-display-length
+@vindex auto-narrow-widen-automatically
+
+  When a file with extremely long lines is opened or inserted in a
+buffer, Emacs automatically enters auto-narrow mode, and the word
+@samp{Auto-Narrow} appears in the mode line.  This means that Emacs
+restricts display, but not editing, to a portion of the buffer above
+and below point.  All editing commands, including narrowing commands,
+remain available, and they act on the whole buffer.  For example,
+@kbd{M->} moves point to the end of the buffer, and not, as would
+happen with ordinary narrowing, to the end of the portion of the
+buffer to which display is currently restricted.
+
+  The behavior of auto-narrow mode is controlled by three variables:
+@code{auto-narrow-long-line-threshold} is the line length above which
+auto-narrow move is entered, @code{auto-narrow-display-length} is the
+number of characters to which display is restricted, and
+@code{auto-narrow-widen-automatically} is a list of commands for which
+display is widened before they are executed.
+
 @node View Mode
 @section View Mode
 @cindex View mode
diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi
index 5e72699bbe..b43c966f87 100644
--- a/doc/emacs/emacs.texi
+++ b/doc/emacs/emacs.texi
@@ -1190,7 +1190,6 @@ Top
 * Crashing::            What Emacs does when it crashes.
 * After a Crash::       Recovering editing in an Emacs session that crashed.
 * Emergency Escape::    What to do if Emacs stops responding.
-* Long Lines::          Mitigating slowness due to extremely long lines.
 * DEL Does Not Delete:: What to do if @key{DEL} doesn't delete.
 
 Reporting Bugs
diff --git a/doc/emacs/trouble.texi b/doc/emacs/trouble.texi
index f06b93759d..887e5c6170 100644
--- a/doc/emacs/trouble.texi
+++ b/doc/emacs/trouble.texi
@@ -158,7 +158,6 @@ Lossage
 * Crashing::              What Emacs does when it crashes.
 * After a Crash::         Recovering editing in an Emacs session that crashed.
 * Emergency Escape::      What to do if Emacs stops responding.
-* Long Lines::            Mitigating slowness due to extremely long lines.
 * DEL Does Not Delete::   What to do if @key{DEL} doesn't delete.
 @end menu
 
@@ -433,64 +432,6 @@ Emergency Escape
 emergency escape---but there are cases where it won't work, when a
 system call hangs or when Emacs is stuck in a tight loop in C code.
 
-@node Long Lines
-@subsection Long Lines
-@cindex long lines
-
-  For a variety of reasons (some of which are fundamental to the Emacs
-redisplay code and the complex range of possibilities it handles;
-others of which are due to modes and features which do not scale well
-in unusual circumstances), Emacs can perform poorly when extremely
-long lines are present (where ``extremely long'' usually means at
-least many thousands of characters).
-
-@cindex @code{so-long} mode
-@findex global-so-long-mode
-@vindex so-long-action
-  A particular problem is that Emacs may ``hang'' for a long time at
-the point of visiting a file with extremely long lines.  This can be
-mitigated by enabling the @file{so-long} library, which detects when a
-visited file contains abnormally long lines, and takes steps to
-disable features which are liable to cause slowness in that situation.
-To enable this library, type @kbd{M-x global-so-long-mode @key{RET}},
-or turn on the @code{global-so-long-mode} in your init file
-(@pxref{Init File}), or customize the @code{global-so-long-mode}
-option.  You can tailor this mode's operation by customizing the
-variable @code{so-long-action}.
-
-  The @file{so-long} library can also significantly improve
-performance when moving and editing in a buffer with long lines.
-Performance is still likely to degrade as you get deeper into the long
-lines, but the improvements from using this library can nevertheless
-be substantial.
-
-@findex so-long-commentary
-  Use @kbd{M-x so-long-commentary} to view the documentation for this
-library and learn more about how to enable and configure it.
-
-@vindex max-redisplay-ticks
-  If even @code{so-long-mode} doesn't help making Emacs responsive
-enough, or if you'd rather not disable the display-related features
-that @code{so-long-mode} turns off, you can instead customize the
-variable @code{max-redisplay-ticks} to a non-zero value.  Then Emacs
-will abort redisplay of a window and commands, like @kbd{C-n} and
-@kbd{M-v}, which use the display code to do their job, if processing a
-window needs more low-level display operations than the value of this
-variable.  The display of the offending window will then remain
-outdated, and possibly incomplete, on the screen, but Emacs should
-otherwise be responsive, and you could then switch to another buffer,
-or kill the problematic buffer, or turn on @code{so-long-mode} or
-@code{so-long-minor-mode} in that buffer.  When the display of a
-window is aborted due to this reason, the buffer shown in that window
-will not have any of its windows redisplayed until the buffer is
-modified or until you type @kbd{C-l} (@pxref{Recentering}) in one of
-that buffer's windows.
-
-  If you decide to customize this variable to a non-zero value, we
-recommend to use a value between 100,000 and 1,000,000, depending on
-your patience and the speed of your system.  The default value is
-zero, which disables this feature.
-
 @node DEL Does Not Delete
 @subsection If @key{DEL} Fails to Delete
 @cindex @key{DEL} vs @key{BACKSPACE}
diff --git a/etc/NEWS b/etc/NEWS
index 7967190c6e..30c71e7f0c 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -324,9 +324,6 @@ startup.  Previously, these functions ignored
 
 * Changes in Emacs 29.1
 
----
-** 'longlines-mode' is no longer obsolete.
-
 +++
 ** New command to change the font size globally.
 To increase the font size, type 'C-x C-M-+' or 'C-x C-M-='; to
@@ -901,6 +898,18 @@ Formerly it invoked 'just-one-space'.  The actions performed by
 'cycle-spacing' and their order can now be customized via the user
 option 'cycle-spacing-actions'.
 
++++
+** Emacs is now capable of editing files with arbitarily long lines.
+When a file with long lines is opened or inserted in a buffer, Emacs
+automatically enters auto-narrow mode.  This means that Emacs
+restricts display, but not editing, to a portion of the buffer above
+and below point.  All editing commands, including narrowing commands,
+remain available, and they act on the whole buffer.  The behavior of
+that mode is controlled by three variables:
+auto-narrow-long-line-threshold, auto-narrow-display-length and
+auto-narrow-widen-automatically.  To disable that feature, set
+auto-narrow-long-line-threshold to nil in your init file.
+
 ---
 ** 'zap-to-char' and 'zap-up-to-char' are case-sensitive for upper-case chars.
 These commands now behave as case-sensitive for interactive calls when
diff --git a/lisp/bindings.el b/lisp/bindings.el
index 0cf1834a4f..fa5040776d 100644
--- a/lisp/bindings.el
+++ b/lisp/bindings.el
@@ -400,10 +400,14 @@ mode-line-modes
 mouse-2: Show help for minor mode\n\
 mouse-3: Toggle minor modes"
 			local-map ,mode-line-minor-mode-keymap)
-	  (propertize "%n" 'help-echo "mouse-2: Remove narrowing from buffer"
-		      'mouse-face 'mode-line-highlight
-		      'local-map (make-mode-line-mouse-map
-				  'mouse-2 #'mode-line-widen))
+          '(:eval
+            (if (not (eq auto-narrow--narrowing 'auto))
+                (propertize "%n"
+                            'help-echo "mouse-2: Remove narrowing from buffer"
+                            'mouse-face 'mode-line-highlight
+                            'local-map (make-mode-line-mouse-map
+                                        'mouse-2 #'mode-line-widen))
+              "%n"))
 	  ")"
 	  (propertize "%]" 'help-echo recursive-edit-help-echo)
 	  " "))
diff --git a/lisp/files.el b/lisp/files.el
index 8812175014..abdeea7a80 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -2648,6 +2648,98 @@ find-file-literally
   	  (confirm-nonexistent-file-or-buffer))))
   (switch-to-buffer (find-file-noselect filename nil t)))
 
+(defcustom auto-narrow-display-length 30000
+  "Number of characters to which display is restricted in `auto-narrow-mode'."
+  :group 'files
+  :group 'find-file
+  :version "29.1"
+  :type 'integer)
+
+(defcustom auto-narrow-widen-automatically
+  '( move-beginning-of-line move-end-of-line backward-sentence forward-sentence
+     backward-sexp forward-sexp beginning-of-defun end-of-defun
+     beginning-of-buffer end-of-buffer goto-char goto-line
+     mark-sexp mark-defun mark-paragraph mark-whole-buffer mark-page
+     exchange-point-and-mark pop-global-mark set-mark-command jump-to-register
+     bookmark-jump)
+  "Commands for which display is automatically widened in `auto-narrow-mode'."
+  :group 'files
+  :group 'find-file
+  :version "29.1"
+  :type '(repeat function))
+
+(defvar-local auto-narrow--widen-automatically nil
+  "Internal variable used by `auto-narrow-mode'.")
+
+(defvar-local auto-narrow--isearch-widen-automatically nil
+  "Internal variable used by `auto-narrow-mode'.")
+
+(defvar-local auto-narrow--initialized nil
+  "Internal variable used by `auto-narrow-mode'.")
+
+(defun auto-narrow-pre-command-function ()
+  "Conditionally widen display when `auto-narrow-mode' is in effect."
+  (when auto-narrow-mode
+    (unless auto-narrow--initialized
+      (setq auto-narrow--widen-automatically widen-automatically
+            auto-narrow--isearch-widen-automatically isearch-widen-automatically
+            auto-narrow--narrowing 'auto
+            auto-narrow--initialized t))
+    (setq-local widen-automatically t
+                isearch-widen-automatically t)
+    (if (memq this-command '(narrow-to-region narrow-to-defun narrow-to-page))
+        (setq auto-narrow--narrowing 'explicit
+              widen-automatically auto-narrow--widen-automatically
+              isearch-widen-automatically auto-narrow--isearch-widen-automatically))
+    (if (eq this-command 'widen)
+        (setq auto-narrow--narrowing 'auto))
+    (when (and (not (eq auto-narrow--narrowing 'explicit))
+               (memq this-command auto-narrow-widen-automatically))
+      (widen))))
+
+(defun auto-narrow-post-command-function ()
+  "Update display narrowing when `auto-narrow-mode' is in effect."
+  (when (and auto-narrow-mode
+             (not (eq auto-narrow--narrowing 'explicit)))
+    (unless auto-narrow--initialized
+      (setq auto-narrow--narrowing 'auto))
+    (let (point cur-point-min buf-point-min buf-point-max size)
+      (setq point (point) cur-point-min (point-min)
+            size (/ auto-narrow-display-length 3))
+      (save-restriction
+        (widen)
+        (setq buf-point-min (point-min) buf-point-max (point-max)))
+      (let* ((pos (* (min (max (/ point size) 1)
+                          (1- (/ buf-point-max size)))
+                     size))
+             (begin (max (- pos size) buf-point-min))
+             (end (min (+ begin (* 3 size)) buf-point-max)))
+        (when (or (not (buffer-narrowed-p))
+		  (not (= begin cur-point-min)))
+	  (narrow-to-region begin end))))))
+
+(setq auto-narrow-long-line-threshold 30000
+      auto-narrow-pre-command-function #'auto-narrow-pre-command-function
+      auto-narrow-post-command-function #'auto-narrow-post-command-function)
+
+(define-minor-mode auto-narrow-mode
+  "Automatically narrow the display of a buffer above and below point.
+
+This mode is automatically entered when a file with one or more lines
+longer than `auto-narrow-long-line-threshold' is opened or inserted
+in a buffer.  It restricts display, but not editing, to
+`auto-narrow-display-length' characters.  Display is widened before
+executing any of the commands listed in `auto-narrow-widen-automatically'."
+  :group 'files
+  :version "29.1"
+  :after-hook (progn (put 'auto-narrow-mode 'permanent-local t)
+                     (put 'auto-narrow--narrowing 'permanent-local t))
+  (if auto-narrow-mode
+      (auto-narrow-post-command-function)
+    (when (not (eq auto-narrow--narrowing 'explicit))
+      (widen))
+    (setq auto-narrow--narrowing nil)))
+
 (defun after-find-file (&optional error warn noauto
 				  _after-find-file-from-revert-buffer
 				  nomodes)
diff --git a/lisp/isearch.el b/lisp/isearch.el
index db7b53c014..a6e034df6b 100644
--- a/lisp/isearch.el
+++ b/lisp/isearch.el
@@ -3037,6 +3037,13 @@ isearch-allow-prefix
   :type 'boolean
   :group 'isearch)
 
+(defcustom isearch-widen-automatically nil
+  "Whether a narrowed buffer can be widened when isearch fails.
+This option has no effect when `widen-automatically' is nil."
+  :version "29.1"
+  :type 'boolean
+  :group 'isearch)
+
 (defun isearch-string-out-of-window (isearch-point)
   "Test whether the search string is currently outside of the window.
 Return nil if it's completely visible, or if point is visible,
@@ -3649,17 +3656,20 @@ isearch-search
 	(while retry
 	  (setq isearch-success
 		(isearch-search-string isearch-string nil t))
-	  ;; Clear RETRY unless the search predicate says
-	  ;; to skip this search hit.
-	  (if (or (not isearch-success)
-		  (funcall isearch-filter-predicate
-			   (match-beginning 0) (match-end 0)))
-	      (setq retry nil)
-	    ;; Advance point on empty matches before retrying
-	    (when (= (match-beginning 0) (match-end 0))
-	      (if (if isearch-forward (eobp) (bobp))
-		  (setq retry nil isearch-success nil)
-		(forward-char (if isearch-forward 1 -1))))))
+          (if (and (not isearch-success) (buffer-narrowed-p)
+                   isearch-widen-automatically widen-automatically)
+              (widen)
+	    ;; Clear RETRY unless the search predicate says
+	    ;; to skip this search hit.
+	    (if (or (not isearch-success)
+		    (funcall isearch-filter-predicate
+			     (match-beginning 0) (match-end 0)))
+	        (setq retry nil)
+	      ;; Advance point on empty matches before retrying
+	      (when (= (match-beginning 0) (match-end 0))
+	        (if (if isearch-forward (eobp) (bobp))
+		    (setq retry nil isearch-success nil)
+		  (forward-char (if isearch-forward 1 -1)))))))
 	(setq isearch-just-started nil)
 	(when isearch-success
 	  (setq isearch-other-end
diff --git a/lisp/longlines.el b/lisp/obsolete/longlines.el
similarity index 99%
rename from lisp/longlines.el
rename to lisp/obsolete/longlines.el
index a6cf93a039..d44a634e2e 100644
--- a/lisp/longlines.el
+++ b/lisp/obsolete/longlines.el
@@ -6,6 +6,7 @@
 ;;             Alex Schroeder <[email-masked]>
 ;;             Chong Yidong <[email-masked]>
 ;; Maintainer: [email-masked]
+;; Obsolete-since: 24.4
 ;; Keywords: convenience, wp
 
 ;; This file is part of GNU Emacs.
diff --git a/src/buffer.c b/src/buffer.c
index 509ce51b55..cbc246067d 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -5276,6 +5276,7 @@ init_buffer_once (void)
   XSETFASTINT (BVAR (&buffer_local_flags, tab_line_format), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, cursor_type), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, extra_line_spacing), idx); ++idx;
+  XSETFASTINT (BVAR (&buffer_local_flags, auto_narrow__narrowing), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, cursor_in_non_selected_windows), idx); ++idx;
 
   /* buffer_local_flags contains no pointers, so it's safe to treat it
@@ -6363,6 +6364,10 @@ from (abs POSITION).  If POSITION is positive, point was at the front
 If value is a floating point number, it specifies the spacing relative
 to the default frame line height.  A value of nil means add no extra space.  */);
 
+  DEFVAR_PER_BUFFER ("auto-narrow--narrowing",
+		     &BVAR (current_buffer, auto_narrow__narrowing), Qnil,
+		     doc: /* Internal variable used by `auto-narrow-mode'.  */);
+
   DEFVAR_PER_BUFFER ("cursor-in-non-selected-windows",
 		     &BVAR (current_buffer, cursor_in_non_selected_windows), Qnil,
 		     doc: /* Non-nil means show a cursor in non-selected windows.
diff --git a/src/buffer.h b/src/buffer.h
index 135eaf72d3..a6abc6ece8 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -561,6 +561,9 @@ #define BVAR(buf, field) ((buf)->field ## _)
      in the display of this buffer.  */
   Lisp_Object extra_line_spacing_;
 
+  /* Narrowing type when auto-narrow mode is in effect.  */
+  Lisp_Object auto_narrow__narrowing_;
+
   /* Cursor type to display in non-selected windows.
      t means to use hollow box cursor.
      See `cursor-type' for other values.  */
diff --git a/src/fileio.c b/src/fileio.c
index 10d4b8bc15..9d2cf2ae00 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -4997,6 +4997,20 @@ because (1) it preserves some marker positions (in unchanged portions
 	   Otherwise start with an empty undo_list.  */
 	bset_undo_list (current_buffer, EQ (old_undo, Qt) ? Qt : Qnil);
 
+      if (!noninteractive && !NILP (Vauto_narrow_long_line_threshold))
+	{
+	  ptrdiff_t cur, next, found, max = 0;
+	  for (cur = PT; cur < PT + inserted; cur = next)
+	    {
+	      next = find_newline1 (cur, CHAR_TO_BYTE (cur), 0, -1, 1,
+				    &found, NULL, true);
+	      if (next - cur > max) max = next - cur;
+	      if (!found) break;
+	    }
+	  if (max > XFIXNUM (Vauto_narrow_long_line_threshold))
+	    safe_call (1, Qauto_narrow_mode);
+	}
+
       unbind_to (count1, Qnil);
     }
 
@@ -6638,6 +6652,17 @@ do (file-exists-p FILENAME) and FILENAME is handled by HANDLER, then
   /* Lisp function for recursively deleting directories.  */
   DEFSYM (Qdelete_directory, "delete-directory");
 
+  DEFVAR_LISP ("auto-narrow-long-line-threshold",
+	       Vauto_narrow_long_line_threshold,
+	       doc: /* Line length above which `auto-narrow-mode' is entered.
+When non-nil, and when a file with one or more lines longer than
+`auto-narrow-long-line-threshold' is opened or inserted in a buffer,
+`auto-narrow-mode' is automatically enabled.
+When nil, `auto-narrow-mode' is not entered automatically.  */);
+  Vauto_narrow_long_line_threshold = Qnil;
+
+  DEFSYM (Qauto_narrow_mode, "auto-narrow-mode");
+
   DEFSYM (Qsubstitute_env_in_file_name, "substitute-env-in-file-name");
   DEFSYM (Qget_buffer_window_list, "get-buffer-window-list");
 
diff --git a/src/keyboard.c b/src/keyboard.c
index bed8307b6f..38aa64774d 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -1290,6 +1290,9 @@ command_loop_1 (void)
 
   if (NILP (Vmemory_full))
     {
+      if (!NILP (Vauto_narrow_post_command_function) && !NILP (Vrun_hooks))
+	safe_run_hooks (Qauto_narrow_post_command_function);
+
       /* Make sure this hook runs after commands that get errors and
 	 throw to top level.  */
       /* Note that the value cell will never directly contain nil
@@ -1472,6 +1475,8 @@ command_loop_1 (void)
       Vreal_this_command = cmd;
       safe_run_hooks (Qpre_command_hook);
 
+      safe_run_hooks (Qauto_narrow_pre_command_function);
+
       already_adjusted = 0;
 
       if (NILP (Vthis_command))
@@ -1522,6 +1527,8 @@ command_loop_1 (void)
           }
       kset_last_prefix_arg (current_kboard, Vcurrent_prefix_arg);
 
+      safe_run_hooks (Qauto_narrow_post_command_function);
+
       safe_run_hooks (Qpost_command_hook);
 
       /* If displaying a message, resize the echo area window to fit
@@ -12040,6 +12047,11 @@ syms_of_keyboard (void)
   DEFSYM (Qpre_command_hook, "pre-command-hook");
   DEFSYM (Qpost_command_hook, "post-command-hook");
 
+  DEFSYM (Qauto_narrow_pre_command_function,
+	  "auto-narrow-pre-command-function");
+  DEFSYM (Qauto_narrow_post_command_function,
+	  "auto-narrow-post-command-function");
+
   DEFSYM (Qundo_auto__add_boundary, "undo-auto--add-boundary");
   DEFSYM (Qundo_auto__undoably_changed_buffers,
           "undo-auto--undoably-changed-buffers");
@@ -12604,6 +12616,22 @@ syms_of_keyboard (void)
 See also `pre-command-hook'.  */);
   Vpost_command_hook = Qnil;
 
+  DEFVAR_LISP ("auto-narrow-pre-command-function",
+	       Vauto_narrow_pre_command_function,
+	       doc: /* Function executed before each command is executed in `auto-narrow-mode'.
+If non-nil, and `auto-narrow-mode' is enabled, the function is
+called before each command is executed, after `pre-command-hook'.
+It is called without arguments.  */);
+  Vauto_narrow_pre_command_function = Qnil;
+
+  DEFVAR_LISP ("auto-narrow-post-command-function",
+	       Vauto_narrow_post_command_function,
+	       doc: /* Function executed after each command is executed in `auto-narrow-mode'.
+If non-nil, and `auto-narrow-mode' is enabled, the function is
+called after each command is executed, before `post-command-hook'.
+It is called without arguments.  */);
+  Vauto_narrow_post_command_function = Qnil;
+
 #if 0
   DEFVAR_LISP ("echo-area-clear-hook", ...,
 	       doc: /* Normal hook run when clearing the echo area.  */);
diff --git a/src/lisp.h b/src/lisp.h
index e4a49b8ef9..289141296c 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4761,6 +4761,8 @@ fast_string_match_ignore_case (Lisp_Object regexp, Lisp_Object string)
 						  ptrdiff_t);
 extern ptrdiff_t fast_looking_at (Lisp_Object, ptrdiff_t, ptrdiff_t,
                                   ptrdiff_t, ptrdiff_t, Lisp_Object);
+extern ptrdiff_t find_newline1 (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t,
+				ptrdiff_t, ptrdiff_t *, ptrdiff_t *, bool);
 extern ptrdiff_t find_newline (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t,
 			       ptrdiff_t, ptrdiff_t *, ptrdiff_t *, bool);
 extern void scan_newline (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t,
diff --git a/src/search.c b/src/search.c
index 9d6bd074e1..b5d6a442c0 100644
--- a/src/search.c
+++ b/src/search.c
@@ -3192,7 +3192,7 @@ DEFUN ("regexp-quote", Fregexp_quote, Sregexp_quote, 1, 1, 0,
 }
 
 /* Like find_newline, but doesn't use the cache, and only searches forward.  */
-static ptrdiff_t
+ptrdiff_t
 find_newline1 (ptrdiff_t start, ptrdiff_t start_byte, ptrdiff_t end,
 	       ptrdiff_t end_byte, ptrdiff_t count, ptrdiff_t *counted,
 	       ptrdiff_t *bytepos, bool allow_quit)
diff --git a/src/xdisp.c b/src/xdisp.c
index 4089525e10..d782659b92 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -27667,7 +27667,12 @@ decode_mode_spec (struct window *w, register int c, int field_width,
 
     case 'n':
       if (BUF_BEGV (b) > BUF_BEG (b) || BUF_ZV (b) < BUF_Z (b))
-	return " Narrow";
+	{
+	  if (EQ (BVAR (current_buffer, auto_narrow__narrowing), intern ("auto")))
+	    return " Auto-Narrow";
+	  else
+	    return " Narrow";
+	}
       break;
 
       /* Display the "degree of travel" of the window through the buffer.  */
-- 
2.35.1