emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emacs-diffs] scratch/so-long 29f39a4: Add so-long library


From: Phil
Subject: [Emacs-diffs] scratch/so-long 29f39a4: Add so-long library
Date: Fri, 11 Jan 2019 18:26:29 -0500 (EST)

branch: scratch/so-long
commit 29f39a4b087fed2cc386c812291e0cc7527728b8
Author: Phil Sainty <address@hidden>
Commit: Phil Sainty <address@hidden>

    Add so-long library
    
    * lisp/so-long.el: New library
    * doc/emacs/trouble.texi: Briefly describe the problem and solution
    * etc/NEWS: Include under "New Modes and Packages in Emacs 27.1"
---
 doc/emacs/trouble.texi |   25 +
 etc/NEWS               |    8 +
 lisp/so-long.el        | 1395 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1428 insertions(+)

diff --git a/doc/emacs/trouble.texi b/doc/emacs/trouble.texi
index 1bdd9fa..9e156de 100644
--- a/doc/emacs/trouble.texi
+++ b/doc/emacs/trouble.texi
@@ -152,6 +152,7 @@ Emacs.
 * 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.
 @end menu
 
 @node DEL Does Not Delete
@@ -457,6 +458,30 @@ program.
 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.
 
address@hidden Long Lines
address@hidden Long Lines
address@hidden 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).
+
+  A particular problem is that Emacs may ``hang'' for a long time at
+the point of visiting a file with extremely long lines, and this case
+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.
+
+  This library can also significantly improve performance when moving
+and editing in such a buffer -- performance is still likely to degrade
+as you get deeper into the long lines, but the improvements can
+nevertheless be substantial.
+
+  Use @kbd{M-x so-long-commentary} to view the documentation for this
+library and learn how to enable and configure it.
 @node Bugs
 @section Reporting Bugs
 
diff --git a/etc/NEWS b/etc/NEWS
index 3670ab5..4677597 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1058,6 +1058,14 @@ expansion to backtrace buffers produced by the Lisp 
debugger, Edebug
 and ERT.  See the node "(elisp) Backtraces" in the Elisp manual for
 documentation of the new mode and its commands.
 
