--- /dev/null
+;;; 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 <psainty@orcon.net.nz>
+;; Maintainer: Phil Sainty <psainty@orcon.net.nz>
+;; URL: https://savannah.nongnu.org/projects/so-long
+;; Keywords: convenience
+;; Created: 23 Dec 2015
+;; Package-Requires: ((emacs "24.4"))
+;; Version: 1.0
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; * Introduction
+;; --------------
+;; 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). This can result in lines
+;; which are many thousands of characters long, and most programming modes
+;; simply aren't optimised (remotely) for this scenario, so performance can
+;; suffer significantly.
+;;
+;; When such files are detected, the command `so-long' is automatically called,
+;; overriding certain minor modes and variables with performance implications
+;; (all configurable), in order to enhance performance in the buffer.
+;;
+;; The default action enables the major mode `so-long-mode' in place of the mode
+;; that Emacs selected. This ensures that the original major mode cannot affect
+;; performance further, as well as making the so-long activity more obvious to
+;; the user. 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 state of the
+;; buffer may be reinstated by calling `so-long-revert' (the key binding for
+;; which is advertised when the major mode change occurs). If you prefer that
+;; the major mode not be changed, the `so-long-minor-mode' 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. As well as
+;; the major and minor mode actions provided by this library, `longlines-mode'
+;; is also supported by default as an alternative action.
+;;
+;; Note that while the measures taken 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 global-so-long-mode to enable/toggle the functionality. To enable
+;; the functionality by default, either customize the `global-so-long-mode' user
+;; option, or add the following to your init file:
+;;
+;; ;; Avoid performance issues in files with very long lines.
+;; (global-so-long-mode 1)
+;;
+;; If necessary, ensure that so-long.el is in a directory in your load-path, and
+;; that the library has been loaded. (These steps are not necessary if you are
+;; using Emacs 27+, or have installed the GNU ELPA package.)
+
+;; * Overview of modes and commands
+;; --------------------------------
+;; - `global-so-long-mode' - A global minor mode which enables the automated
+;; behaviour, causing the user's preferred action to be invoked whenever a
+;; newly-visited file contains excessively long lines.
+;; - `so-long-mode' - A major mode, and the default action.
+;; - `so-long-minor-mode' - A minor mode version of the major mode, and an
+;; alternative action.
+;; - `longlines-mode' - A minor mode provided by the longlines.el library,
+;; and another alternative action.
+;; - `so-long' - Manually invoke the user's preferred action, enabling its
+;; performance improvements for the current buffer.
+;; - `so-long-revert' - Restore the original state of the buffer.
+;; - `so-long-customize' - Configure the user options.
+;; - `so-long-commentary' - Read this documentation in outline-mode.
+
+;; * Usage
+;; -------
+;; In most cases you will simply enable `global-so-long-mode' and leave it to
+;; act automatically as required, in accordance with your configuration (see
+;; "Basic configuration" below).
+;;
+;; On rare occasions you may choose to manually invoke the `so-long' command,
+;; which invokes your preferred `so-long-action' (exactly as the automatic
+;; behaviour would do if it had detected long lines). You might use this if a
+;; problematic file did not meet your configured criteria, and you wished to
+;; trigger the performance improvements manually.
+;;
+;; It is also possible to directly use `so-long-mode' or `so-long-minor-mode'
+;; (major and minor modes, respectively). Both of these modes are actions
+;; available to `so-long' but, like any other mode, they can be invoked directly
+;; if you have a need to do that (see also "Other ways of using so-long" below).
+;;
+;; If the behaviour ever triggers when you did not want it to, you can use the
+;; `so-long-revert' command to restore the buffer to its original state.
+
+;; * Basic configuration
+;; ---------------------
+;; Use M-x customize-group RET so-long RET
+;; (or M-x so-long-customize 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 selected action can be invoked
+;; manually with M-x so-long; and in general M-x so-long-revert will undo the
+;; effects of whichever action was used (which is particularly useful when the
+;; action is triggered automatically, but the detection was a 'false positive'.)
+;;
+;; 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
+;; (as the author of the file would otherwise be unlikely to have set that mode),
+;; 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 `so-long-minor-mode'
+;; action to be used instead of the `so-long-mode' action, if the latter was
+;; going to be used for this file. This is still a conservative default, but
+;; 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 `so-long-minor-mode'. 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 `so-long-minor-mode',
+;; 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-hook' runs at the end of the `so-long' command, after the configured
+;; action has been invoked.
+;;
+;; 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.
+;;
+;; The major and minor modes also provide the usual hooks: `so-long-mode-hook'
+;; for the major mode (running between `change-major-mode-after-body-hook' and
+;; `after-change-major-mode-hook'); and `so-long-minor-mode-hook' (when that
+;; mode is enabled or disabled).
+
+;; * 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 so-long -f global-so-long-mode <file>
+;;
+;; For versions of Emacs < 27, use:
+;; emacs -Q -l /path/to/so-long.el -f global-so-long-mode <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.
+;;
+;; The in-built profiler can be an effective way of discovering the cause
+;; of such problems. Refer to M-: (info "(elisp) Profiling") RET
+;;
+;; In some cases it may be useful to set a file-local `mode' variable to
+;; `so-long-mode', completely bypassing the automated decision process.
+;; Refer to M-: (info "(emacs) Specifying File Variables") RET
+;;
+;; If so-long itself is causing problems, it can be inhibited by setting the
+;; `so-long-enabled' variable to nil, or by disabling the global mode with
+;; M-- M-x global-so-long-mode, or M-: (global-so-long-mode 0)
+
+;; * 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)
+;; (global-so-long-mode 1)
+;; ;; Basic settings.
+;; (setq so-long-action 'so-long-minor-mode)
+;; (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 major mode `so-long-mode' 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")
+;;
+;; - A specific individual file:
+;; (rx bos "/path/to/file" eos)
+;;
+;; Another way to target individual files is to set a file-local `mode'
+;; variable. Refer to M-: (info "(emacs) Specifying File Variables") RET
+;;
+;; `so-long-minor-mode' can also be called directly if desired. e.g.:
+;; (add-hook 'FOO-mode-hook 'so-long-minor-mode)
+;;
+;; 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 - Included in Emacs 27.1, and in GNU ELPA for prior versions of Emacs.
+;; - New global mode `global-so-long-mode' to enable/disable the library.
+;; - 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'.
+;; - New minor mode action `so-long-minor-mode' facilitates retaining the
+;; original major mode, while still disabling minor modes and overriding
+;; variables like the major mode `so-long-mode'.
+;; - 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'.
+;; - Converted defadvice to nadvice.
+;; 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.
+\f
+;;; Code:
+
+(require 'cl-lib)
+
+(add-to-list 'customize-package-emacs-version-alist
+ '(so-long ("1.0" . "27.1")))
+
+(declare-function longlines-mode "longlines")
+(defvar longlines-mode)
+
+(declare-function outline-next-visible-heading "outline")
+(declare-function outline-previous-visible-heading "outline")
+(declare-function outline-toggle-children "outline")
+(declare-function outline-toggle-children "outline")
+
+(defvar so-long-enabled nil
+ "Set to nil to prevent `so-long' from being triggered automatically.
+
+Has no effect if `global-so-long-mode' is not enabled.")
+
+(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 so-long--hack-local-variables-no-mode nil ; internal use
+ "Non-nil to prevent `hack-local-variables' applying a 'mode' variable.")
+
+(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.
+
+If the file begins with a shebang (#!), this option also causes that line to be
+ignored even if it doesn't match the buffer's comment syntax, to ensure that
+comments following the shebang will be ignored.
+
+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 all modes are targeted. Note that this is only useful with a
+custom `so-long-predicate', as many file types (archives and binary files,
+for example) can safely contain long lines, and invoking `so-long' on such
+files would prevent Emacs from handling them correctly."
+ ;; 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'.
+
+The specified function will be called with no arguments. If it returns non-nil
+then `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)))
+ (assq-delete-all nil so-long-action-alist))
+ (const :tag "Do nothing" nil)))
+
+(defun so-long--action-alist-setter (option value)
+ "The customize :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)
+ (so-long-minor-mode
+ "Enable so-long-minor-mode"
+ turn-on-so-long-minor-mode
+ turn-off-so-long-minor-mode)
+ (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, the `so-long-minor-mode' action 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 (&optional action-arg)
+ "The value of `so-long-function', else derive from `so-long-action'.
+
+If ACTION-ARG is provided, it is used in place of `so-long-action'."
+ (or so-long-function
+ (and (or action-arg
+ (setq action-arg so-long-action))
+ (let ((action (assq action-arg so-long-action-alist)))
+ (nth 2 action)))))
+
+(defun so-long-revert-function (&optional action-arg)
+ "The value of `so-long-revert-function', else derive from `so-long-action'.
+
+If ACTION-ARG is provided, it is used in place of `so-long-action'."
+ (or so-long-revert-function
+ (and (or action-arg
+ (setq action-arg so-long-action))
+ (let ((action (assq action-arg 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 `so-long-minor-mode' will be used in
+place of `so-long-mode' -- therefore respecting the file-local mode value, yet
+still overriding minor modes and variables (as if `so-long-action' had been set
+to `so-long-minor-mode').
+
+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, and also if the file-local mode is `so-long-mode'. Custom
+functions may need to test for these cases -- see `so-long-mode-downgrade' for
+an example."
+ :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.
+ (when (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
+ goto-address-mode
+ goto-address-prog-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 `so-long-minor-mode'. 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)
+ (show-paren-mode . nil)
+ (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 `so-long-minor-mode'.
+
+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)
+ (show-paren-mode 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-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."
+ :package-version '(so-long . "1.0")
+ :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."
+ :package-version '(so-long . "1.0")
+ :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)
+ "Store the value of VARIABLE in `so-long-original-values'.
+
+We additionally store a boolean value which indicates whether that value was
+buffer-local."
+ (when (boundp variable)
+ (setq so-long-original-values
+ (assq-delete-all variable so-long-original-values))
+ (push (list variable
+ (symbol-value variable)
+ (local-variable-p variable))
+ so-long-original-values)))
+
+(defun so-long-remember-all (&optional reset)
+ "Remember the current variable and minor mode values.
+
+Stores the existing value for each entry in `so-long-variable-overrides'.
+Stores the name of each enabled mode from the list `so-long-minor-modes'.
+
+If RESET is non-nil, remove any existing values before storing the new ones."
+ (when reset
+ (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))))
+
+(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
+ ,(let ((sym (make-symbol "so-long-menu-item-replace-action")))
+ ;; Using a symbol here, so that `describe-key' on the menu item
+ ;; produces the `so-long-menu-item-replace-action' documentation.
+ (defalias sym
+ (apply-partially #'so-long-menu-item-replace-action item)
+ (documentation #'so-long-menu-item-replace-action))
+ (put sym 'interactive-form '(interactive))
+ sym)
+ :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 in `outline-mode'."
+ (interactive)
+ (let ((buf "*So Long: Commentary*"))
+ (when (buffer-live-p (get-buffer buf))
+ (kill-buffer buf))
+ ;; Use `finder-commentary' to generate the buffer.
+ (require 'finder)
+ (cl-letf (((symbol-function 'finder-summary) #'ignore))
+ (finder-commentary "so-long"))
+ (let ((inhibit-read-only t))
+ (when (looking-at "^Commentary:\n\n")
+ (replace-match "so-long.el\n\n"))
+ (save-excursion
+ (while (re-search-forward "^-+$" nil :noerror)
+ (replace-match ""))))
+ (rename-buffer buf)
+ ;; Enable `outline-mode' and `view-mode' for user convenience.
+ (outline-mode)
+ (view-mode 1)
+ ;; Add some custom local bindings.
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "TAB") #'outline-toggle-children)
+ (define-key map (kbd "<M-tab>") #'outline-toggle-children)
+ (define-key map (kbd "M-n") #'outline-next-visible-heading)
+ (define-key map (kbd "M-p") #'outline-previous-visible-heading)
+ (set-keymap-parent map (current-local-map))
+ (use-local-map map))
+ ;; Display the So Long menu.
+ (so-long--ensure-enabled)
+ (let ((so-long-action nil))
+ (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 ()
+ "Determine whether the current buffer contains long lines.
+
+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
+ ;; Skip the shebang line, if any. This is not necessarily comment
+ ;; syntax, so we need to treat it specially.
+ (when (looking-at "#!")
+ (forward-line 1))
+ ;; Move past any leading whitespace and/or comments.
+ ;; 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
+ ;; unless we're at the end of the buffer (in which case there was no
+ ;; non-comment/whitespace content in the buffer at all).
+ (unless (eobp)
+ (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'."
+ (require 'longlines)
+ (so-long-remember 'longlines-mode)
+ (longlines-mode 1))
+
+(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 0)))
+ (longlines-mode 0))))
+
+(defun turn-on-so-long-minor-mode ()
+ "Enable minor mode `so-long-minor-mode'."
+ (so-long-minor-mode 1))
+
+(defun turn-off-so-long-minor-mode ()
+ "Disable minor mode `so-long-minor-mode'."
+ (so-long-minor-mode 0))
+
+;;;###autoload
+(define-minor-mode so-long-minor-mode
+ "This is the minor mode equivalent of `so-long-mode'.
+
+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'.
+
+This minor mode is a standard `so-long-action' option."
+ nil nil nil
+ (if so-long-minor-mode ;; We are enabling the mode.
+ (progn
+ ;; Housekeeping. `so-long-minor-mode' might be invoked directly rather
+ ;; than via `so-long', so replicate the necessary behaviours. The minor
+ ;; mode also cares about whether `so-long' was already active, as we do
+ ;; not want to remember values which were potentially overridden already.
+ (unless (or so-long--calling so-long--active)
+ (so-long--ensure-enabled)
+ (setq so-long--active t
+ so-long-detected-p t
+ so-long-function 'turn-on-so-long-minor-mode
+ so-long-revert-function 'turn-off-so-long-minor-mode)
+ (so-long-remember-all :reset)
+ (unless (derived-mode-p 'so-long-mode)
+ (setq so-long-mode-line-info (so-long-mode-line-info))))
+ ;; Now perform the overrides.
+ (so-long-disable-minor-modes)
+ (so-long-override-variables))
+ ;; We are disabling the mode.
+ (unless so-long--calling ;; Housekeeping.
+ (when (eq so-long-function 'turn-on-so-long-minor-mode)
+ (setq so-long--active nil))
+ (unless (derived-mode-p 'so-long-mode)
+ (setq so-long-mode-line-info (so-long-mode-line-info))))
+ ;; Restore the overridden settings.
+ (so-long-restore-minor-modes)
+ (so-long-restore-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'.")
+
+;;;###autoload
+(define-derived-mode so-long-mode nil "So Long"
+ "This major mode is the default `so-long-action' option.
+
+The normal reason for this mode being active is that `global-so-long-mode' is
+enabled, and `so-long-predicate' has detected that the file contains long lines.
+
+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.
+
+This major mode disables any active minor modes listed in `so-long-minor-modes'
+for the current buffer, and buffer-local values are assigned to variables in
+accordance with `so-long-variable-overrides'.
+
+To restore the original major mode (along with the minor modes and variable
+values), despite potential performance issues, type \\[so-long-revert].
+
+Use \\[so-long-commentary] for more information.
+
+Use \\[so-long-customize] to configure the behaviour."
+ ;; Housekeeping. `so-long-mode' might be invoked directly rather than via
+ ;; `so-long', so replicate the necessary behaviours. 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
+ (so-long--ensure-enabled)
+ (setq so-long--active t
+ so-long-detected-p t
+ so-long-function 'so-long-mode
+ so-long-revert-function 'so-long-mode-revert))
+ ;; Use `after-change-major-mode-hook' to disable minor modes and override
+ ;; variables. Append, to 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 (or (eq this-command 'so-long)
+ (and (symbolp this-command)
+ (provided-mode-derived-p this-command
+ '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--change-major-mode ()
+ ;; Advice, enabled with:
+ ;; (advice-add 'so-long-mode :before #'so-long--change-major-mode)
+ ;;
+ ;; n.b. `major-mode-suspend' and `major-mode-restore' are new in Emacs 27, and
+ ;; related to what we're doing here; but it's not worth going to the effort of
+ ;; using those (conditionally, only for 27+) when we have our own framework
+ ;; for remembering and restoring this buffer state (amongst other things).
+ "Ensure that `so-long-mode' knows the original `major-mode'.
+
+This advice acts before `so-long-mode', with the previous mode still active."
+ (unless (derived-mode-p 'so-long-mode)
+ ;; Housekeeping. `so-long-mode' might be invoked directly rather than
+ ;; via `so-long', so replicate the necessary behaviours.
+ (unless so-long--calling
+ (so-long-remember-all :reset))
+ ;; Remember the original major mode, regardless.
+ (so-long-remember 'major-mode)))
+
+(advice-add 'so-long-mode :before #'so-long--change-major-mode)
+
+(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 ()
+ "Set the buffer-local values defined by `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.
+
+The variables are set in accordance with what was remembered in `so-long'."
+ (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.
+
+This is the `so-long-revert-function' for `so-long-mode'."
+ (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'; provided there was a `buffer-file-name'), but for older
+ ;; versions we need to call it here. In Emacs 26+ the revised 'HANDLE-MODE'
+ ;; argument is set to `no-mode' (being the non-nil-and-non-t behaviour),
+ ;; which we mimic here by binding `so-long--hack-local-variables-no-mode',
+ ;; in order to prevent a local 'mode' variable from clobbering the major
+ ;; mode we have just called.
+ (when (< emacs-major-version 26)
+ (let ((so-long--hack-local-variables-no-mode t))
+ (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-mode-downgrade (&optional mode)
+ "The default value for `so-long-file-local-mode-function'.
+
+A buffer-local 'downgrade' from `so-long-mode' to `so-long-minor-mode'.
+
+When `so-long-function' is set to `so-long-mode', then we change it to to
+`turn-on-so-long-minor-mode' 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.
+
+We also do nothing if MODE (the file-local mode) has the value `so-long-mode',
+because we do not want to downgrade the major mode in that scenario."
+ ;; Do nothing if the file-local mode was `so-long-mode'.
+ (unless (provided-mode-derived-p mode 'so-long-mode)
+ ;; Act only if `so-long-mode' would be enabled by the current action.
+ (when (and (symbolp (so-long-function))
+ (provided-mode-derived-p (so-long-function) 'so-long-mode))
+ ;; Downgrade from `so-long-mode' to the `so-long-minor-mode' behaviour.
+ (setq so-long-function 'turn-on-so-long-minor-mode
+ so-long-revert-function 'turn-off-so-long-minor-mode))))
+
+(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 ()
+ ;; See also "Files with a file-local 'mode'" in the Commentary.
+ "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
+
+(defun so-long--hack-local-variables (orig-fun &optional handle-mode &rest args)
+ ;; Advice, enabled with:
+ ;; (advice-add 'hack-local-variables :around #'so-long--hack-local-variables)
+ ;;
+ ;; See also "Files with a file-local 'mode'" in the Commentary.
+ "Ensure that `so-long' defers to file-local mode declarations if necessary.
+
+If a file-local mode is detected, then we call the function defined by
+`so-long-file-local-mode-function'.
+
+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)."
+ ;; 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'.
+ (let ((retval (apply orig-fun handle-mode args)))
+ (and (eq handle-mode t)
+ retval ; A file-local mode was set.
+ (so-long-handle-file-local-mode retval))
+ retval))
+
+(defun so-long--set-auto-mode (orig-fun &rest args)
+ ;; Advice, enabled with:
+ ;; (advice-add 'set-auto-mode :around #'so-long--set-auto-mode)
+ "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))
+ ;; Call `set-auto-mode'.
+ (apply orig-fun args)) ; 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)))
+
+(defun so-long--hack-one-local-variable (orig-fun var val)
+ ;; Advice, enabled with:
+ ;; (advice-add 'hack-one-local-variable :around
+ ;; #'so-long--hack-one-local-variable)
+ "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.
+
+We likewise need to support an equivalent of the `no-mode' behaviour in 26.1+
+to ensure that `so-long-mode-revert' will not restore a file-local mode again
+after it has already reverted to the original mode.
+
+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 var 'mode)
+ ;; Adapted directly from `hack-one-local-variable'
+ (let ((mode (intern (concat (downcase (symbol-name val))
+ "-mode"))))
+ (unless (or so-long--hack-local-variables-no-mode
+ (let ((origmode (so-long-original 'major-mode)))
+ ;; We bind origmode because (indirect-function nil) is an
+ ;; error in Emacs versions < 25.1, and so we need to test
+ ;; it first.
+ (and origmode
+ (eq (indirect-function mode)
+ (indirect-function origmode)))))
+ (funcall orig-fun var val)))
+ ;; VAR is not the 'mode' pseudo-variable.
+ (funcall orig-fun var val)))
+
+;;;###autoload
+(defun so-long (&optional action)
+ "Invoke `so-long-action' and run `so-long-hook'.
+
+This command is called automatically when long lines are detected, when
+`global-so-long-mode' is enabled.
+
+The effects of the action can be undone by calling `so-long-revert'.
+
+If ACTION is provided, it is used instead of `so-long-action'. With a prefix
+argument, select the action to use interactively."
+ (interactive
+ (list (and current-prefix-arg
+ (intern
+ (completing-read "Action (none): "
+ (mapcar #'car so-long-action-alist)
+ nil :require-match)))))
+ (unless so-long--calling
+ (let ((so-long--calling t))
+ (so-long--ensure-enabled)
+ ;; ACTION takes precedence if supplied.
+ (when action
+ (setq so-long-function nil
+ so-long-revert-function nil))
+ ;; 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 action)))
+ (unless so-long-revert-function
+ (setq so-long-revert-function (so-long-revert-function action)))
+ ;; Remember original settings.
+ (so-long-remember-all :reset)
+ ;; 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 the active `so-long-action' and run `so-long-revert-hook'.
+
+This undoes the effects of the `so-long' command (which is normally called
+automatically by `global-so-long-mode').
+
+For the default action, reverting will restore the original major mode, and
+restore the minor modes and settings which were overridden when `so-long' was
+invoked."
+ (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)))))
+
+;; Duplicate the `so-long-revert' documentation for the menu item.
+(put 'so-long-menu-item-revert 'function-documentation
+ (documentation 'so-long-revert t))
+
+;;;###autoload
+(defun so-long-enable ()
+ "Enable the so-long library's functionality.
+
+Equivalent to calling (global-so-long-mode 1)"
+ (interactive)
+ (global-so-long-mode 1))
+
+(defun so-long-disable ()
+ "Disable the so-long library's functionality.
+
+Equivalent to calling (global-so-long-mode 0)"
+ (interactive)
+ (global-so-long-mode 0))
+
+(make-obsolete 'so-long-enable 'global-so-long-mode "so-long 1.0")
+(make-obsolete 'so-long-disable 'global-so-long-mode "so-long 1.0")
+
+;;;###autoload
+(define-minor-mode global-so-long-mode
+ "Toggle automated performance mitigations for files with long lines.
+
+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 that has been
+compacted into the smallest file size possible, which often entails removing
+newlines should they not be strictly necessary).
+
+When such files are detected by `so-long-predicate', we invoke the selected
+`so-long-action' to mitigate potential performance problems in the buffer.
+
+Use \\[so-long-commentary] for more information.
+
+Use \\[so-long-customize] to configure the behaviour."
+ :global t
+ :group 'so-long
+ (if global-so-long-mode
+ ;; Enable
+ (progn
+ (so-long--enable)
+ (advice-add 'hack-local-variables :around
+ #'so-long--hack-local-variables)
+ (advice-add 'set-auto-mode :around
+ #'so-long--set-auto-mode)
+ (when (< emacs-major-version 26)
+ (advice-add 'hack-one-local-variable :around
+ #'so-long--hack-one-local-variable)))
+ ;; Disable
+ (so-long--disable)
+ (advice-remove 'hack-local-variables #'so-long--hack-local-variables)
+ (advice-remove 'set-auto-mode #'so-long--set-auto-mode)
+ (when (< emacs-major-version 26)
+ (advice-remove 'hack-one-local-variable
+ #'so-long--hack-one-local-variable))))
+
+(put 'global-so-long-mode 'variable-documentation
+ "Non-nil if the so-long library's automated functionality is enabled.
+
+Use \\[so-long-commentary] for more information.
+
+Setting this variable directly does not take effect;
+either customize it (see the info node `Easy Customization')
+or call the function `global-so-long-mode'.")
+
+(defun so-long--ensure-enabled ()
+ "Enable essential functionality, if not already enabled."
+ (unless so-long-enabled
+ (so-long--enable)))
+
+(defun so-long--enable ()
+ "Enable functionality other than `global-so-long-mode'."
+ (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 functionality other than `global-so-long-mode'."
+ (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 ()
+ "Handler for `unload-feature'."
+ (global-so-long-mode 0)
+ 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