++++
+** so-long.el helps to mitigate performance problems with long lines.
+When `so-long-enable' has been called, visiting a file with very long
+lines will (subject to configuration) cause the user's preferred
+`so-long-action' to be automatically invoked (by default, the buffer's
+major mode is replaced by `so-long-mode').  In extreme cases this can
+prevent delays of several minutes, and make Emacs responsive almost
+immediately.  Type 'M-x so-long-commentary' for full documentation.
 
 * Incompatible Lisp Changes in Emacs 27.1
 
diff --git a/lisp/so-long.el b/lisp/so-long.el
new file mode 100644
index 0000000..b7c58ed
--- /dev/null
+++ b/lisp/so-long.el
@@ -0,0 +1,1395 @@
+;;; so-long.el --- Say farewell to performance problems with minified code.  
-*- lexical-binding:t -*-
+;;
+;; Copyright (C) 2015, 2016, 2018, 2019 Free Software Foundation, Inc.
+
+;; Author: Phil Sainty <address@hidden>
+;; Maintainer: Phil Sainty <address@hidden>
+;; URL: https://savannah.nongnu.org/projects/so-long
+;; Keywords: convenience
+;; Created: 23 Dec 2015
+;; Package-Requires: ((emacs "24.3"))
+;; Version: 1.0
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; When the lines in a file are so long that performance could suffer to an
+;; unacceptable degree, we say "so long" to the slow modes and options enabled
+;; in that buffer, and invoke something much more basic in their place.
+;;
+;; Many Emacs modes struggle with buffers which contain excessively long lines.
+;; This is commonly on account of 'minified' code (i.e. code that has been
+;; compacted into the smallest file size possible, which often entails removing
+;; newlines should they not be strictly necessary).  Most programming modes
+;; simply aren't optimised (remotely) for this scenario, and so performance can
+;; suffer significantly.
+;;
+;; When such files are detected, we automatically override certain minor modes
+;; and variables with performance implications (all configurable), in order to
+;; enhance performance in the buffer.
+;;
+;; By default we also invoke `so-long-mode' in place of the major mode that
+;; Emacs selected.  This is almost identical to `fundamental-mode', and so
+;; provides optimal major mode performance.  These kinds of minified files are
+;; typically not intended to be edited, so not providing the usual editing mode
+;; in such cases will rarely be an issue.  However, should the user wish to do
+;; so, the original mode may be reinstated easily in any given buffer using
+;; `so-long-revert' (the key binding for which is advertised when the major
+;; mode change occurs).  If you prefer not to change the major mode, the
+;; `overrides-only' action can be configured.
+;;
+;; The user options `so-long-action' and `so-long-action-alist' determine what
+;; will happen when `so-long' and `so-long-revert' are invoked, allowing
+;; alternative actions (including custom actions) to be configured.  By default
+;; `longlines-mode' is supported as an alternative action.
+;;
+;; Note that while the measures taken by this library can improve performance
+;; dramatically when dealing with such files, this library does not have any
+;; effect on the fundamental limitations of the Emacs redisplay code itself; 
and
+;; so if you do need to edit the file, performance may still degrade as you get
+;; deeper into the long lines.  In such circumstances you may find that
+;; `longlines-mode' is the most helpful facility.
+;;
+;; Note also that the mitigations are automatically triggered when visiting a
+;; file.  The library does not automatically detect if long lines are inserted
+;; into an existing buffer (although the `so-long' command can be invoked
+;; manually in such situations).
+
+;; Installation
+;; ------------
+;; Use M-x so-long-enable to enable the functionality, or add the following to
+;; your init file to do so permanently:
+;;
+;; (when (require 'so-long nil :noerror)
+;;   (so-long-enable))
+;;
+;; If necessary, ensure that so-long.el is in a directory in your load-path.
+;; (This step is not necessary if you are using Emacs 27+, or have installed 
the
+;; GNU ELPA package.)
+;;
+;; To disable the functionality, use M-x so-long-disable.
+
+;; Basic configuration
+;; -------------------
+;; Use M-x customize-group RET so-long RET
+;;
+;; The user options `so-long-target-modes', `so-long-threshold', and
+;; `so-long-max-lines' determine whether action will be taken automatically 
when
+;; visiting a file, and `so-long-action' determines what will be done.
+
+;; Actions and menus
+;; -----------------
+;; The user options `so-long-action' and `so-long-action-alist' determine what
+;; will happen when `so-long' and `so-long-revert' are invoked, and you can add
+;; your own custom actions if you wish.  The action can be invoked manually 
with
+;; M-x so-long.
+;;
+;; All defined actions are presented in the "So Long" menu, which is visible
+;; whenever long lines have been detected.  Selecting an action from the menu
+;; will firstly cause the current action (if any) to be reverted, before the
+;; newly-selected action is invoked.
+;;
+;; Aside from the menu bar, the menu is also available in the mode line --
+;; either via the major mode construct (when `so-long-mode' is active), or in
+;; a separate mode line construct when some other major mode is active.
+
+;; Files with a file-local 'mode'
+;; ------------------------------
+;; A file-local major mode is likely to be safe even if long lines are 
detected,
+;; and so these files are treated as special cases.  When a file-local 'mode' 
is
+;; present, the function defined by the `so-long-file-local-mode-function' user
+;; option is called.  The default value will cause the `overrides-only' action
+;; to be used instead of the `so-long-mode' action, if the latter was going to
+;; be used for this file.  This option can also be configured to inhibit 
so-long
+;; entirely in this scenario, or to not treat a file-local mode as a special
+;; case at all.
+
+;; Inhibiting and disabling minor modes
+;; ------------------------------------
+;; Certain minor modes cause significant performance issues in the presence of
+;; very long lines, and should be disabled automatically in this situation.
+;;
+;; The simple way to disable most buffer-local minor modes is to add the mode
+;; symbol to the `so-long-minor-modes' list.  Several modes are targeted by
+;; default, and it is a good idea to customize this variable to add any
+;; additional buffer-local minor modes that you use which you know to have
+;; performance implications.
+;;
+;; These minor modes are disabled if `so-long-action' is set to either
+;; `so-long-mode' or `overrides-only'.  If `so-long-revert' is called, then
+;; the original values are restored.
+;;
+;; In the case of globalized minor modes, be sure to specify the buffer-local
+;; minor mode, and not the global mode which controls it.
+;;
+;; Note that `so-long-minor-modes' is not useful for other global minor modes
+;; (as distinguished from globalized minor modes), but in some cases it will be
+;; possible to inhibit or otherwise counter-act the behaviour of a global mode
+;; by overriding variables, or by employing hooks (see below).  You would need
+;; to inspect the code for a given global mode (on a case by case basis) to
+;; determine whether it's possible to inhibit it for a single buffer -- and if
+;; so, how best to do that, as not all modes are alike.
+
+;; Overriding variables
+;; --------------------
+;; `so-long-variable-overrides' is an alist mapping variable symbols to values.
+;; If `so-long-action' is set to either `so-long-mode' or `overrides-only', the
+;; buffer-local value for each variable in the list is set to the associated
+;; value in the alist.  Use this to enforce values which will improve
+;; performance or otherwise avoid undesirable behaviours.  If `so-long-revert'
+;; is called, then the original values are restored.
+
+;; Hooks
+;; -----
+;; `so-long-mode-hook' is the standard major mode hook, which runs between
+;; `change-major-mode-after-body-hook' and `after-change-major-mode-hook' if
+;; `so-long-mode' is invoked.
+;;
+;; `so-long-hook' runs at the end of `so-long'.  Note that for the default
+;; action `so-long-mode', this means globalized minor modes have finished 
acting
+;; for the new major mode.
+;;
+;; Likewise, if the `so-long-revert' command is used to restore the original
+;; state then, once that has happened, `so-long-revert-hook' is run.  This 
could
+;; be used to undo the effects of the previous hooks.
+
+;; Troubleshooting
+;; ---------------
+;; Any elisp library has the potential to cause performance problems; so while
+;; the default configuration addresses some important common cases, it's
+;; entirely possible that your own config introduces problem cases which are
+;; unknown to this library.
+;;
+;; If visiting a file is still taking a very long time with so-long enabled,
+;; you should test the following command:
+;;
+;; emacs -Q -l /path/to/so-long.el -f so-long-enable <file>
+;;
+;; If the file loads quickly when that command is used, you'll know that
+;; something in your personal configuration is causing problems.  If this
+;; turns out to be a buffer-local minor mode, or a user option, you can
+;; likely alleviate the issue by customizing `so-long-minor-modes' or
+;; `so-long-variable-overrides' accordingly.
+;;
+;; In some cases it may be useful to set a file-local `mode' variable to
+;; `so-long-mode', completely bypassing the automated decision process.
+;;
+;; If so-long itself is causing problems, it can be inhibited by setting the
+;; `so-long-enabled' variable to nil, or disabled entirely by calling the
+;; `so-long-disable' command.
+
+;; Example configuration
+;; ---------------------
+;; If you prefer to configure in code rather than via the customize interface,
+;; then you might use something along these lines:
+;;
+;; ;; Enable so-long library.
+;; (when (require 'so-long nil :noerror)
+;;   (so-long-enable)
+;;   ;; Basic settings.
+;;   (setq so-long-action 'overrides-only)
+;;   (setq so-long-threshold 1000)
+;;   (setq so-long-max-lines 100)
+;;   ;; Additional target major modes to trigger for.
+;;   (mapc (apply-partially 'add-to-list 'so-long-target-modes)
+;;         '(sgml-mode nxml-mode))
+;;   ;; Additional buffer-local minor modes to disable.
+;;   (mapc (apply-partially 'add-to-list 'so-long-minor-modes)
+;;         '(diff-hl-mode diff-hl-amend-mode diff-hl-flydiff-mode))
+;;   ;; Additional variables to override.
+;;   (mapc (apply-partially 'add-to-list 'so-long-variable-overrides)
+;;         '((show-trailing-whitespace . nil)
+;;           (truncate-lines . nil))))
+
+;; Other ways of using so-long
+;; ---------------------------
+;; It may prove useful to automatically invoke `so-long-mode' and its
+;; performance benefits for certain files, irrespective of whether they
+;; contain long lines.
+;;
+;; To target specific files and extensions, using `auto-mode-alist' is the
+;; simplest method.  To add such an entry, use:
+;; (add-to-list 'auto-mode-alist (cons REGEXP 'so-long-mode))
+;; Where REGEXP is a regular expression matching the filename.  e.g.:
+;;
+;; * Any filename with a particular extension ".foo":
+;;   (rx ".foo" eos)
+;;
+;; * Any file in a specific directory:
+;;   (rx bos "/path/to/directory/")
+;;
+;; * Only *.c filenames under that directory:
+;;   (rx bos "/path/to/directory/" (zero-or-more not-newline) ".c" eos)
+;;
+;; * Match some sub-path anywhere in a filename:
+;;   (rx "/sub/path/foo")
+;;
+;; In Emacs 26.1 or later (see "Caveats" below) you also have the option of
+;; using file-local and directory-local variables to determine how so-long
+;; behaves.  e.g. -*- so-long-action: longlines-mode; -*-
+;;
+;; The buffer-local `so-long-function' and `so-long-revert-function' values may
+;; be set directly (in a major mode hook, for instance), as any existing value
+;; for these variables will be used in preference to the values defined by the
+;; selected action.  For file-local or directory-local usage it is preferable 
to
+;; set only `so-long-action', as all function variables are marked as 'risky',
+;; meaning you would need to add to `safe-local-variable-values' in order to
+;; avoid being queried about them.
+;;
+;; Finally, the `so-long-predicate' user option enables the automated behaviour
+;; to be determined by a custom function, if greater control is needed.
+
+;; Implementation notes
+;; --------------------
+;; This library advises `set-auto-mode' (in order to react after Emacs has
+;; chosen the major mode for a buffer), and `hack-local-variables' (so that we
+;; may behave differently when a file-local mode is set).  In earlier versions
+;; of Emacs (< 26.1) we also advise `hack-one-local-variable' (to prevent a
+;; file-local mode from restoring the original major mode if we had changed 
it).
+;;
+;; Many variables are permanent-local because after the normal major mode has
+;; been set, we potentially change the major mode to `so-long-mode', and it's
+;; important that the values which were established prior to that are retained.
+
+;; Caveats
+;; -------
+;; The variables affecting the automated behavior of this library (such as
+;; `so-long-action') can be used as file- or dir-local values in Emacs 26+, but
+;; not in previous versions of Emacs.  This is on account of improvements made
+;; to `normal-mode' in 26.1, which altered the execution order with respect to
+;; when local variables are processed.  In earlier versions of Emacs the local
+;; variables are processed too late, and hence have no effect on the decision-
+;; making process for invoking `so-long'.  It is unlikely that equivalent
+;; support will be implemented for older versions of Emacs.  The exception to
+;; this caveat is the `mode' pseudo-variable, which is processed early in all
+;; versions of Emacs, and can be set to `so-long-mode' if desired.
+
+;;; Change Log:
+;;
+;; 1.0   - New user option `so-long-action'.
+;;       - New user option `so-long-action-alist' defining alternative actions.
+;;       - New user option `so-long-variable-overrides'.
+;;       - New user option `so-long-skip-leading-comments'.
+;;       - New user option `so-long-file-local-mode-function'.
+;;       - New user option `so-long-predicate'.
+;;       - New variable and function `so-long-function'.
+;;       - New variable and function `so-long-revert-function'.
+;;       - New command `so-long' to invoke `so-long-function' interactively.
+;;       - New command `so-long-revert' to invoke `so-long-revert-function'.
+;;       - Support retaining the original major mode while still disabling
+;;         minor modes and overriding variables.
+;;       - Support `longlines-mode' as a `so-long-action' option.
+;;       - Added "So Long" menu, including all selectable actions.
+;;       - Added mode-line indicator, user option `so-long-mode-line-label',
+;;         and faces `so-long-mode-line-active', `so-long-mode-line-inactive'.
+;;       - New help commands `so-long-commentary' and `so-long-customize'.
+;;       - Renamed `so-long-mode-enabled' to `so-long-enabled'.
+;;       - Refactored the default hook values using variable overrides
+;;         (and returning all the hooks to nil default values).
+;;       - Performance improvements for `so-long-detected-long-line-p'.
+;; 0.7.6 - Bug fix for `so-long-mode-hook' losing its default value.
+;; 0.7.5 - Documentation.
+;;       - Added sgml-mode and nxml-mode to `so-long-target-modes'.
+;; 0.7.4 - Refactored the handling of `whitespace-mode'.
+;; 0.7.3 - Added customize group `so-long' with user options.
+;;       - Added `so-long-original-values' to generalise the storage and
+;;         restoration of values from the original mode upon `so-long-revert'.
+;;       - Added `so-long-revert-hook'.
+;; 0.7.2 - Remember the original major mode even with M-x `so-long-mode'.
+;; 0.7.1 - Clarified interaction with globalized minor modes.
+;; 0.7   - Handle header 'mode' declarations.
+;;       - Hack local variables after reverting to the original major mode.
+;;       - Reverted `so-long-max-lines' to a default value of 5.
+;; 0.6.5 - Inhibit globalized `hl-line-mode' and `whitespace-mode'.
+;;       - Set `buffer-read-only' by default.
+;; 0.6   - Added `so-long-minor-modes' and `so-long-hook'.
+;; 0.5   - Renamed library to "so-long.el".
+;;       - Added explicit `so-long-enable' command to activate our advice.
+;; 0.4   - Amended/documented behaviour with file-local 'mode' variables.
+;; 0.3   - Defer to a file-local 'mode' variable.
+;; 0.2   - Initial release to EmacsWiki.
+;; 0.1   - Experimental.
+
+;;; Code:
+
+(add-to-list 'customize-package-emacs-version-alist
+             '(so-long ("1.0" . "27.1")))
+
+(declare-function longlines-mode "longlines")
+(defvar longlines-mode)
+
+(defvar so-long-enabled t
+  "Set to nil to prevent `so-long' from being triggered automatically.")
+
+(defvar-local so-long--active nil ; internal use
+  "Non-nil when `so-long' mitigations are in effect.")
+
+(defvar so-long--set-auto-mode nil ; internal use
+  "Non-nil while `set-auto-mode' is executing.")
+
+(defvar-local so-long--inhibited nil ; internal use
+  "When non-nil, prevents the `set-auto-mode' advice from calling `so-long'.")
+(put 'so-long--inhibited 'permanent-local t)
+
+(defvar so-long--calling nil ; internal use
+  ;; This prevents infinite recursion if eval:(so-long) is specified
+  ;; as a file- or dir-local variable, and `so-long-action' is set to
+  ;; `so-long-mode' (as that major mode would once again process the
+  ;; local variables, and hence call itself).
+  "Non-nil while `so-long' or `so-long-revert' is executing.")
+
+(defvar-local so-long-detected-p nil
+  "Non-nil if `so-long' has been invoked (even if subsequently reverted).")
+(put 'so-long-detected-p 'permanent-local t)
+
+(defgroup so-long nil
+  "Prevent unacceptable performance degradation with very long lines."
+  :prefix "so-long"
+  :group 'convenience)
+
+(defcustom so-long-threshold 250
+  "Maximum line length permitted before invoking `so-long-function'.
+
+See `so-long-detected-long-line-p' for details."
+  :type 'integer
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+
+(defcustom so-long-max-lines 5
+  "Number of non-blank, non-comment lines to test for excessive length.
+
+If nil then all lines will be tested, until either a long line is detected,
+or the end of the buffer is reached.
+
+If `so-long-skip-leading-comments' is nil then comments and blank lines will
+be counted.
+
+See `so-long-detected-long-line-p' for details."
+  :type '(choice (integer :tag "Limit")
+                 (const :tag "Unlimited" nil))
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+
+(defcustom so-long-skip-leading-comments t
+  "Non-nil to ignore all leading comments and whitespace.
+
+See `so-long-detected-long-line-p' for details."
+  :type 'boolean
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+
+(defcustom so-long-target-modes
+  '(prog-mode css-mode sgml-mode nxml-mode)
+  "`so-long' affects only these modes and their derivatives.
+
+Our primary use-case is minified programming code, so `prog-mode' covers
+most cases, but there are some exceptions to this.
+
+If t, then so-long affects all modes."
+  ;; Use 'symbol', as 'function' may be unknown => mismatch.
+  :type '(choice (repeat :tag "Specified modes" symbol)
+                 (const :tag "All modes" t))
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+
+(defcustom so-long-predicate 'so-long-detected-long-line-p
+  "Function, called after `set-auto-mode' to decide whether action is needed.
+
+Only called if the major mode is a member of `so-long-target-modes'.
+
+If the function returns non-nil, `so-long' will be invoked.
+
+Defaults to `so-long-detected-long-line-p'."
+  :type '(choice (const so-long-detected-long-line-p)
+                 (function :tag "Custom function"))
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+
+;; Silence byte-compiler warning.  `so-long-action-alist' is defined below
+;; as a user option; but the definition sequence required for its setter
+;; function means we also need to declare it beforehand.
+(defvar so-long-action-alist)
+
+(defun so-long-action-type ()
+  "Generate a :type for `so-long-action' based on `so-long-action-alist'."
+  ;; :type seemingly cannot be a form to be evaluated on demand, so we
+  ;; endeavour to keep it up-to-date with `so-long-action-alist' by
+  ;; calling this from `so-long-action-alist-setter'.
+  `(radio ,@(mapcar (lambda (x) (list 'const :tag (cadr x) (car x)))
+                    so-long-action-alist)
+          (const :tag "Do nothing" nil)))
+
+(defun so-long-action-alist-setter (option value)
+  "The :set function for `so-long-action-alist'."
+  ;; Set the value as normal.
+  (set-default option value)
+  ;; Update the :type of `so-long-action' to present the updated values.
+  (put 'so-long-action 'custom-type (so-long-action-type)))
+
+(defcustom so-long-action-alist
+  '((so-long-mode
+     "Change major mode to so-long-mode"
+     so-long-mode
+     so-long-mode-revert)
+    (overrides-only
+     "Disable minor modes and override variables"
+     so-long-function-overrides-only
+     so-long-revert-function-overrides-only)
+    (longlines-mode
+     "Enable longlines-mode"
+     so-long-function-longlines-mode
+     so-long-revert-function-longlines-mode))
+  "Options for `so-long-action'.
+
+Each element is a list comprising (KEY LABEL ACTION REVERT)
+
+KEY is a symbol which is a valid value for `so-long-action', and LABEL is a
+string which describes and represents the key in that option's customize
+interface, and in the \"So Long\" menu.  ACTION and REVERT are functions:
+
+ACTION will be the `so-long-function' value when `so-long' is called, and
+REVERT will be the `so-long-revert-function' value, if `so-long-revert' is
+subsequently called."
+  :type '(alist :key-type (symbol :tag "Key" :value action)
+                :value-type (list (string :tag "Label" :value "description")
+                                  (function :tag "Action")
+                                  (function :tag "Revert")))
+  :set #'so-long-action-alist-setter
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+(put 'so-long-action-alist 'risky-local-variable t)
+
+(defcustom so-long-action 'so-long-mode
+  "The action taken by `so-long' when long lines are detected.
+
+\(Long lines are determined by `so-long-predicate' after `set-auto-mode'.)
+
+The value is a key to one of the options defined by `so-long-action-alist'.
+
+The default action is to replace the original major mode with `so-long-mode'.
+Alternatively, `overrides-only' retains the original major mode while still
+disabling minor modes and overriding variables.  These are the only standard
+values for which `so-long-minor-modes' and `so-long-variable-overrides' will
+be automatically processed; but custom actions can also do these things.
+
+The value `longlines-mode' causes that minor mode to be enabled.  See
+longlines.el for more details.
+
+Each action likewise determines the behaviour of `so-long-revert'.
+
+If the value is nil, or not defined in `so-long-action-alist', then no action
+will be taken."
+  :type (so-long-action-type)
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+
+(defvar-local so-long-function nil
+  "The function called by `so-long'.
+
+This should be set in conjunction with `so-long-revert-function'.  This usually
+happens automatically, based on the value of `so-long-action'.
+
+The specified function will be called with no arguments, after which
+`so-long-hook' runs.")
+(put 'so-long-function 'permanent-local t)
+
+(defvar-local so-long-revert-function nil
+  "The function called by `so-long-revert'.
+
+This should be set in conjunction with `so-long-function'.  This usually
+happens automatically, based on the value of `so-long-action'.
+
+The specified function will be called with no arguments, after which
+`so-long-revert-hook' runs.")
+(put 'so-long-revert-function 'permanent-local t)
+
+(defun so-long-function ()
+  "The value of `so-long-function', else derive from `so-long-action'."
+  (or so-long-function
+      (and so-long-action
+           (let ((action (assq so-long-action so-long-action-alist)))
+             (nth 2 action)))))
+
+(defun so-long-revert-function ()
+  "The value of `so-long-revert-function', else derive from `so-long-action'."
+  (or so-long-revert-function
+      (and so-long-action
+           (let ((action (assq so-long-action so-long-action-alist)))
+             (nth 3 action)))))
+
+(defcustom so-long-file-local-mode-function 'so-long-mode-downgrade
+  "Function to call during `set-auto-mode' when a file-local mode is set.
+
+The specified function will be called with a single argument, being the
+file-local mode which was established.
+
+This happens before `so-long' is called, and so this function can modify the
+subsequent action.
+
+The value `so-long-mode-downgrade' means that `so-long-function-overrides-only'
+will be used in place of `so-long-mode' -- respecting the file-local mode, but
+still overriding minor modes and variables, as if `so-long-action' had been set
+to `overrides-only'.
+
+The value `so-long-inhibit' means that so-long will not take any action at all
+for this file.
+
+If nil, then do not treat files with file-local modes any differently to other
+files.
+
+Note that this function is called if a file-local mode is set even if `so-long'
+will not be called.  The exception to this is when the file-local mode is
+`so-long-mode', in which case `so-long-file-local-mode-function' is ignored,
+as the required action is unambiguous."
+  :type '(radio (const so-long-mode-downgrade)
+                (const so-long-inhibit)
+                (const :tag "nil: Use so-long-function as normal" nil)
+                (function :tag "Custom function"))
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+(make-variable-buffer-local 'so-long-file-local-mode-function)
+
+;; `provided-mode-derived-p' was added in 26.1
+(unless (fboundp 'provided-mode-derived-p)
+  (defun provided-mode-derived-p (mode &rest modes)
+    "Non-nil if MODE is derived from one of MODES.
+Uses the `derived-mode-parent' property of the symbol to trace backwards.
+If you just want to check `major-mode', use `derived-mode-p'."
+    (while (and (not (memq mode modes))
+                (setq mode (get mode 'derived-mode-parent))))
+    mode))
+
+(defun so-long-handle-file-local-mode (mode)
+  "Wrapper for calling `so-long-file-local-mode-function'.
+
+The function is called with one argument, MODE, being the file-local mode which
+was established."
+  ;; Handle the special case whereby the file-local mode was `so-long-mode'.
+  ;; In this instance we set `so-long--inhibited', because the file-local mode
+  ;; is already going to do everything that is wanted.
+  (if (provided-mode-derived-p mode 'so-long-mode)
+      (setq so-long--inhibited t)
+    ;; Call `so-long-file-local-mode-function'.
+    (when so-long-file-local-mode-function
+      (funcall so-long-file-local-mode-function mode))))
+
+(defcustom so-long-minor-modes
+  ;; In sorted groups.
+  '(font-lock-mode ;; (Generally the most important).
+    ;; Other standard minor modes:
+    display-line-numbers-mode
+    hi-lock-mode
+    highlight-changes-mode
+    hl-line-mode
+    linum-mode
+    nlinum-mode
+    prettify-symbols-mode
+    visual-line-mode
+    whitespace-mode
+    ;; Known third-party modes-of-interest:
+    diff-hl-amend-mode
+    diff-hl-flydiff-mode
+    diff-hl-mode
+    dtrt-indent-mode
+    hl-sexp-mode
+    idle-highlight-mode
+    rainbow-delimiters-mode
+    )
+  ;; It's not clear to me whether all of these would be problematic, but they
+  ;; seemed like reasonable targets.  Some are certainly excessive in smaller
+  ;; buffers of minified code, but we should be aiming to maximise performance
+  ;; by default, so that Emacs is as responsive as we can manage in even very
+  ;; large buffers of minified code.
+  "List of buffer-local minor modes to explicitly disable.
+
+The ones which were originally enabled in the buffer are disabled by calling
+them with the numeric argument 0.  Unknown modes, and modes which were were not
+enabled, are ignored.
+
+This happens after any globalized minor modes have acted, so that buffer-local
+modes controlled by globalized modes can also be targeted.
+
+By default this happens if `so-long-action' is set to either `so-long-mode'
+or `overrides-only'.  If `so-long-revert' is subsequently invoked, then the
+disabled modes are re-enabled by calling them with the numeric argument 1.
+
+`so-long-hook' can be used where more custom behaviour is desired.
+
+Please submit bug reports to recommend additional modes for this list, whether
+they are in Emacs core, GNU ELPA, or elsewhere."
+  :type '(repeat symbol) ;; not function, as may be unknown => mismatch.
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+
+(defcustom so-long-variable-overrides
+  '((bidi-display-reordering . nil)
+    (buffer-read-only . t)
+    (global-hl-line-mode . nil)
+    (line-move-visual . t)
+    (truncate-lines . nil)
+    (which-func-mode . nil))
+  "Variables to override, and the values to override them with.
+
+The variables are given buffer-local values.  By default this happens if
+`so-long-action' is set to either `so-long-mode' or `overrides-only'.  If
+`so-long-revert' is subsequently invoked, then the variables are restored to
+their original states."
+  :type '(alist :key-type (variable :tag "Variable")
+                :value-type (sexp :tag "Value"))
+  :options '((bidi-display-reordering boolean)
+             (buffer-read-only boolean)
+             (global-hl-line-mode boolean)
+             (line-move-visual boolean)
+             (truncate-lines boolean)
+             (which-func-mode boolean))
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+
+(defcustom so-long-hook nil
+  "List of functions to call after `so-long' is called.
+
+See also `so-long-revert-hook'."
+  :type 'hook
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+
+(defcustom so-long-revert-hook nil
+  "List of functions to call after `so-long-mode-revert' is called.
+
+See also `so-long-hook'."
+  :type 'hook
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+
+(defcustom so-long-mode-line-label "So Long"
+  "Text label of `so-long-mode-line-info' when long lines are detected.
+
+If nil, no mode line indicator will be displayed."
+  :type '(choice (string :tag "String")
+                 (const :tag "None" nil))
+  :package-version '(so-long . "1.0")
+  :group 'so-long)
+
+(defface so-long-mode-line-active
+  '((t :inherit mode-line-emphasis))
+  "Face for `so-long-mode-line-info' when mitigations are active."
+  :group 'so-long)
+
+(defface so-long-mode-line-inactive
+  '((t :inherit mode-line-inactive))
+  "Face for `so-long-mode-line-info' when mitigations have been reverted."
+  :group 'so-long)
+
+;; Modes that go slowly and line lengths excessive
+;; Font-lock performance becoming oppressive
+;; All of my CPU tied up with strings
+;; These are a few of my least-favourite things
+
+(defvar-local so-long-original-values nil
+  "Alist holding the buffer's original `major-mode' value, and other data.
+
+Any values to be restored by `so-long-revert' can be stored here by the
+`so-long-function' or during `so-long-hook'.  `so-long' itself stores the
+original states for `so-long-variable-overrides' and `so-long-minor-modes',
+so these values are available to custom actions by default.
+
+See also `so-long-remember' and `so-long-original'.")
+(put 'so-long-original-values 'permanent-local t)
+
+(defun so-long-original (key &optional exists)
+  "Return the current value for KEY in `so-long-original-values'.
+
+If you need to differentiate between a stored value of nil and no stored value
+at all, make EXISTS non-nil.  This then returns the result of `assq' directly:
+nil if no value was set, and a cons cell otherwise."
+  (if exists
+      (assq key so-long-original-values)
+    (cadr (assq key so-long-original-values))))
+
+(defun so-long-remember (variable)
+  "Push the `symbol-value' for VARIABLE to `so-long-original-values'."
+  (when (boundp variable)
+    (setq so-long-original-values
+          (assq-delete-all variable so-long-original-values))
+    (push (list variable
+                (symbol-value variable)
+                (consp (assq variable (buffer-local-variables))))
+          so-long-original-values)))
+
+(defun so-long-change-major-mode ()
+  "Ensures that `so-long-mode' knows the original `major-mode'
+even when invoked interactively.
+
+Called during `change-major-mode-hook'."
+  (unless (or (minibufferp)
+              (derived-mode-p 'so-long-mode))
+    (so-long-remember 'major-mode)))
+
+(defun so-long-menu ()
+  "Dynamically generate the \"So Long\" menu."
+  ;; (info "(elisp) Menu Example")
+  (let ((map (make-sparse-keymap "So Long"))
+        (help-map (make-sparse-keymap "Help")))
+    ;; `so-long-revert'.
+    (define-key-after map [so-long-revert]
+      '(menu-item "Revert to normal" so-long-menu-item-revert
+                  :enable (and so-long-revert-function
+                               so-long--active)))
+    ;; `so-long-menu-item-replace-action' over `so-long-action-alist'.
+    (define-key-after map [so-long-actions-separator]
+      '(menu-item "--"))
+    (dolist (item so-long-action-alist)
+      (cl-destructuring-bind (key label actionfunc revertfunc)
+          item
+        (define-key-after map (vector key)
+          `(menu-item
+            ,label (lambda ()
+                     (interactive)
+                     (so-long-menu-item-replace-action ',item))
+            :enable (not (and so-long--active
+                              (eq ',actionfunc so-long-function)
+                              (eq ',revertfunc so-long-revert-function)))))))
+    ;; "Help" sub-menu.
+    (define-key-after map [so-long-help-separator]
+      '(menu-item "--"))
+    (define-key-after map [so-long-help]
+      `(menu-item "Help" ,help-map))
+    (define-key-after help-map [so-long-commentary]
+      '(menu-item "Commentary" so-long-commentary))
+    (define-key-after help-map [so-long-customize]
+      '(menu-item "Customize" so-long-customize))
+    map))
+
+(defun so-long-menu-click-window ()
+  "Return the window for a click in the So Long menu.
+
+Commands in the mode-line menu may be triggered by mouse when some other window
+is selected, so we need to make sure we are acting on the correct buffer."
+  ;; Refer to (info "(elisp) Click Events") regarding the form of the mouse
+  ;; position list for clicks in the mode line.
+  (or (and (mouse-event-p last-nonmenu-event)
+           (windowp (car (cadr last-nonmenu-event))) ; cXXXr only available
+           (car (cadr last-nonmenu-event)))          ; since Emacs 26.1
+      (selected-window)))
+
+(defun so-long-menu-item-revert ()
+  "Invoke `so-long-revert'."
+  (interactive)
+  (with-selected-window (so-long-menu-click-window)
+    (so-long-revert)))
+
+(defun so-long-menu-item-replace-action (replacement)
+  "Revert the current action and invoke the specified replacement.
+
+REPLACEMENT is a `so-long-action-alist' item."
+  (interactive)
+  (with-selected-window (so-long-menu-click-window)
+    (when so-long--active
+      (so-long-revert))
+    (cl-destructuring-bind (_key _label actionfunc revertfunc)
+        replacement
+      (setq so-long-function actionfunc)
+      (setq so-long-revert-function revertfunc)
+      (setq this-command 'so-long)
+      (so-long))))
+
+;;;###autoload
+(defun so-long-commentary ()
+  "View the so-long documentation using `finder-commentary'."
+  (interactive)
+  (finder-commentary "so-long"))
+
+;;;###autoload
+(defun so-long-customize ()
+  "Open the so-long `customize' group."
+  (interactive)
+  (customize-group 'so-long))
+
+(defvar-local so-long-mode-line-info nil
+  "Mode line construct displayed when `so-long' has been triggered.
+
+Displayed as part of `mode-line-misc-info'.
+
+`so-long-mode-line-label' defines the text to be displayed (if any).
+
+Face `so-long-mode-line-active' is used while mitigations are active, and
+`so-long-mode-line-inactive' is used if `so-long-revert' is called.
+
+Not displayed when `so-long-mode' is enabled, as the major mode construct
+serves the same purpose.")
+
+;; Ensure we can display text properties on this value in the mode line.
+;; See (info "(elisp) Mode Line Data") or (info "(elisp) Properties in Mode").
+(put 'so-long-mode-line-info 'risky-local-variable t)
+
+(defun so-long-mode-line-info ()
+  "Returns the mode line construct for variable `so-long-mode-line-info'."
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "<mode-line> <down-mouse-1>")
+      `(menu-item "" nil
+                  :filter ,(lambda (_cmd) (so-long-menu))))
+    ;; Mode line construct.
+    ;; n.b. It's necessary for `so-long-mode-line-info' to have a non-nil
+    ;; risky-local-variable property, as otherwise the text properties won't
+    ;; be rendered.
+    `(so-long-mode-line-label
+      ("" (:eval (propertize so-long-mode-line-label
+                             'mouse-face 'highlight
+                             'keymap ',map
+                             'help-echo t ;; Suppress the mode-line value
+                             'face (if so-long--active
+                                       'so-long-mode-line-active
+                                     'so-long-mode-line-inactive)))
+       " "))))
+
+;; When the line's long
+;; When the mode's slow
+;; When Emacs is sad
+;; We change automatically to faster code
+;; And then I won't feel so mad
+
+(defun so-long-detected-long-line-p ()
+  "Following any initial comments and blank lines, the next N lines of the
+buffer will be tested for excessive length (where \"excessive\" means above
+`so-long-threshold', and N is `so-long-max-lines').
+
+Returns non-nil if any such excessive-length line is detected.
+
+If `so-long-skip-leading-comments' is nil then the N lines will be counted
+starting from the first line of the buffer.  In this instance you will likely
+want to increase `so-long-max-lines' to allow for possible comments.
+
+This is the default value of `so-long-predicate'."
+  (let ((count 0) start)
+    (save-excursion
+      (goto-char (point-min))
+      (when so-long-skip-leading-comments
+        ;; Clears whitespace at minimum.
+        ;; We use narrowing to limit the amount of text being processed at any
+        ;; given time, where possible, as this makes things more efficient.
+        (setq start (point))
+        (while (save-restriction
+                 (narrow-to-region start (min (+ (point) so-long-threshold)
+                                              (point-max)))
+                 (goto-char start)
+                 ;; Possibilities for `comment-forward' are:
+                 ;; 0. No comment; no movement; return nil.
+                 ;; 1. Comment is <= point-max; move end of comment; return t.
+                 ;; 2. Comment is truncated; move point-max; return nil.
+                 ;; 3. Only whitespace; move end of WS; return nil.
+                 (prog1 (or (comment-forward 1) ;; Moved past a comment.
+                            (and (eobp) ;; Truncated, or WS up to point-max.
+                                 (progn ;; Widen and retry.
+                                   (widen)
+                                   (goto-char start)
+                                   (comment-forward 1))))
+                   ;; Otherwise there was no comment, and we return nil.
+                   ;; If there was whitespace, we moved past it.
+                   (setq start (point)))))
+        ;; We're at the first non-comment line, but we may have moved past
+        ;; indentation whitespace, so move back to the beginning of the line.
+        (forward-line 0))
+      ;; Start looking for long lines.
+      ;; `while' will ultimately return nil if we do not `throw' a result.
+      (catch 'excessive
+        (while (and (not (eobp))
+                    (or (not so-long-max-lines)
+                        (< count so-long-max-lines)))
+          (setq start (point))
+          (save-restriction
+            (narrow-to-region start (min (+ start 1 so-long-threshold)
+                                         (point-max)))
+            (forward-line 1))
+          ;; If point is not now at the beginning of a line, then the previous
+          ;; line was long -- with the exception of when point is at the end of
+          ;; the buffer (bearing in mind that we have widened again), in which
+          ;; case there was a short final line with no newline.  There is an
+          ;; edge case when such a final line is exactly (1+ so-long-threshold)
+          ;; chars long, so if we're at (eobp) we need to verify the length in
+          ;; order to be consistent.
+          (unless (or (bolp)
+                      (and (eobp) (<= (- (point) start)
+                                      so-long-threshold)))
+            (throw 'excessive t))
+          (setq count (1+ count)))))))
+
+(defun so-long-function-longlines-mode ()
+  "Enable minor mode `longlines-mode'.
+
+This is a `so-long-function' option."
+  (require 'longlines)
+  (so-long-remember 'longlines-mode)
+  (longlines-mode 1))
+
+(defun so-long-function-overrides-only ()
+  "Disable minor modes and override variables, but retain the major mode.
+
+This is a `so-long-function' option."
+  (so-long-disable-minor-modes)
+  (so-long-override-variables))
+
+;; How do you solve a problem like a long line?
+;; How do you stop a mode from slowing down?
+;; How do you cope with processing a long line?
+;; A bit of advice! A mode! A workaround!
+
+(defvar so-long-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "C-c C-c") 'so-long-revert)
+    ;; Define the major mode menu.  We have an awkward issue whereby
+    ;; [menu-bar so-long] is already defined in the global map and is
+    ;; :visible so-long-detected-p, but we also want this to be
+    ;; available via the major mode construct in the mode line.
+    ;; The following achieves the desired end result, as :visible nil
+    ;; prevents this from duplicating its contents in the menu bar,
+    ;; but still includes it in the mode line.
+    (define-key map [menu-bar so-long]
+      `(menu-item "" nil
+                  :visible nil
+                  :filter ,(lambda (_cmd) (so-long-menu))))
+    map)
+  "Major mode keymap and menu for `so-long-mode'.")
+
+(define-derived-mode so-long-mode nil "So Long"
+  "This major mode is the default `so-long-action' option.
+
+Many Emacs modes struggle with buffers which contain excessively long lines,
+and may consequently cause unacceptable performance issues.
+
+This is commonly on account of 'minified' code (i.e. code has been compacted
+into the smallest file size possible, which often entails removing newlines
+should they not be strictly necessary).  These kinds of files are typically
+not intended to be edited, so not providing the usual editing mode in these
+cases will rarely be an issue.
+
+When such files are detected, we invoke this mode.  This happens after
+`set-auto-mode' has set the major mode, should the selected major mode be
+a member (or derivative of a member) of `so-long-target-modes'.
+
+After changing modes, any active minor modes listed in `so-long-minor-modes'
+are disabled for the current buffer, and buffer-local values are assigned to
+variables in accordance with `so-long-variable-overrides'.  These steps occur
+in `after-change-major-mode-hook', so that minor modes controlled by globalized
+minor modes can also be disabled.
+
+Some globalized minor modes may be inhibited by acting in `so-long-mode-hook'.
+
+By default this mode is essentially equivalent to `fundamental-mode', and
+exists mainly to provide information to the user as to why the expected mode
+was not used, and to facilitate hooks for other so-long functionality.
+
+To revert to the original mode despite any potential performance issues,
+type \\[so-long-mode-revert], or else re-invoke it manually."
+  ;; Housekeeping.  `so-long-mode' might be invoked directly rather than via
+  ;; `so-long', so duplicate the critical settings.  We could use this same 
test
+  ;; in `so-long-after-change-major-mode' to run `so-long-hook', but that's not
+  ;; so obviously the right thing to do, so I've omitted it for now.
+  (unless so-long--calling
+    (setq so-long--active t
+          so-long-detected-p t
+          so-long-original-values nil
+          so-long-function 'so-long-mode
+          so-long-revert-function 'so-long-mode-revert))
+  (unless (so-long-original 'major-mode)
+    (so-long-remember 'major-mode))
+  ;; Use `after-change-major-mode-hook' to disable minor modes and override
+  ;; variables, so that we act after any globalized modes have acted.
+  (add-hook 'after-change-major-mode-hook
+            'so-long-after-change-major-mode :append :local)
+  ;; Override variables.  This is the first of two instances where we do this
+  ;; (the other being `so-long-after-change-major-mode').  It is desirable to
+  ;; set variables here in order to cover cases where the setting of a variable
+  ;; influences how a global minor mode behaves in this buffer.
+  (so-long-override-variables)
+  ;; Hide redundant mode-line information (our major mode info replicates 
this).
+  (setq so-long-mode-line-info nil)
+  ;; Inform the user about our major mode hijacking.
+  (unless (or so-long--inhibited so-long--set-auto-mode)
+    (message (concat "Changed to %s (from %s)"
+                     (unless (memq this-command '(so-long so-long-mode))
+                       " on account of line length")
+                     ".  %s to revert.")
+             major-mode
+             (or (so-long-original 'major-mode) "<unknown>")
+             (substitute-command-keys "\\[so-long-revert]"))))
+
+(defun so-long-after-change-major-mode ()
+  "Run by `so-long-mode' in `after-change-major-mode-hook'.
+
+Calls `so-long-disable-minor-modes' and `so-long-override-variables'."
+  ;; Disable minor modes.
+  (so-long-disable-minor-modes)
+  ;; Override variables (again).  We already did this in `so-long-mode' in
+  ;; order that variables which affect global/globalized minor modes can have
+  ;; that effect; however it's feasible that one of the minor modes disabled
+  ;; above might have reverted one of these variables, so we re-enforce them.
+  ;; (For example, disabling `visual-line-mode' sets `line-move-visual' to
+  ;; nil, when for our purposes it is preferable for it to be non-nil).
+  (so-long-override-variables))
+
+(defun so-long-disable-minor-modes ()
+  "Disable any active minor modes listed in `so-long-minor-modes'."
+  (dolist (mode so-long-minor-modes)
+    (when (and (boundp mode) mode)
+      (funcall mode 0))))
+
+(defun so-long-restore-minor-modes ()
+  "Restore the minor modes which were disabled.
+
+The modes are enabled in accordance with what was remembered in `so-long'."
+  (dolist (mode so-long-minor-modes)
+    (when (and (so-long-original mode)
+               (boundp mode)
+               (not (symbol-value mode)))
+      (funcall mode 1))))
+
+(defun so-long-override-variables ()
+  "Process `so-long-variable-overrides'."
+  (dolist (ovar so-long-variable-overrides)
+    (set (make-local-variable (car ovar)) (cdr ovar))))
+
+(defun so-long-restore-variables ()
+  "Restore the remembered values for the overridden variables."
+  (dolist (ovar so-long-variable-overrides)
+    (so-long-restore-variable (car ovar))))
+
+(defun so-long-restore-variable (variable)
+  "Restore the remembered value (if any) for VARIABLE."
+  ;; In the instance where `so-long-mode-revert' has just reverted the major
+  ;; mode, note that `kill-all-local-variables' was already called by the
+  ;; original mode function, and so these 'overridden' variables may now have
+  ;; global rather than buffer-local values.
+  (let* ((remembered (so-long-original variable :exists))
+         (originally-local (nth 2 remembered)))
+    (if originally-local
+        ;; The variable originally existed with a buffer-local value, so we
+        ;; restore it as such (even if the global value is a match).
+        (set (make-local-variable variable) (cadr remembered))
+      ;; Either this variable did not exist initially, or it did not have a
+      ;; buffer-local value at that time.  In either case we kill the current
+      ;; buffer-local value (if any) in order to restore the original state.
+      ;;
+      ;; It's possible that the global value has *changed* in the interim; but
+      ;; we can't know whether it's best to use the new global value, or retain
+      ;; the old value as a buffer-local value, so we keep it simple.
+      (kill-local-variable variable))))
+
+(defun so-long-mode-revert ()
+  "Call the `major-mode' which was selected before `so-long-mode' replaced it.
+
+Re-process local variables, and restore overridden variables and minor modes."
+  (interactive)
+  (let ((so-long-original-mode (so-long-original 'major-mode)))
+    (unless so-long-original-mode
+      (error "Original mode unknown."))
+    (funcall so-long-original-mode)
+    ;; Emacs 26+ has already called `hack-local-variables' (during
+    ;; `run-mode-hooks'), but for older versions we need to call it here.
+    (when (< emacs-major-version 26)
+      (hack-local-variables))
+    ;; Restore minor modes.
+    (so-long-restore-minor-modes)
+    ;; Restore overridden variables.
+    ;; `kill-all-local-variables' was already called by the original mode
+    ;; function, so we may be seeing global values.
+    (so-long-restore-variables)
+    ;; Restore the mode line construct.
+    (unless (derived-mode-p 'so-long-mode)
+      (setq so-long-mode-line-info (so-long-mode-line-info)))))
+
+(defun so-long-revert-function-overrides-only ()
+  "Restore original state of the overridden minor modes and variables."
+  (so-long-restore-minor-modes)
+  (so-long-restore-variables))
+
+(defun so-long-revert-function-longlines-mode ()
+  "Restore original state of `longlines-mode'."
+  (require 'longlines)
+  (let ((state (so-long-original 'longlines-mode :exists)))
+    (if state
+        (unless (equal (cadr state) longlines-mode)
+          (longlines-mode (if (cadr state) 1 -1)))
+      (longlines-mode -1))))
+
+(defun so-long-mode-downgrade (&optional _mode)
+  "The default value for `so-long-file-local-mode-function'.
+
+A buffer-local 'downgrade' from the `so-long-mode' action to `overrides-only'.
+
+When `so-long-function' is set to `so-long-mode', then we change it to to
+`so-long-function-overrides-only' instead -- retaining the file-local major
+mode, but still doing everything else that `so-long-mode' would have done.
+`so-long-revert-function' is likewise updated.
+
+If `so-long-function' has any value other than `so-long-mode', we do nothing,
+as if `so-long-file-local-mode-function' was nil."
+  (when (and (symbolp (so-long-function))
+             (provided-mode-derived-p (so-long-function) 'so-long-mode))
+    ;; Downgrade from `so-long-mode' to the `overrides-only' behaviour.
+    (setq so-long-function 'so-long-function-overrides-only
+          so-long-revert-function 'so-long-revert-function-overrides-only)))
+
+(defun so-long-inhibit (&optional _mode)
+  "Prevent so-long from having any effect at all.
+
+This is a `so-long-file-local-mode-function' option."
+  (setq so-long--inhibited t))
+
+(defun so-long-check-header-modes ()
+  "Handles the header-comments processing in `set-auto-mode'.
+
+`set-auto-mode' has some special-case code to handle the 'mode' pseudo-variable
+when set in the header comment.  This runs outside of `hack-local-variables'
+and cannot be conveniently intercepted, so we are forced to replicate it here.
+
+This special-case code will ultimately be removed from Emacs, as it exists to
+deal with a deprecated feature; but until then we need to replicate it in order
+to inhibit our own behaviour in the presence of a header comment 'mode'
+declaration.
+
+If a file-local mode is detected in the header comment, then we call the
+function defined by `so-long-file-local-mode-function'."
+  ;; The following code for processing MODE declarations in the header
+  ;; comments is copied verbatim from `set-auto-mode', because we have
+  ;; no way of intercepting it.
+  ;;
+  (let ((try-locals (not (inhibit-local-variables-p)))
+        end _done _mode modes)
+    ;; Once we drop the deprecated feature where mode: is also allowed to
+    ;; specify minor-modes (ie, there can be more than one "mode:"), we can
+    ;; remove this section and just let (hack-local-variables t) handle it.
+    ;; Find a -*- mode tag.
+    (save-excursion
+      (goto-char (point-min))
+      (skip-chars-forward " \t\n")
+      ;; Note by design local-enable-local-variables does not matter here.
+      (and enable-local-variables
+           try-locals
+           (setq end (set-auto-mode-1))
+           (if (save-excursion (search-forward ":" end t))
+               ;; Find all specifications for the `mode:' variable
+               ;; and execute them left to right.
+               (while (let ((case-fold-search t))
+                        (or (and (looking-at "mode:")
+                                 (goto-char (match-end 0)))
+                            (re-search-forward "[ \t;]mode:" end t)))
+                 (skip-chars-forward " \t")
+                 (let ((beg (point)))
+                   (if (search-forward ";" end t)
+                       (forward-char -1)
+                     (goto-char end))
+                   (skip-chars-backward " \t")
+                   (push (intern (concat (downcase (buffer-substring beg 
(point))) "-mode"))
+                         modes)))
+             ;; Simple -*-MODE-*- case.
+             (push (intern (concat (downcase (buffer-substring (point) end))
+                                   "-mode"))
+                   modes))))
+
+    ;; `so-long' now processes the resulting mode list.  If any modes were
+    ;; listed, we assume that one of them is a major mode.  It's possible that
+    ;; this isn't true, but the buffer would remain in fundamental-mode if that
+    ;; were the case, so it is very unlikely.  For the purposes of passing a
+    ;; value to `so-long-handle-file-local-mode' we assume the major mode was
+    ;; the first mode specified (in which case it is the last in the list).
+    (when modes
+      (so-long-handle-file-local-mode (car (last modes))))))
+
+;; Lisp advice, Lisp advice
+;; Every calling you greet me
+;; Code of mine, redefined
+;; You look happy to tweak me
+
+(defadvice hack-local-variables (after so-long--file-local-mode disable)
+  "Ensure that `so-long' defers to file-local mode declarations if necessary.
+
+This advice acts after the HANDLE-MODE:t call to `hack-local-variables'.
+\(MODE-ONLY in Emacs versions < 26).
+
+File-local header comments are currently an exception, and are processed by
+`so-long-check-header-modes' (see which for details).
+
+If a file-local mode is detected, then we call the function defined by
+`so-long-file-local-mode-function'."
+  ;; The first arg to `hack-local-variables' is HANDLE-MODE since Emacs 26.1,
+  ;; and MODE-ONLY in earlier versions.  In either case we are interested in
+  ;; whether it has the value `t'.
+  (and (eq (ad-get-arg 0) t)
+       ad-return-value ; A file-local mode was set.
+       (so-long-handle-file-local-mode ad-return-value)))
+
+;; n.b. Call (so-long-enable) after changes, to re-activate the advice.
+
+(defadvice set-auto-mode (around so-long--set-auto-mode disable)
+  "Maybe call `so-long' for files with very long lines.
+
+This advice acts after `set-auto-mode' has set the buffer's major mode.
+
+We can't act before this point, because some major modes must be exempt
+\(binary file modes, for example).  Instead, we act only when the selected
+major mode is a member (or derivative of a member) of `so-long-target-modes'.
+
+`so-long-predicate' then determines whether the mode change is needed."
+  (setq so-long--inhibited nil) ; is permanent-local
+  (when so-long-enabled
+    (so-long-check-header-modes)) ; may cause `so-long--inhibited' to be set.
+  (let ((so-long--set-auto-mode t))
+    ad-do-it) ; `set-auto-mode'   ; may cause `so-long--inhibited' to be set.
+  ;; Test the new major mode for long lines.
+  (and so-long-enabled
+       (not so-long--inhibited)
+       (not so-long--calling)
+       (or (eq so-long-target-modes t)
+           (apply #'derived-mode-p so-long-target-modes))
+       (setq so-long-detected-p (funcall so-long-predicate))
+       (so-long)))
+
+;; n.b. Call (so-long-enable) after changes, to re-activate the advice.
+
+(defadvice hack-one-local-variable (around so-long--ignore-mode-dup disable)
+  "Prevent the original major mode being restored after `so-long-mode'.
+
+This advice is needed and enabled only for Emacs versions < 26.1.
+
+If the local 'mode' pseudo-variable is used, `set-auto-mode-0' will call it
+firstly, and subsequently `hack-one-local-variable' may call it again.
+
+Usually `hack-one-local-variable' tries to avoid processing that second call,
+by testing the value against `major-mode'; but as we may have changed the
+major mode to `so-long-mode' by this point, that protection is insufficient
+and so we need to perform our own test.
+
+The changes to `normal-mode' in Emacs 26.1 modified the execution order, and
+makes this advice unnecessary.  The relevant NEWS entry is:
+
+** File local and directory local variables are now initialized each
+time the major mode is set, not just when the file is first visited.
+These local variables will thus not vanish on setting a major mode."
+  (if (eq (ad-get-arg 0) 'mode)
+      ;; Adapted directly from `hack-one-local-variable'
+      (let ((mode (intern (concat (downcase (symbol-name (ad-get-arg 1)))
+                                  "-mode"))))
+        (unless (eq (indirect-function mode)
+                    (indirect-function (so-long-original 'major-mode)))
+          ad-do-it))
+    ;; VAR is not the 'mode' pseudo-variable.
+    ad-do-it))
+
+;; n.b. Call (so-long-enable) after changes, to re-activate the advice.
+
+;;;###autoload
+(defun so-long ()
+  "Invoke `so-long-action' and run `so-long-hook'."
+  (interactive)
+  (unless so-long--calling
+    (let ((so-long--calling t))
+      ;; Some of these settings need to be duplicated in `so-long-mode' to 
cover
+      ;; the case when that mode is invoked directly.
+      (setq so-long-detected-p t) ;; ensure menu is present.
+      (unless so-long-function
+        (setq so-long-function (so-long-function)))
+      (unless so-long-revert-function
+        (setq so-long-revert-function (so-long-revert-function)))
+      ;; Remember original settings.
+      (setq so-long-original-values nil)
+      (dolist (ovar so-long-variable-overrides)
+        (so-long-remember (car ovar)))
+      (dolist (mode so-long-minor-modes)
+        (when (and (boundp mode) mode)
+          (so-long-remember mode)))
+      ;; Call the configured `so-long-function'.
+      (when so-long-function
+        (funcall so-long-function)
+        ;; Set `so-long--active' last, as it isn't permanent-local.
+        (setq so-long--active t))
+      ;; Display mode line info, unless we are in `so-long-mode' (which 
provides
+      ;; equivalent information in the mode line construct for the major mode).
+      (unless (derived-mode-p 'so-long-mode)
+        (setq so-long-mode-line-info (so-long-mode-line-info)))
+      ;; Run `so-long-hook'.
+      ;; By default we set `buffer-read-only', which can cause problems if hook
+      ;; functions need to modify the buffer.  We use `inhibit-read-only' to
+      ;; side-step the issue (and likewise in `so-long-revert').
+      (let ((inhibit-read-only t))
+        (run-hooks 'so-long-hook)))))
+
+(defun so-long-revert ()
+  "Revert `so-long-action' and run `so-long-revert-hook'."
+  (interactive)
+  (unless so-long--calling
+    (let ((so-long--calling t))
+      (when so-long-revert-function
+        (funcall so-long-revert-function)
+        (setq so-long--active nil))
+      (let ((inhibit-read-only t))
+        (run-hooks 'so-long-revert-hook)))))
+
+;;;###autoload
+(defun so-long-enable ()
+  "Enable the so-long library's functionality."
+  (interactive)
+  (add-hook 'change-major-mode-hook 'so-long-change-major-mode)
+  (ad-enable-advice 'hack-local-variables 'after 'so-long--file-local-mode)
+  (ad-enable-advice 'set-auto-mode 'around 'so-long--set-auto-mode)
+  (ad-activate 'hack-local-variables)
+  (ad-activate 'set-auto-mode)
+  (when (< emacs-major-version 26)
+    (ad-enable-advice 'hack-one-local-variable
+                      'around 'so-long--ignore-mode-dup)
+    (ad-activate 'hack-one-local-variable))
+  (add-to-list 'mode-line-misc-info '("" so-long-mode-line-info))
+  (define-key-after (current-global-map) [menu-bar so-long]
+    `(menu-item "So Long" nil
+                ;; See also `so-long-mode-map'.
+                :visible (or so-long--active
+                             so-long-detected-p
+                             (derived-mode-p 'so-long-mode))
+                :filter ,(lambda (_cmd) (so-long-menu))))
+  (setq so-long-enabled t))
+
+(defun so-long-disable ()
+  "Disable the so-long library's functionality."
+  (interactive)
+  (remove-hook 'change-major-mode-hook 'so-long-change-major-mode)
+  (ad-disable-advice 'hack-local-variables 'after 'so-long--file-local-mode)
+  (ad-disable-advice 'set-auto-mode 'around 'so-long--set-auto-mode)
+  (ad-activate 'hack-local-variables)
+  (ad-activate 'set-auto-mode)
+  (when (< emacs-major-version 26)
+    (ad-disable-advice 'hack-one-local-variable
+                       'around 'so-long--ignore-mode-dup)
+    (ad-activate 'hack-one-local-variable))
+  (setq mode-line-misc-info
+        (delete '("" so-long-mode-line-info) mode-line-misc-info))
+  (define-key (current-global-map) [menu-bar so-long] nil)
+  (setq so-long-enabled nil))
+
+(defun so-long-unload-function ()
+  (so-long-disable)
+  nil)
+
+(provide 'so-long)
+
+;; Local Variables:
+;; emacs-lisp-docstring-fill-column: 80
+;; fill-column: 80
+;; indent-tabs-mode: nil
+;; End:
+
+;; So long, farewell, auf wiedersehen, goodbye
+;; You have to go, this code is minified
+;; Goodbye!
+
+;;; so-long.el ends here



reply via email to

[Prev in Thread] Current Thread [Next in Thread]