aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/emacs/emacs.texi1
-rw-r--r--doc/emacs/trouble.texi25
-rw-r--r--etc/NEWS8
-rw-r--r--lisp/so-long.el1703
-rw-r--r--test/lisp/so-long-tests/autoload-longlines-mode-tests.el51
-rw-r--r--test/lisp/so-long-tests/autoload-major-mode-tests.el46
-rw-r--r--test/lisp/so-long-tests/autoload-minor-mode-tests.el50
-rw-r--r--test/lisp/so-long-tests/so-long-tests-helpers.el113
-rw-r--r--test/lisp/so-long-tests/so-long-tests.el448
9 files changed, 2445 insertions, 0 deletions
diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi
index a34cef221e1..ad4be90aaf3 100644
--- a/doc/emacs/emacs.texi
+++ b/doc/emacs/emacs.texi
@@ -1176,6 +1176,7 @@ Dealing with Emacs Trouble
1176* Crashing:: What Emacs does when it crashes. 1176* Crashing:: What Emacs does when it crashes.
1177* After a Crash:: Recovering editing in an Emacs session that crashed. 1177* After a Crash:: Recovering editing in an Emacs session that crashed.
1178* Emergency Escape:: What to do if Emacs stops responding. 1178* Emergency Escape:: What to do if Emacs stops responding.
1179* Long Lines:: Mitigating slowness due to extremely long lines.
1179 1180
1180Reporting Bugs 1181Reporting Bugs
1181 1182
diff --git a/doc/emacs/trouble.texi b/doc/emacs/trouble.texi
index 2fe54878058..aa940208214 100644
--- a/doc/emacs/trouble.texi
+++ b/doc/emacs/trouble.texi
@@ -152,6 +152,7 @@ Emacs.
152* Crashing:: What Emacs does when it crashes. 152* Crashing:: What Emacs does when it crashes.
153* After a Crash:: Recovering editing in an Emacs session that crashed. 153* After a Crash:: Recovering editing in an Emacs session that crashed.
154* Emergency Escape:: What to do if Emacs stops responding. 154* Emergency Escape:: What to do if Emacs stops responding.
155* Long Lines:: Mitigating slowness due to extremely long lines.
155@end menu 156@end menu
156 157
157@node DEL Does Not Delete 158@node DEL Does Not Delete
@@ -457,6 +458,30 @@ program.
457emergency escape---but there are cases where it won't work, when a 458emergency escape---but there are cases where it won't work, when a
458system call hangs or when Emacs is stuck in a tight loop in C code. 459system call hangs or when Emacs is stuck in a tight loop in C code.
459 460
461@node Long Lines
462@subsection Long Lines
463@cindex long lines
464
465 For a variety of reasons (some of which are fundamental to the Emacs
466redisplay code and the complex range of possibilities it handles;
467others of which are due to modes and features which do not scale well
468in unusual circumstances), Emacs can perform poorly when extremely
469long lines are present (where ``extremely long'' usually means at
470least many thousands of characters).
471
472 A particular problem is that Emacs may ``hang'' for a long time at
473the point of visiting a file with extremely long lines, and this case
474can be mitigated by enabling the @file{so-long} library, which detects
475when a visited file contains abnormally long lines, and takes steps to
476disable features which are liable to cause slowness in that situation.
477
478 This library can also significantly improve performance when moving
479and editing in such a buffer -- performance is still likely to degrade
480as you get deeper into the long lines, but the improvements can
481nevertheless be substantial.
482
483 Use @kbd{M-x so-long-commentary} to view the documentation for this
484library and learn how to enable and configure it.
460@node Bugs 485@node Bugs
461@section Reporting Bugs 486@section Reporting Bugs
462 487
diff --git a/etc/NEWS b/etc/NEWS
index 10470dfb5eb..dc9194aa74a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1705,6 +1705,14 @@ expansion to backtrace buffers produced by the Lisp debugger, Edebug
1705and ERT. See the node "(elisp) Backtraces" in the Elisp manual for 1705and ERT. See the node "(elisp) Backtraces" in the Elisp manual for
1706documentation of the new mode and its commands. 1706documentation of the new mode and its commands.
1707 1707
1708+++
1709** so-long.el helps to mitigate performance problems with long lines.
1710When 'global-so-long-mode' has been enabled, visiting a file with very
1711long lines will (subject to configuration) cause the user's preferred
1712'so-long-action' to be automatically invoked (by default, the buffer's
1713major mode is replaced by 'so-long-mode'). In extreme cases this can
1714prevent delays of several minutes, and make Emacs responsive almost
1715immediately. Type 'M-x so-long-commentary' for full documentation.
1708 1716
1709* Incompatible Lisp Changes in Emacs 27.1 1717* Incompatible Lisp Changes in Emacs 27.1
1710 1718
diff --git a/lisp/so-long.el b/lisp/so-long.el
new file mode 100644
index 00000000000..e5220fc5024
--- /dev/null
+++ b/lisp/so-long.el
@@ -0,0 +1,1703 @@
1;;; so-long.el --- Say farewell to performance problems with minified code. -*- lexical-binding:t -*-
2;;
3;; Copyright (C) 2015, 2016, 2018, 2019 Free Software Foundation, Inc.
4
5;; Author: Phil Sainty <psainty@orcon.net.nz>
6;; Maintainer: Phil Sainty <psainty@orcon.net.nz>
7;; URL: https://savannah.nongnu.org/projects/so-long
8;; Keywords: convenience
9;; Created: 23 Dec 2015
10;; Package-Requires: ((emacs "24.4"))
11;; Version: 1.0
12
13;; This file is part of GNU Emacs.
14
15;; GNU Emacs is free software: you can redistribute it and/or modify
16;; it under the terms of the GNU General Public License as published by
17;; the Free Software Foundation, either version 3 of the License, or
18;; (at your option) any later version.
19
20;; GNU Emacs is distributed in the hope that it will be useful,
21;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23;; GNU General Public License for more details.
24
25;; You should have received a copy of the GNU General Public License
26;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
27
28;;; Commentary:
29;;
30;; * Introduction
31;; --------------
32;; When the lines in a file are so long that performance could suffer to an
33;; unacceptable degree, we say "so long" to the slow modes and options enabled
34;; in that buffer, and invoke something much more basic in their place.
35;;
36;; Many Emacs modes struggle with buffers which contain excessively long lines.
37;; This is commonly on account of 'minified' code (i.e. code that has been
38;; compacted into the smallest file size possible, which often entails removing
39;; newlines should they not be strictly necessary). This can result in lines
40;; which are many thousands of characters long, and most programming modes
41;; simply aren't optimised (remotely) for this scenario, so performance can
42;; suffer significantly.
43;;
44;; When such files are detected, the command `so-long' is automatically called,
45;; overriding certain minor modes and variables with performance implications
46;; (all configurable), in order to enhance performance in the buffer.
47;;
48;; The default action enables the major mode `so-long-mode' in place of the mode
49;; that Emacs selected. This ensures that the original major mode cannot affect
50;; performance further, as well as making the so-long activity more obvious to
51;; the user. These kinds of minified files are typically not intended to be
52;; edited, so not providing the usual editing mode in such cases will rarely be
53;; an issue. However, should the user wish to do so, the original state of the
54;; buffer may be reinstated by calling `so-long-revert' (the key binding for
55;; which is advertised when the major mode change occurs). If you prefer that
56;; the major mode not be changed, the `so-long-minor-mode' action can be
57;; configured.
58;;
59;; The user options `so-long-action' and `so-long-action-alist' determine what
60;; will happen when `so-long' and `so-long-revert' are invoked, allowing
61;; alternative actions (including custom actions) to be configured. As well as
62;; the major and minor mode actions provided by this library, `longlines-mode'
63;; is also supported by default as an alternative action.
64;;
65;; Note that while the measures taken can improve performance dramatically when
66;; dealing with such files, this library does not have any effect on the
67;; fundamental limitations of the Emacs redisplay code itself; and so if you do
68;; need to edit the file, performance may still degrade as you get deeper into
69;; the long lines. In such circumstances you may find that `longlines-mode' is
70;; the most helpful facility.
71;;
72;; Note also that the mitigations are automatically triggered when visiting a
73;; file. The library does not automatically detect if long lines are inserted
74;; into an existing buffer (although the `so-long' command can be invoked
75;; manually in such situations).
76
77;; * Installation
78;; --------------
79;; Use M-x global-so-long-mode to enable/toggle the functionality. To enable
80;; the functionality by default, either customize the `global-so-long-mode' user
81;; option, or add the following to your init file:
82;;
83;; ;; Avoid performance issues in files with very long lines.
84;; (global-so-long-mode 1)
85;;
86;; If necessary, ensure that so-long.el is in a directory in your load-path, and
87;; that the library has been loaded. (These steps are not necessary if you are
88;; using Emacs 27+, or have installed the GNU ELPA package.)
89
90;; * Overview of modes and commands
91;; --------------------------------
92;; - `global-so-long-mode' - A global minor mode which enables the automated
93;; behaviour, causing the user's preferred action to be invoked whenever a
94;; newly-visited file contains excessively long lines.
95;; - `so-long-mode' - A major mode, and the default action.
96;; - `so-long-minor-mode' - A minor mode version of the major mode, and an
97;; alternative action.
98;; - `longlines-mode' - A minor mode provided by the longlines.el library,
99;; and another alternative action.
100;; - `so-long' - Manually invoke the user's preferred action, enabling its
101;; performance improvements for the current buffer.
102;; - `so-long-revert' - Restore the original state of the buffer.
103;; - `so-long-customize' - Configure the user options.
104;; - `so-long-commentary' - Read this documentation in outline-mode.
105
106;; * Usage
107;; -------
108;; In most cases you will simply enable `global-so-long-mode' and leave it to
109;; act automatically as required, in accordance with your configuration (see
110;; "Basic configuration" below).
111;;
112;; On rare occasions you may choose to manually invoke the `so-long' command,
113;; which invokes your preferred `so-long-action' (exactly as the automatic
114;; behaviour would do if it had detected long lines). You might use this if a
115;; problematic file did not meet your configured criteria, and you wished to
116;; trigger the performance improvements manually.
117;;
118;; It is also possible to directly use `so-long-mode' or `so-long-minor-mode'
119;; (major and minor modes, respectively). Both of these modes are actions
120;; available to `so-long' but, like any other mode, they can be invoked directly
121;; if you have a need to do that (see also "Other ways of using so-long" below).
122;;
123;; If the behaviour ever triggers when you did not want it to, you can use the
124;; `so-long-revert' command to restore the buffer to its original state.
125
126;; * Basic configuration
127;; ---------------------
128;; Use M-x customize-group RET so-long RET
129;; (or M-x so-long-customize RET)
130;;
131;; The user options `so-long-target-modes', `so-long-threshold', and
132;; `so-long-max-lines' determine whether action will be taken automatically when
133;; visiting a file, and `so-long-action' determines what will be done.
134
135;; * Actions and menus
136;; -------------------
137;; The user options `so-long-action' and `so-long-action-alist' determine what
138;; will happen when `so-long' and `so-long-revert' are invoked, and you can add
139;; your own custom actions if you wish. The selected action can be invoked
140;; manually with M-x so-long; and in general M-x so-long-revert will undo the
141;; effects of whichever action was used (which is particularly useful when the
142;; action is triggered automatically, but the detection was a 'false positive'.)
143;;
144;; All defined actions are presented in the "So Long" menu, which is visible
145;; whenever long lines have been detected. Selecting an action from the menu
146;; will firstly cause the current action (if any) to be reverted, before the
147;; newly-selected action is invoked.
148;;
149;; Aside from the menu bar, the menu is also available in the mode line --
150;; either via the major mode construct (when `so-long-mode' is active), or in
151;; a separate mode line construct when some other major mode is active.
152
153;; * Files with a file-local 'mode'
154;; --------------------------------
155;; A file-local major mode is likely to be safe even if long lines are detected
156;; (as the author of the file would otherwise be unlikely to have set that mode),
157;; and so these files are treated as special cases. When a file-local 'mode' is
158;; present, the function defined by the `so-long-file-local-mode-function' user
159;; option is called. The default value will cause the `so-long-minor-mode'
160;; action to be used instead of the `so-long-mode' action, if the latter was
161;; going to be used for this file. This is still a conservative default, but
162;; this option can also be configured to inhibit so-long entirely in this
163;; scenario, or to not treat a file-local mode as a special case at all.
164
165;; * Inhibiting and disabling minor modes
166;; --------------------------------------
167;; Certain minor modes cause significant performance issues in the presence of
168;; very long lines, and should be disabled automatically in this situation.
169;;
170;; The simple way to disable most buffer-local minor modes is to add the mode
171;; symbol to the `so-long-minor-modes' list. Several modes are targeted by
172;; default, and it is a good idea to customize this variable to add any
173;; additional buffer-local minor modes that you use which you know to have
174;; performance implications.
175;;
176;; These minor modes are disabled if `so-long-action' is set to either
177;; `so-long-mode' or `so-long-minor-mode'. If `so-long-revert' is called, then
178;; the original values are restored.
179;;
180;; In the case of globalized minor modes, be sure to specify the buffer-local
181;; minor mode, and not the global mode which controls it.
182;;
183;; Note that `so-long-minor-modes' is not useful for other global minor modes
184;; (as distinguished from globalized minor modes), but in some cases it will be
185;; possible to inhibit or otherwise counter-act the behaviour of a global mode
186;; by overriding variables, or by employing hooks (see below). You would need
187;; to inspect the code for a given global mode (on a case by case basis) to
188;; determine whether it's possible to inhibit it for a single buffer -- and if
189;; so, how best to do that, as not all modes are alike.
190
191;; * Overriding variables
192;; ----------------------
193;; `so-long-variable-overrides' is an alist mapping variable symbols to values.
194;; If `so-long-action' is set to either `so-long-mode' or `so-long-minor-mode',
195;; the buffer-local value for each variable in the list is set to the associated
196;; value in the alist. Use this to enforce values which will improve
197;; performance or otherwise avoid undesirable behaviours. If `so-long-revert'
198;; is called, then the original values are restored.
199
200;; * Hooks
201;; -------
202;; `so-long-hook' runs at the end of the `so-long' command, after the configured
203;; action has been invoked.
204;;
205;; Likewise, if the `so-long-revert' command is used to restore the original
206;; state then, once that has happened, `so-long-revert-hook' is run.
207;;
208;; The major and minor modes also provide the usual hooks: `so-long-mode-hook'
209;; for the major mode (running between `change-major-mode-after-body-hook' and
210;; `after-change-major-mode-hook'); and `so-long-minor-mode-hook' (when that
211;; mode is enabled or disabled).
212
213;; * Troubleshooting
214;; -----------------
215;; Any elisp library has the potential to cause performance problems; so while
216;; the default configuration addresses some important common cases, it's
217;; entirely possible that your own config introduces problem cases which are
218;; unknown to this library.
219;;
220;; If visiting a file is still taking a very long time with so-long enabled,
221;; you should test the following command:
222;;
223;; emacs -Q -l so-long -f global-so-long-mode <file>
224;;
225;; For versions of Emacs < 27, use:
226;; emacs -Q -l /path/to/so-long.el -f global-so-long-mode <file>
227;;
228;; If the file loads quickly when that command is used, you'll know that
229;; something in your personal configuration is causing problems. If this
230;; turns out to be a buffer-local minor mode, or a user option, you can
231;; likely alleviate the issue by customizing `so-long-minor-modes' or
232;; `so-long-variable-overrides' accordingly.
233;;
234;; The in-built profiler can be an effective way of discovering the cause
235;; of such problems. Refer to M-: (info "(elisp) Profiling") RET
236;;
237;; In some cases it may be useful to set a file-local `mode' variable to
238;; `so-long-mode', completely bypassing the automated decision process.
239;; Refer to M-: (info "(emacs) Specifying File Variables") RET
240;;
241;; If so-long itself is causing problems, it can be inhibited by setting the
242;; `so-long-enabled' variable to nil, or by disabling the global mode with
243;; M-- M-x global-so-long-mode, or M-: (global-so-long-mode 0)
244
245;; * Example configuration
246;; -----------------------
247;; If you prefer to configure in code rather than via the customize interface,
248;; then you might use something along these lines:
249;;
250;; ;; Enable so-long library.
251;; (when (require 'so-long nil :noerror)
252;; (global-so-long-mode 1)
253;; ;; Basic settings.
254;; (setq so-long-action 'so-long-minor-mode)
255;; (setq so-long-threshold 1000)
256;; (setq so-long-max-lines 100)
257;; ;; Additional target major modes to trigger for.
258;; (mapc (apply-partially 'add-to-list 'so-long-target-modes)
259;; '(sgml-mode nxml-mode))
260;; ;; Additional buffer-local minor modes to disable.
261;; (mapc (apply-partially 'add-to-list 'so-long-minor-modes)
262;; '(diff-hl-mode diff-hl-amend-mode diff-hl-flydiff-mode))
263;; ;; Additional variables to override.
264;; (mapc (apply-partially 'add-to-list 'so-long-variable-overrides)
265;; '((show-trailing-whitespace . nil)
266;; (truncate-lines . nil))))
267
268;; * Other ways of using so-long
269;; -----------------------------
270;; It may prove useful to automatically invoke major mode `so-long-mode' for
271;; certain files, irrespective of whether they contain long lines.
272;;
273;; To target specific files and extensions, using `auto-mode-alist' is the
274;; simplest method. To add such an entry, use:
275;; (add-to-list 'auto-mode-alist (cons REGEXP 'so-long-mode))
276;; Where REGEXP is a regular expression matching the filename. e.g.:
277;;
278;; - Any filename with a particular extension ".foo":
279;; (rx ".foo" eos)
280;;
281;; - Any file in a specific directory:
282;; (rx bos "/path/to/directory/")
283;;
284;; - Only *.c filenames under that directory:
285;; (rx bos "/path/to/directory/" (zero-or-more not-newline) ".c" eos)
286;;
287;; - Match some sub-path anywhere in a filename:
288;; (rx "/sub/path/foo")
289;;
290;; - A specific individual file:
291;; (rx bos "/path/to/file" eos)
292;;
293;; Another way to target individual files is to set a file-local `mode'
294;; variable. Refer to M-: (info "(emacs) Specifying File Variables") RET
295;;
296;; `so-long-minor-mode' can also be called directly if desired. e.g.:
297;; (add-hook 'FOO-mode-hook 'so-long-minor-mode)
298;;
299;; In Emacs 26.1 or later (see "Caveats" below) you also have the option of
300;; using file-local and directory-local variables to determine how `so-long'
301;; behaves. e.g. -*- so-long-action: longlines-mode; -*-
302;;
303;; The buffer-local `so-long-function' and `so-long-revert-function' values may
304;; be set directly (in a major mode hook, for instance), as any existing value
305;; for these variables will be used in preference to the values defined by the
306;; selected action. For file-local or directory-local usage it is preferable to
307;; set only `so-long-action', as all function variables are marked as 'risky',
308;; meaning you would need to add to `safe-local-variable-values' in order to
309;; avoid being queried about them.
310;;
311;; Finally, the `so-long-predicate' user option enables the automated behaviour
312;; to be determined by a custom function, if greater control is needed.
313
314;; * Implementation notes
315;; ----------------------
316;; This library advises `set-auto-mode' (in order to react after Emacs has
317;; chosen the major mode for a buffer), and `hack-local-variables' (so that we
318;; may behave differently when a file-local mode is set). In earlier versions
319;; of Emacs (< 26.1) we also advise `hack-one-local-variable' (to prevent a
320;; file-local mode from restoring the original major mode if we had changed it).
321;;
322;; Many variables are permanent-local because after the normal major mode has
323;; been set, we potentially change the major mode to `so-long-mode', and it's
324;; important that the values which were established prior to that are retained.
325
326;; * Caveats
327;; ---------
328;; The variables affecting the automated behavior of this library (such as
329;; `so-long-action') can be used as file- or dir-local values in Emacs 26+, but
330;; not in previous versions of Emacs. This is on account of improvements made
331;; to `normal-mode' in 26.1, which altered the execution order with respect to
332;; when local variables are processed. In earlier versions of Emacs the local
333;; variables are processed too late, and hence have no effect on the decision-
334;; making process for invoking `so-long'. It is unlikely that equivalent
335;; support will be implemented for older versions of Emacs. The exception to
336;; this caveat is the `mode' pseudo-variable, which is processed early in all
337;; versions of Emacs, and can be set to `so-long-mode' if desired.
338
339;;; * Change Log:
340;;
341;; 1.0 - Included in Emacs 27.1, and in GNU ELPA for prior versions of Emacs.
342;; - New global mode `global-so-long-mode' to enable/disable the library.
343;; - New user option `so-long-action'.
344;; - New user option `so-long-action-alist' defining alternative actions.
345;; - New user option `so-long-variable-overrides'.
346;; - New user option `so-long-skip-leading-comments'.
347;; - New user option `so-long-file-local-mode-function'.
348;; - New user option `so-long-predicate'.
349;; - New variable and function `so-long-function'.
350;; - New variable and function `so-long-revert-function'.
351;; - New command `so-long' to invoke `so-long-function' interactively.
352;; - New command `so-long-revert' to invoke `so-long-revert-function'.
353;; - New minor mode action `so-long-minor-mode' facilitates retaining the
354;; original major mode, while still disabling minor modes and overriding
355;; variables like the major mode `so-long-mode'.
356;; - Support `longlines-mode' as a `so-long-action' option.
357;; - Added "So Long" menu, including all selectable actions.
358;; - Added mode-line indicator, user option `so-long-mode-line-label',
359;; and faces `so-long-mode-line-active', `so-long-mode-line-inactive'.
360;; - New help commands `so-long-commentary' and `so-long-customize'.
361;; - Renamed `so-long-mode-enabled' to `so-long-enabled'.
362;; - Refactored the default hook values using variable overrides
363;; (and returning all the hooks to nil default values).
364;; - Performance improvements for `so-long-detected-long-line-p'.
365;; - Converted defadvice to nadvice.
366;; 0.7.6 - Bug fix for `so-long-mode-hook' losing its default value.
367;; 0.7.5 - Documentation.
368;; - Added sgml-mode and nxml-mode to `so-long-target-modes'.
369;; 0.7.4 - Refactored the handling of `whitespace-mode'.
370;; 0.7.3 - Added customize group `so-long' with user options.
371;; - Added `so-long-original-values' to generalise the storage and
372;; restoration of values from the original mode upon `so-long-revert'.
373;; - Added `so-long-revert-hook'.
374;; 0.7.2 - Remember the original major mode even with M-x `so-long-mode'.
375;; 0.7.1 - Clarified interaction with globalized minor modes.
376;; 0.7 - Handle header 'mode' declarations.
377;; - Hack local variables after reverting to the original major mode.
378;; - Reverted `so-long-max-lines' to a default value of 5.
379;; 0.6.5 - Inhibit globalized `hl-line-mode' and `whitespace-mode'.
380;; - Set `buffer-read-only' by default.
381;; 0.6 - Added `so-long-minor-modes' and `so-long-hook'.
382;; 0.5 - Renamed library to "so-long.el".
383;; - Added explicit `so-long-enable' command to activate our advice.
384;; 0.4 - Amended/documented behaviour with file-local 'mode' variables.
385;; 0.3 - Defer to a file-local 'mode' variable.
386;; 0.2 - Initial release to EmacsWiki.
387;; 0.1 - Experimental.
388
389;;; Code:
390
391(require 'cl-lib)
392
393(add-to-list 'customize-package-emacs-version-alist
394 '(so-long ("1.0" . "27.1")))
395
396(declare-function longlines-mode "longlines")
397(defvar longlines-mode)
398
399(declare-function outline-next-visible-heading "outline")
400(declare-function outline-previous-visible-heading "outline")
401(declare-function outline-toggle-children "outline")
402(declare-function outline-toggle-children "outline")
403
404(defvar so-long-enabled nil
405 "Set to nil to prevent `so-long' from being triggered automatically.
406
407Has no effect if `global-so-long-mode' is not enabled.")
408
409(defvar-local so-long--active nil ; internal use
410 "Non-nil when `so-long' mitigations are in effect.")
411
412(defvar so-long--set-auto-mode nil ; internal use
413 "Non-nil while `set-auto-mode' is executing.")
414
415(defvar so-long--hack-local-variables-no-mode nil ; internal use
416 "Non-nil to prevent `hack-local-variables' applying a 'mode' variable.")
417
418(defvar-local so-long--inhibited nil ; internal use
419 "When non-nil, prevents the `set-auto-mode' advice from calling `so-long'.")
420(put 'so-long--inhibited 'permanent-local t)
421
422(defvar so-long--calling nil ; internal use
423 ;; This prevents infinite recursion if eval:(so-long) is specified
424 ;; as a file- or dir-local variable, and `so-long-action' is set to
425 ;; `so-long-mode' (as that major mode would once again process the
426 ;; local variables, and hence call itself).
427 "Non-nil while `so-long' or `so-long-revert' is executing.")
428
429(defvar-local so-long-detected-p nil
430 "Non-nil if `so-long' has been invoked (even if subsequently reverted).")
431(put 'so-long-detected-p 'permanent-local t)
432
433(defgroup so-long nil
434 "Prevent unacceptable performance degradation with very long lines."
435 :prefix "so-long"
436 :group 'convenience)
437
438(defcustom so-long-threshold 250
439 "Maximum line length permitted before invoking `so-long-function'.
440
441See `so-long-detected-long-line-p' for details."
442 :type 'integer
443 :package-version '(so-long . "1.0")
444 :group 'so-long)
445
446(defcustom so-long-max-lines 5
447 "Number of non-blank, non-comment lines to test for excessive length.
448
449If nil then all lines will be tested, until either a long line is detected,
450or the end of the buffer is reached.
451
452If `so-long-skip-leading-comments' is nil then comments and blank lines will
453be counted.
454
455See `so-long-detected-long-line-p' for details."
456 :type '(choice (integer :tag "Limit")
457 (const :tag "Unlimited" nil))
458 :package-version '(so-long . "1.0")
459 :group 'so-long)
460
461(defcustom so-long-skip-leading-comments t
462 "Non-nil to ignore all leading comments and whitespace.
463
464If the file begins with a shebang (#!), this option also causes that line to be
465ignored even if it doesn't match the buffer's comment syntax, to ensure that
466comments following the shebang will be ignored.
467
468See `so-long-detected-long-line-p' for details."
469 :type 'boolean
470 :package-version '(so-long . "1.0")
471 :group 'so-long)
472
473(defcustom so-long-target-modes
474 '(prog-mode css-mode sgml-mode nxml-mode)
475 "`so-long' affects only these modes and their derivatives.
476
477Our primary use-case is minified programming code, so `prog-mode' covers
478most cases, but there are some exceptions to this.
479
480If t, then all modes are targeted. Note that this is only useful with a
481custom `so-long-predicate', as many file types (archives and binary files,
482for example) can safely contain long lines, and invoking `so-long' on such
483files would prevent Emacs from handling them correctly."
484 ;; Use 'symbol', as 'function' may be unknown => mismatch.
485 :type '(choice (repeat :tag "Specified modes" symbol)
486 (const :tag "All modes" t))
487 :package-version '(so-long . "1.0")
488 :group 'so-long)
489
490(defcustom so-long-predicate 'so-long-detected-long-line-p
491 "Function, called after `set-auto-mode' to decide whether action is needed.
492
493Only called if the major mode is a member of `so-long-target-modes'.
494
495The specified function will be called with no arguments. If it returns non-nil
496then `so-long' will be invoked.
497
498Defaults to `so-long-detected-long-line-p'."
499 :type '(choice (const so-long-detected-long-line-p)
500 (function :tag "Custom function"))
501 :package-version '(so-long . "1.0")
502 :group 'so-long)
503
504;; Silence byte-compiler warning. `so-long-action-alist' is defined below
505;; as a user option; but the definition sequence required for its setter
506;; function means we also need to declare it beforehand.
507(defvar so-long-action-alist)
508
509(defun so-long--action-type ()
510 "Generate a :type for `so-long-action' based on `so-long-action-alist'."
511 ;; :type seemingly cannot be a form to be evaluated on demand, so we
512 ;; endeavour to keep it up-to-date with `so-long-action-alist' by
513 ;; calling this from `so-long--action-alist-setter'.
514 `(radio ,@(mapcar (lambda (x) (list 'const :tag (cadr x) (car x)))
515 (assq-delete-all nil so-long-action-alist))
516 (const :tag "Do nothing" nil)))
517
518(defun so-long--action-alist-setter (option value)
519 "The customize :set function for `so-long-action-alist'."
520 ;; Set the value as normal.
521 (set-default option value)
522 ;; Update the :type of `so-long-action' to present the updated values.
523 (put 'so-long-action 'custom-type (so-long--action-type)))
524
525(defcustom so-long-action-alist
526 '((so-long-mode
527 "Change major mode to so-long-mode"
528 so-long-mode
529 so-long-mode-revert)
530 (so-long-minor-mode
531 "Enable so-long-minor-mode"
532 turn-on-so-long-minor-mode
533 turn-off-so-long-minor-mode)
534 (longlines-mode
535 "Enable longlines-mode"
536 so-long-function-longlines-mode
537 so-long-revert-function-longlines-mode))
538 "Options for `so-long-action'.
539
540Each element is a list comprising (KEY LABEL ACTION REVERT)
541
542KEY is a symbol which is a valid value for `so-long-action', and LABEL is a
543string which describes and represents the key in that option's customize
544interface, and in the \"So Long\" menu. ACTION and REVERT are functions:
545
546ACTION will be the `so-long-function' value when `so-long' is called, and
547REVERT will be the `so-long-revert-function' value, if `so-long-revert' is
548subsequently called."
549 :type '(alist :key-type (symbol :tag "Key" :value <action>)
550 :value-type (list (string :tag "Label" :value "<description>")
551 (function :tag "Action")
552 (function :tag "Revert")))
553 :set #'so-long--action-alist-setter
554 :package-version '(so-long . "1.0")
555 :group 'so-long)
556(put 'so-long-action-alist 'risky-local-variable t)
557
558(defcustom so-long-action 'so-long-mode
559 "The action taken by `so-long' when long lines are detected.
560
561\(Long lines are determined by `so-long-predicate' after `set-auto-mode'.)
562
563The value is a key to one of the options defined by `so-long-action-alist'.
564
565The default action is to replace the original major mode with `so-long-mode'.
566Alternatively, the `so-long-minor-mode' action retains the original major mode
567while still disabling minor modes and overriding variables. These are the only
568standard values for which `so-long-minor-modes' and `so-long-variable-overrides'
569will be automatically processed; but custom actions can also do these things.
570
571The value `longlines-mode' causes that minor mode to be enabled. See
572longlines.el for more details.
573
574Each action likewise determines the behaviour of `so-long-revert'.
575
576If the value is nil, or not defined in `so-long-action-alist', then no action
577will be taken."
578 :type (so-long--action-type)
579 :package-version '(so-long . "1.0")
580 :group 'so-long)
581
582(defvar-local so-long-function nil
583 "The function called by `so-long'.
584
585This should be set in conjunction with `so-long-revert-function'. This usually
586happens automatically, based on the value of `so-long-action'.
587
588The specified function will be called with no arguments, after which
589`so-long-hook' runs.")
590(put 'so-long-function 'permanent-local t)
591
592(defvar-local so-long-revert-function nil
593 "The function called by `so-long-revert'.
594
595This should be set in conjunction with `so-long-function'. This usually
596happens automatically, based on the value of `so-long-action'.
597
598The specified function will be called with no arguments, after which
599`so-long-revert-hook' runs.")
600(put 'so-long-revert-function 'permanent-local t)
601
602(defun so-long-function (&optional action-arg)
603 "The value of `so-long-function', else derive from `so-long-action'.
604
605If ACTION-ARG is provided, it is used in place of `so-long-action'."
606 (or so-long-function
607 (and (or action-arg
608 (setq action-arg so-long-action))
609 (let ((action (assq action-arg so-long-action-alist)))
610 (nth 2 action)))))
611
612(defun so-long-revert-function (&optional action-arg)
613 "The value of `so-long-revert-function', else derive from `so-long-action'.
614
615If ACTION-ARG is provided, it is used in place of `so-long-action'."
616 (or so-long-revert-function
617 (and (or action-arg
618 (setq action-arg so-long-action))
619 (let ((action (assq action-arg so-long-action-alist)))
620 (nth 3 action)))))
621
622(defcustom so-long-file-local-mode-function 'so-long-mode-downgrade
623 "Function to call during `set-auto-mode' when a file-local mode is set.
624
625The specified function will be called with a single argument, being the
626file-local mode which was established.
627
628This happens before `so-long' is called, and so this function can modify the
629subsequent action.
630
631The value `so-long-mode-downgrade' means `so-long-minor-mode' will be used in
632place of `so-long-mode' -- therefore respecting the file-local mode value, yet
633still overriding minor modes and variables (as if `so-long-action' had been set
634to `so-long-minor-mode').
635
636The value `so-long-inhibit' means that so-long will not take any action at all
637for this file.
638
639If nil, then do not treat files with file-local modes any differently to other
640files.
641
642Note that this function is called if a file-local mode is set even if `so-long'
643will not be called, and also if the file-local mode is `so-long-mode'. Custom
644functions may need to test for these cases -- see `so-long-mode-downgrade' for
645an example."
646 :type '(radio (const so-long-mode-downgrade)
647 (const so-long-inhibit)
648 (const :tag "nil: Use so-long-function as normal" nil)
649 (function :tag "Custom function"))
650 :package-version '(so-long . "1.0")
651 :group 'so-long)
652(make-variable-buffer-local 'so-long-file-local-mode-function)
653
654;; `provided-mode-derived-p' was added in 26.1
655(unless (fboundp 'provided-mode-derived-p)
656 (defun provided-mode-derived-p (mode &rest modes)
657 "Non-nil if MODE is derived from one of MODES.
658Uses the `derived-mode-parent' property of the symbol to trace backwards.
659If you just want to check `major-mode', use `derived-mode-p'."
660 (while (and (not (memq mode modes))
661 (setq mode (get mode 'derived-mode-parent))))
662 mode))
663
664(defun so-long-handle-file-local-mode (mode)
665 "Wrapper for calling `so-long-file-local-mode-function'.
666
667The function is called with one argument, MODE, being the file-local mode which
668was established."
669 ;; Handle the special case whereby the file-local mode was `so-long-mode'.
670 ;; In this instance we set `so-long--inhibited', because the file-local mode
671 ;; is already going to do everything that is wanted.
672 (when (provided-mode-derived-p mode 'so-long-mode)
673 (setq so-long--inhibited t))
674 ;; Call `so-long-file-local-mode-function'.
675 (when so-long-file-local-mode-function
676 (funcall so-long-file-local-mode-function mode)))
677
678(defcustom so-long-minor-modes
679 ;; In sorted groups.
680 '(font-lock-mode ;; (Generally the most important).
681 ;; Other standard minor modes:
682 display-line-numbers-mode
683 goto-address-mode
684 goto-address-prog-mode
685 hi-lock-mode
686 highlight-changes-mode
687 hl-line-mode
688 linum-mode
689 nlinum-mode
690 prettify-symbols-mode
691 visual-line-mode
692 whitespace-mode
693 ;; Known third-party modes-of-interest:
694 diff-hl-amend-mode
695 diff-hl-flydiff-mode
696 diff-hl-mode
697 dtrt-indent-mode
698 hl-sexp-mode
699 idle-highlight-mode
700 rainbow-delimiters-mode
701 )
702 ;; It's not clear to me whether all of these would be problematic, but they
703 ;; seemed like reasonable targets. Some are certainly excessive in smaller
704 ;; buffers of minified code, but we should be aiming to maximise performance
705 ;; by default, so that Emacs is as responsive as we can manage in even very
706 ;; large buffers of minified code.
707 "List of buffer-local minor modes to explicitly disable.
708
709The ones which were originally enabled in the buffer are disabled by calling
710them with the numeric argument 0. Unknown modes, and modes which were were not
711enabled, are ignored.
712
713This happens after any globalized minor modes have acted, so that buffer-local
714modes controlled by globalized modes can also be targeted.
715
716By default this happens if `so-long-action' is set to either `so-long-mode'
717or `so-long-minor-mode'. If `so-long-revert' is subsequently invoked, then the
718disabled modes are re-enabled by calling them with the numeric argument 1.
719
720`so-long-hook' can be used where more custom behaviour is desired.
721
722Please submit bug reports to recommend additional modes for this list, whether
723they are in Emacs core, GNU ELPA, or elsewhere."
724 :type '(repeat symbol) ;; not function, as may be unknown => mismatch.
725 :package-version '(so-long . "1.0")
726 :group 'so-long)
727
728(defcustom so-long-variable-overrides
729 '((bidi-display-reordering . nil)
730 (buffer-read-only . t)
731 (global-hl-line-mode . nil)
732 (line-move-visual . t)
733 (show-paren-mode . nil)
734 (truncate-lines . nil)
735 (which-func-mode . nil))
736 "Variables to override, and the values to override them with.
737
738The variables are given buffer-local values. By default this happens if
739`so-long-action' is set to either `so-long-mode' or `so-long-minor-mode'.
740
741If `so-long-revert' is subsequently invoked, then the variables are restored
742to their original states."
743 :type '(alist :key-type (variable :tag "Variable")
744 :value-type (sexp :tag "Value"))
745 :options '((bidi-display-reordering boolean)
746 (buffer-read-only boolean)
747 (global-hl-line-mode boolean)
748 (line-move-visual boolean)
749 (show-paren-mode boolean)
750 (truncate-lines boolean)
751 (which-func-mode boolean))
752 :package-version '(so-long . "1.0")
753 :group 'so-long)
754
755(defcustom so-long-hook nil
756 "List of functions to call after `so-long' is called.
757
758See also `so-long-revert-hook'."
759 :type 'hook
760 :package-version '(so-long . "1.0")
761 :group 'so-long)
762
763(defcustom so-long-revert-hook nil
764 "List of functions to call after `so-long-revert' is called.
765
766See also `so-long-hook'."
767 :type 'hook
768 :package-version '(so-long . "1.0")
769 :group 'so-long)
770
771(defcustom so-long-mode-line-label "So Long"
772 "Text label of `so-long-mode-line-info' when long lines are detected.
773
774If nil, no mode line indicator will be displayed."
775 :type '(choice (string :tag "String")
776 (const :tag "None" nil))
777 :package-version '(so-long . "1.0")
778 :group 'so-long)
779
780(defface so-long-mode-line-active
781 '((t :inherit mode-line-emphasis))
782 "Face for `so-long-mode-line-info' when mitigations are active."
783 :package-version '(so-long . "1.0")
784 :group 'so-long)
785
786(defface so-long-mode-line-inactive
787 '((t :inherit mode-line-inactive))
788 "Face for `so-long-mode-line-info' when mitigations have been reverted."
789 :package-version '(so-long . "1.0")
790 :group 'so-long)
791
792;; Modes that go slowly and line lengths excessive
793;; Font-lock performance becoming oppressive
794;; All of my CPU tied up with strings
795;; These are a few of my least-favourite things
796
797(defvar-local so-long-original-values nil
798 "Alist holding the buffer's original `major-mode' value, and other data.
799
800Any values to be restored by `so-long-revert' can be stored here by the
801`so-long-function' or during `so-long-hook'. `so-long' itself stores the
802original states for `so-long-variable-overrides' and `so-long-minor-modes',
803so these values are available to custom actions by default.
804
805See also `so-long-remember' and `so-long-original'.")
806(put 'so-long-original-values 'permanent-local t)
807
808(defun so-long-original (key &optional exists)
809 "Return the current value for KEY in `so-long-original-values'.
810
811If you need to differentiate between a stored value of nil and no stored value
812at all, make EXISTS non-nil. This then returns the result of `assq' directly:
813nil if no value was set, and a cons cell otherwise."
814 (if exists
815 (assq key so-long-original-values)
816 (cadr (assq key so-long-original-values))))
817
818(defun so-long-remember (variable)
819 "Store the value of VARIABLE in `so-long-original-values'.
820
821We additionally store a boolean value which indicates whether that value was
822buffer-local."
823 (when (boundp variable)
824 (setq so-long-original-values
825 (assq-delete-all variable so-long-original-values))
826 (push (list variable
827 (symbol-value variable)
828 (local-variable-p variable))
829 so-long-original-values)))
830
831(defun so-long-remember-all (&optional reset)
832 "Remember the current variable and minor mode values.
833
834Stores the existing value for each entry in `so-long-variable-overrides'.
835Stores the name of each enabled mode from the list `so-long-minor-modes'.
836
837If RESET is non-nil, remove any existing values before storing the new ones."
838 (when reset
839 (setq so-long-original-values nil))
840 (dolist (ovar so-long-variable-overrides)
841 (so-long-remember (car ovar)))
842 (dolist (mode so-long-minor-modes)
843 (when (and (boundp mode) mode)
844 (so-long-remember mode))))
845
846(defun so-long-menu ()
847 "Dynamically generate the \"So Long\" menu."
848 ;; (info "(elisp) Menu Example")
849 (let ((map (make-sparse-keymap "So Long"))
850 (help-map (make-sparse-keymap "Help")))
851 ;; `so-long-revert'.
852 (define-key-after map [so-long-revert]
853 '(menu-item "Revert to normal" so-long-menu-item-revert
854 :enable (and so-long-revert-function
855 so-long--active)))
856 ;; `so-long-menu-item-replace-action' over `so-long-action-alist'.
857 (define-key-after map [so-long-actions-separator]
858 '(menu-item "--"))
859 (dolist (item so-long-action-alist)
860 (cl-destructuring-bind (key label actionfunc revertfunc)
861 item
862 (define-key-after map (vector key)
863 `(menu-item
864 ,label
865 ,(let ((sym (make-symbol "so-long-menu-item-replace-action")))
866 ;; Using a symbol here, so that `describe-key' on the menu item
867 ;; produces the `so-long-menu-item-replace-action' documentation.
868 (defalias sym
869 (apply-partially #'so-long-menu-item-replace-action item)
870 (documentation #'so-long-menu-item-replace-action))
871 (put sym 'interactive-form '(interactive))
872 sym)
873 :enable (not (and so-long--active
874 (eq ',actionfunc so-long-function)
875 (eq ',revertfunc so-long-revert-function)))))))
876 ;; "Help" sub-menu.
877 (define-key-after map [so-long-help-separator]
878 '(menu-item "--"))
879 (define-key-after map [so-long-help]
880 `(menu-item "Help" ,help-map))
881 (define-key-after help-map [so-long-commentary]
882 '(menu-item "Commentary" so-long-commentary))
883 (define-key-after help-map [so-long-customize]
884 '(menu-item "Customize" so-long-customize))
885 map))
886
887(defun so-long-menu-click-window ()
888 "Return the window for a click in the So Long menu.
889
890Commands in the mode-line menu may be triggered by mouse when some other window
891is selected, so we need to make sure we are acting on the correct buffer."
892 ;; Refer to (info "(elisp) Click Events") regarding the form of the mouse
893 ;; position list for clicks in the mode line.
894 (or (and (mouse-event-p last-nonmenu-event)
895 (windowp (car (cadr last-nonmenu-event))) ; cXXXr only available
896 (car (cadr last-nonmenu-event))) ; since Emacs 26.1
897 (selected-window)))
898
899(defun so-long-menu-item-revert ()
900 "Invoke `so-long-revert'."
901 (interactive)
902 (with-selected-window (so-long-menu-click-window)
903 (so-long-revert)))
904
905(defun so-long-menu-item-replace-action (replacement)
906 "Revert the current action and invoke the specified replacement.
907
908REPLACEMENT is a `so-long-action-alist' item."
909 (interactive)
910 (with-selected-window (so-long-menu-click-window)
911 (when so-long--active
912 (so-long-revert))
913 (cl-destructuring-bind (_key _label actionfunc revertfunc)
914 replacement
915 (setq so-long-function actionfunc)
916 (setq so-long-revert-function revertfunc)
917 (setq this-command 'so-long)
918 (so-long))))
919
920;;;###autoload
921(defun so-long-commentary ()
922 "View the so-long documentation in `outline-mode'."
923 (interactive)
924 (let ((buf "*So Long: Commentary*"))
925 (when (buffer-live-p (get-buffer buf))
926 (kill-buffer buf))
927 ;; Use `finder-commentary' to generate the buffer.
928 (require 'finder)
929 (cl-letf (((symbol-function 'finder-summary) #'ignore))
930 (finder-commentary "so-long"))
931 (let ((inhibit-read-only t))
932 (when (looking-at "^Commentary:\n\n")
933 (replace-match "so-long.el\n\n"))
934 (save-excursion
935 (while (re-search-forward "^-+$" nil :noerror)
936 (replace-match ""))))
937 (rename-buffer buf)
938 ;; Enable `outline-mode' and `view-mode' for user convenience.
939 (outline-mode)
940 (view-mode 1)
941 ;; Add some custom local bindings.
942 (let ((map (make-sparse-keymap)))
943 (define-key map (kbd "TAB") #'outline-toggle-children)
944 (define-key map (kbd "<M-tab>") #'outline-toggle-children)
945 (define-key map (kbd "M-n") #'outline-next-visible-heading)
946 (define-key map (kbd "M-p") #'outline-previous-visible-heading)
947 (set-keymap-parent map (current-local-map))
948 (use-local-map map))
949 ;; Display the So Long menu.
950 (so-long--ensure-enabled)
951 (let ((so-long-action nil))
952 (so-long))))
953
954;;;###autoload
955(defun so-long-customize ()
956 "Open the so-long `customize' group."
957 (interactive)
958 (customize-group 'so-long))
959
960(defvar-local so-long-mode-line-info nil
961 "Mode line construct displayed when `so-long' has been triggered.
962
963Displayed as part of `mode-line-misc-info'.
964
965`so-long-mode-line-label' defines the text to be displayed (if any).
966
967Face `so-long-mode-line-active' is used while mitigations are active, and
968`so-long-mode-line-inactive' is used if `so-long-revert' is called.
969
970Not displayed when `so-long-mode' is enabled, as the major mode construct
971serves the same purpose.")
972
973;; Ensure we can display text properties on this value in the mode line.
974;; See (info "(elisp) Mode Line Data") or (info "(elisp) Properties in Mode").
975(put 'so-long-mode-line-info 'risky-local-variable t)
976
977(defun so-long-mode-line-info ()
978 "Returns the mode line construct for variable `so-long-mode-line-info'."
979 (let ((map (make-sparse-keymap)))
980 (define-key map (kbd "<mode-line> <down-mouse-1>")
981 `(menu-item "" nil
982 :filter ,(lambda (_cmd) (so-long-menu))))
983 ;; Mode line construct.
984 ;; n.b. It's necessary for `so-long-mode-line-info' to have a non-nil
985 ;; risky-local-variable property, as otherwise the text properties won't
986 ;; be rendered.
987 `(so-long-mode-line-label
988 ("" (:eval (propertize so-long-mode-line-label
989 'mouse-face 'highlight
990 'keymap ',map
991 'help-echo t ;; Suppress the mode-line value
992 'face (if so-long--active
993 'so-long-mode-line-active
994 'so-long-mode-line-inactive)))
995 " "))))
996
997;; When the line's long
998;; When the mode's slow
999;; When Emacs is sad
1000;; We change automatically to faster code
1001;; And then I won't feel so mad
1002
1003(defun so-long-detected-long-line-p ()
1004 "Determine whether the current buffer contains long lines.
1005
1006Following any initial comments and blank lines, the next N lines of the buffer
1007will be tested for excessive length (where \"excessive\" means above
1008`so-long-threshold', and N is `so-long-max-lines').
1009
1010Returns non-nil if any such excessive-length line is detected.
1011
1012If `so-long-skip-leading-comments' is nil then the N lines will be counted
1013starting from the first line of the buffer. In this instance you will likely
1014want to increase `so-long-max-lines' to allow for possible comments.
1015
1016This is the default value of `so-long-predicate'."
1017 (let ((count 0) start)
1018 (save-excursion
1019 (goto-char (point-min))
1020 (when so-long-skip-leading-comments
1021 ;; Skip the shebang line, if any. This is not necessarily comment
1022 ;; syntax, so we need to treat it specially.
1023 (when (looking-at "#!")
1024 (forward-line 1))
1025 ;; Move past any leading whitespace and/or comments.
1026 ;; We use narrowing to limit the amount of text being processed at any
1027 ;; given time, where possible, as this makes things more efficient.
1028 (setq start (point))
1029 (while (save-restriction
1030 (narrow-to-region start (min (+ (point) so-long-threshold)
1031 (point-max)))
1032 (goto-char start)
1033 ;; Possibilities for `comment-forward' are:
1034 ;; 0. No comment; no movement; return nil.
1035 ;; 1. Comment is <= point-max; move end of comment; return t.
1036 ;; 2. Comment is truncated; move point-max; return nil.
1037 ;; 3. Only whitespace; move end of WS; return nil.
1038 (prog1 (or (comment-forward 1) ;; Moved past a comment.
1039 (and (eobp) ;; Truncated, or WS up to point-max.
1040 (progn ;; Widen and retry.
1041 (widen)
1042 (goto-char start)
1043 (comment-forward 1))))
1044 ;; Otherwise there was no comment, and we return nil.
1045 ;; If there was whitespace, we moved past it.
1046 (setq start (point)))))
1047 ;; We're at the first non-comment line, but we may have moved past
1048 ;; indentation whitespace, so move back to the beginning of the line
1049 ;; unless we're at the end of the buffer (in which case there was no
1050 ;; non-comment/whitespace content in the buffer at all).
1051 (unless (eobp)
1052 (forward-line 0)))
1053 ;; Start looking for long lines.
1054 ;; `while' will ultimately return nil if we do not `throw' a result.
1055 (catch 'excessive
1056 (while (and (not (eobp))
1057 (or (not so-long-max-lines)
1058 (< count so-long-max-lines)))
1059 (setq start (point))
1060 (save-restriction
1061 (narrow-to-region start (min (+ start 1 so-long-threshold)
1062 (point-max)))
1063 (forward-line 1))
1064 ;; If point is not now at the beginning of a line, then the previous
1065 ;; line was long -- with the exception of when point is at the end of
1066 ;; the buffer (bearing in mind that we have widened again), in which
1067 ;; case there was a short final line with no newline. There is an
1068 ;; edge case when such a final line is exactly (1+ so-long-threshold)
1069 ;; chars long, so if we're at (eobp) we need to verify the length in
1070 ;; order to be consistent.
1071 (unless (or (bolp)
1072 (and (eobp) (<= (- (point) start)
1073 so-long-threshold)))
1074 (throw 'excessive t))
1075 (setq count (1+ count)))))))
1076
1077(defun so-long-function-longlines-mode ()
1078 "Enable minor mode `longlines-mode'."
1079 (require 'longlines)
1080 (so-long-remember 'longlines-mode)
1081 (longlines-mode 1))
1082
1083(defun so-long-revert-function-longlines-mode ()
1084 "Restore original state of `longlines-mode'."
1085 (require 'longlines)
1086 (let ((state (so-long-original 'longlines-mode :exists)))
1087 (if state
1088 (unless (equal (cadr state) longlines-mode)
1089 (longlines-mode (if (cadr state) 1 0)))
1090 (longlines-mode 0))))
1091
1092(defun turn-on-so-long-minor-mode ()
1093 "Enable minor mode `so-long-minor-mode'."
1094 (so-long-minor-mode 1))
1095
1096(defun turn-off-so-long-minor-mode ()
1097 "Disable minor mode `so-long-minor-mode'."
1098 (so-long-minor-mode 0))
1099
1100;;;###autoload
1101(define-minor-mode so-long-minor-mode
1102 "This is the minor mode equivalent of `so-long-mode'.
1103
1104Any active minor modes listed in `so-long-minor-modes' are disabled for the
1105current buffer, and buffer-local values are assigned to variables in accordance
1106with `so-long-variable-overrides'.
1107
1108This minor mode is a standard `so-long-action' option."
1109 nil nil nil
1110 (if so-long-minor-mode ;; We are enabling the mode.
1111 (progn
1112 ;; Housekeeping. `so-long-minor-mode' might be invoked directly rather
1113 ;; than via `so-long', so replicate the necessary behaviours. The minor
1114 ;; mode also cares about whether `so-long' was already active, as we do
1115 ;; not want to remember values which were potentially overridden already.
1116 (unless (or so-long--calling so-long--active)
1117 (so-long--ensure-enabled)
1118 (setq so-long--active t
1119 so-long-detected-p t
1120 so-long-function 'turn-on-so-long-minor-mode
1121 so-long-revert-function 'turn-off-so-long-minor-mode)
1122 (so-long-remember-all :reset)
1123 (unless (derived-mode-p 'so-long-mode)
1124 (setq so-long-mode-line-info (so-long-mode-line-info))))
1125 ;; Now perform the overrides.
1126 (so-long-disable-minor-modes)
1127 (so-long-override-variables))
1128 ;; We are disabling the mode.
1129 (unless so-long--calling ;; Housekeeping.
1130 (when (eq so-long-function 'turn-on-so-long-minor-mode)
1131 (setq so-long--active nil))
1132 (unless (derived-mode-p 'so-long-mode)
1133 (setq so-long-mode-line-info (so-long-mode-line-info))))
1134 ;; Restore the overridden settings.
1135 (so-long-restore-minor-modes)
1136 (so-long-restore-variables)))
1137
1138;; How do you solve a problem like a long line?
1139;; How do you stop a mode from slowing down?
1140;; How do you cope with processing a long line?
1141;; A bit of advice! A mode! A workaround!
1142
1143(defvar so-long-mode-map
1144 (let ((map (make-sparse-keymap)))
1145 (define-key map (kbd "C-c C-c") 'so-long-revert)
1146 ;; Define the major mode menu. We have an awkward issue whereby
1147 ;; [menu-bar so-long] is already defined in the global map and is
1148 ;; :visible so-long-detected-p, but we also want this to be
1149 ;; available via the major mode construct in the mode line.
1150 ;; The following achieves the desired end result, as :visible nil
1151 ;; prevents this from duplicating its contents in the menu bar,
1152 ;; but still includes it in the mode line.
1153 (define-key map [menu-bar so-long]
1154 `(menu-item "" nil
1155 :visible nil
1156 :filter ,(lambda (_cmd) (so-long-menu))))
1157 map)
1158 "Major mode keymap and menu for `so-long-mode'.")
1159
1160;;;###autoload
1161(define-derived-mode so-long-mode nil "So Long"
1162 "This major mode is the default `so-long-action' option.
1163
1164The normal reason for this mode being active is that `global-so-long-mode' is
1165enabled, and `so-long-predicate' has detected that the file contains long lines.
1166
1167Many Emacs modes struggle with buffers which contain excessively long lines,
1168and may consequently cause unacceptable performance issues.
1169
1170This is commonly on account of 'minified' code (i.e. code has been compacted
1171into the smallest file size possible, which often entails removing newlines
1172should they not be strictly necessary). These kinds of files are typically
1173not intended to be edited, so not providing the usual editing mode in these
1174cases will rarely be an issue.
1175
1176This major mode disables any active minor modes listed in `so-long-minor-modes'
1177for the current buffer, and buffer-local values are assigned to variables in
1178accordance with `so-long-variable-overrides'.
1179
1180To restore the original major mode (along with the minor modes and variable
1181values), despite potential performance issues, type \\[so-long-revert].
1182
1183Use \\[so-long-commentary] for more information.
1184
1185Use \\[so-long-customize] to configure the behaviour."
1186 ;; Housekeeping. `so-long-mode' might be invoked directly rather than via
1187 ;; `so-long', so replicate the necessary behaviours. We could use this same
1188 ;; test in `so-long-after-change-major-mode' to run `so-long-hook', but that's
1189 ;; not so obviously the right thing to do, so I've omitted it for now.
1190 (unless so-long--calling
1191 (so-long--ensure-enabled)
1192 (setq so-long--active t
1193 so-long-detected-p t
1194 so-long-function 'so-long-mode
1195 so-long-revert-function 'so-long-mode-revert))
1196 ;; Use `after-change-major-mode-hook' to disable minor modes and override
1197 ;; variables. Append, to act after any globalized modes have acted.
1198 (add-hook 'after-change-major-mode-hook
1199 'so-long-after-change-major-mode :append :local)
1200 ;; Override variables. This is the first of two instances where we do this
1201 ;; (the other being `so-long-after-change-major-mode'). It is desirable to
1202 ;; set variables here in order to cover cases where the setting of a variable
1203 ;; influences how a global minor mode behaves in this buffer.
1204 (so-long-override-variables)
1205 ;; Hide redundant mode-line information (our major mode info replicates this).
1206 (setq so-long-mode-line-info nil)
1207 ;; Inform the user about our major mode hijacking.
1208 (unless (or so-long--inhibited so-long--set-auto-mode)
1209 (message (concat "Changed to %s (from %s)"
1210 (unless (or (eq this-command 'so-long)
1211 (and (symbolp this-command)
1212 (provided-mode-derived-p this-command
1213 'so-long-mode)))
1214 " on account of line length")
1215 ". %s to revert.")
1216 major-mode
1217 (or (so-long-original 'major-mode) "<unknown>")
1218 (substitute-command-keys "\\[so-long-revert]"))))
1219
1220(defun so-long--change-major-mode ()
1221 ;; Advice, enabled with:
1222 ;; (advice-add 'so-long-mode :before #'so-long--change-major-mode)
1223 ;;
1224 ;; n.b. `major-mode-suspend' and `major-mode-restore' are new in Emacs 27, and
1225 ;; related to what we're doing here; but it's not worth going to the effort of
1226 ;; using those (conditionally, only for 27+) when we have our own framework
1227 ;; for remembering and restoring this buffer state (amongst other things).
1228 "Ensure that `so-long-mode' knows the original `major-mode'.
1229
1230This advice acts before `so-long-mode', with the previous mode still active."
1231 (unless (derived-mode-p 'so-long-mode)
1232 ;; Housekeeping. `so-long-mode' might be invoked directly rather than
1233 ;; via `so-long', so replicate the necessary behaviours.
1234 (unless so-long--calling
1235 (so-long-remember-all :reset))
1236 ;; Remember the original major mode, regardless.
1237 (so-long-remember 'major-mode)))
1238
1239(advice-add 'so-long-mode :before #'so-long--change-major-mode)
1240
1241(defun so-long-after-change-major-mode ()
1242 "Run by `so-long-mode' in `after-change-major-mode-hook'.
1243
1244Calls `so-long-disable-minor-modes' and `so-long-override-variables'."
1245 ;; Disable minor modes.
1246 (so-long-disable-minor-modes)
1247 ;; Override variables (again). We already did this in `so-long-mode' in
1248 ;; order that variables which affect global/globalized minor modes can have
1249 ;; that effect; however it's feasible that one of the minor modes disabled
1250 ;; above might have reverted one of these variables, so we re-enforce them.
1251 ;; (For example, disabling `visual-line-mode' sets `line-move-visual' to
1252 ;; nil, when for our purposes it is preferable for it to be non-nil).
1253 (so-long-override-variables))
1254
1255(defun so-long-disable-minor-modes ()
1256 "Disable any active minor modes listed in `so-long-minor-modes'."
1257 (dolist (mode so-long-minor-modes)
1258 (when (and (boundp mode) mode)
1259 (funcall mode 0))))
1260
1261(defun so-long-restore-minor-modes ()
1262 "Restore the minor modes which were disabled.
1263
1264The modes are enabled in accordance with what was remembered in `so-long'."
1265 (dolist (mode so-long-minor-modes)
1266 (when (and (so-long-original mode)
1267 (boundp mode)
1268 (not (symbol-value mode)))
1269 (funcall mode 1))))
1270
1271(defun so-long-override-variables ()
1272 "Set the buffer-local values defined by `so-long-variable-overrides'."
1273 (dolist (ovar so-long-variable-overrides)
1274 (set (make-local-variable (car ovar)) (cdr ovar))))
1275
1276(defun so-long-restore-variables ()
1277 "Restore the remembered values for the overridden variables.
1278
1279The variables are set in accordance with what was remembered in `so-long'."
1280 (dolist (ovar so-long-variable-overrides)
1281 (so-long-restore-variable (car ovar))))
1282
1283(defun so-long-restore-variable (variable)
1284 "Restore the remembered value (if any) for VARIABLE."
1285 ;; In the instance where `so-long-mode-revert' has just reverted the major
1286 ;; mode, note that `kill-all-local-variables' was already called by the
1287 ;; original mode function, and so these 'overridden' variables may now have
1288 ;; global rather than buffer-local values.
1289 (let* ((remembered (so-long-original variable :exists))
1290 (originally-local (nth 2 remembered)))
1291 (if originally-local
1292 ;; The variable originally existed with a buffer-local value, so we
1293 ;; restore it as such (even if the global value is a match).
1294 (set (make-local-variable variable) (cadr remembered))
1295 ;; Either this variable did not exist initially, or it did not have a
1296 ;; buffer-local value at that time. In either case we kill the current
1297 ;; buffer-local value (if any) in order to restore the original state.
1298 ;;
1299 ;; It's possible that the global value has *changed* in the interim; but
1300 ;; we can't know whether it's best to use the new global value, or retain
1301 ;; the old value as a buffer-local value, so we keep it simple.
1302 (kill-local-variable variable))))
1303
1304(defun so-long-mode-revert ()
1305 "Call the `major-mode' which was selected before `so-long-mode' replaced it.
1306
1307Re-process local variables, and restore overridden variables and minor modes.
1308
1309This is the `so-long-revert-function' for `so-long-mode'."
1310 (interactive)
1311 (let ((so-long-original-mode (so-long-original 'major-mode)))
1312 (unless so-long-original-mode
1313 (error "Original mode unknown."))
1314 (funcall so-long-original-mode)
1315 ;; Emacs 26+ has already called `hack-local-variables' (during
1316 ;; `run-mode-hooks'; provided there was a `buffer-file-name'), but for older
1317 ;; versions we need to call it here. In Emacs 26+ the revised 'HANDLE-MODE'
1318 ;; argument is set to `no-mode' (being the non-nil-and-non-t behaviour),
1319 ;; which we mimic here by binding `so-long--hack-local-variables-no-mode',
1320 ;; in order to prevent a local 'mode' variable from clobbering the major
1321 ;; mode we have just called.
1322 (when (< emacs-major-version 26)
1323 (let ((so-long--hack-local-variables-no-mode t))
1324 (hack-local-variables)))
1325 ;; Restore minor modes.
1326 (so-long-restore-minor-modes)
1327 ;; Restore overridden variables.
1328 ;; `kill-all-local-variables' was already called by the original mode
1329 ;; function, so we may be seeing global values.
1330 (so-long-restore-variables)
1331 ;; Restore the mode line construct.
1332 (unless (derived-mode-p 'so-long-mode)
1333 (setq so-long-mode-line-info (so-long-mode-line-info)))))
1334
1335(defun so-long-mode-downgrade (&optional mode)
1336 "The default value for `so-long-file-local-mode-function'.
1337
1338A buffer-local 'downgrade' from `so-long-mode' to `so-long-minor-mode'.
1339
1340When `so-long-function' is set to `so-long-mode', then we change it to to
1341`turn-on-so-long-minor-mode' instead -- retaining the file-local major
1342mode, but still doing everything else that `so-long-mode' would have done.
1343`so-long-revert-function' is likewise updated.
1344
1345If `so-long-function' has any value other than `so-long-mode', we do nothing,
1346as if `so-long-file-local-mode-function' was nil.
1347
1348We also do nothing if MODE (the file-local mode) has the value `so-long-mode',
1349because we do not want to downgrade the major mode in that scenario."
1350 ;; Do nothing if the file-local mode was `so-long-mode'.
1351 (unless (provided-mode-derived-p mode 'so-long-mode)
1352 ;; Act only if `so-long-mode' would be enabled by the current action.
1353 (when (and (symbolp (so-long-function))
1354 (provided-mode-derived-p (so-long-function) 'so-long-mode))
1355 ;; Downgrade from `so-long-mode' to the `so-long-minor-mode' behaviour.
1356 (setq so-long-function 'turn-on-so-long-minor-mode
1357 so-long-revert-function 'turn-off-so-long-minor-mode))))
1358
1359(defun so-long-inhibit (&optional _mode)
1360 "Prevent so-long from having any effect at all.
1361
1362This is a `so-long-file-local-mode-function' option."
1363 (setq so-long--inhibited t))
1364
1365(defun so-long--check-header-modes ()
1366 ;; See also "Files with a file-local 'mode'" in the Commentary.
1367 "Handles the header-comments processing in `set-auto-mode'.
1368
1369`set-auto-mode' has some special-case code to handle the 'mode' pseudo-variable
1370when set in the header comment. This runs outside of `hack-local-variables'
1371and cannot be conveniently intercepted, so we are forced to replicate it here.
1372
1373This special-case code will ultimately be removed from Emacs, as it exists to
1374deal with a deprecated feature; but until then we need to replicate it in order
1375to inhibit our own behaviour in the presence of a header comment 'mode'
1376declaration.
1377
1378If a file-local mode is detected in the header comment, then we call the
1379function defined by `so-long-file-local-mode-function'."
1380 ;; The following code for processing MODE declarations in the header
1381 ;; comments is copied verbatim from `set-auto-mode', because we have
1382 ;; no way of intercepting it.
1383 ;;
1384 (let ((try-locals (not (inhibit-local-variables-p)))
1385 end _done _mode modes)
1386 ;; Once we drop the deprecated feature where mode: is also allowed to
1387 ;; specify minor-modes (ie, there can be more than one "mode:"), we can
1388 ;; remove this section and just let (hack-local-variables t) handle it.
1389 ;; Find a -*- mode tag.
1390 (save-excursion
1391 (goto-char (point-min))
1392 (skip-chars-forward " \t\n")
1393 ;; Note by design local-enable-local-variables does not matter here.
1394 (and enable-local-variables
1395 try-locals
1396 (setq end (set-auto-mode-1))
1397 (if (save-excursion (search-forward ":" end t))
1398 ;; Find all specifications for the `mode:' variable
1399 ;; and execute them left to right.
1400 (while (let ((case-fold-search t))
1401 (or (and (looking-at "mode:")
1402 (goto-char (match-end 0)))
1403 (re-search-forward "[ \t;]mode:" end t)))
1404 (skip-chars-forward " \t")
1405 (let ((beg (point)))
1406 (if (search-forward ";" end t)
1407 (forward-char -1)
1408 (goto-char end))
1409 (skip-chars-backward " \t")
1410 (push (intern (concat (downcase (buffer-substring
1411 beg (point)))
1412 "-mode"))
1413 modes)))
1414 ;; Simple -*-MODE-*- case.
1415 (push (intern (concat (downcase (buffer-substring (point) end))
1416 "-mode"))
1417 modes))))
1418
1419 ;; `so-long' now processes the resulting mode list. If any modes were
1420 ;; listed, we assume that one of them is a major mode. It's possible that
1421 ;; this isn't true, but the buffer would remain in fundamental-mode if that
1422 ;; were the case, so it is very unlikely. For the purposes of passing a
1423 ;; value to `so-long-handle-file-local-mode' we assume the major mode was
1424 ;; the first mode specified (in which case it is the last in the list).
1425 (when modes
1426 (so-long-handle-file-local-mode (car (last modes))))))
1427
1428;; Lisp advice, Lisp advice
1429;; Every calling you greet me
1430;; Code of mine, redefined
1431;; You look happy to tweak me
1432
1433(defun so-long--hack-local-variables (orig-fun &optional handle-mode &rest args)
1434 ;; Advice, enabled with:
1435 ;; (advice-add 'hack-local-variables :around #'so-long--hack-local-variables)
1436 ;;
1437 ;; See also "Files with a file-local 'mode'" in the Commentary.
1438 "Ensure that `so-long' defers to file-local mode declarations if necessary.
1439
1440If a file-local mode is detected, then we call the function defined by
1441`so-long-file-local-mode-function'.
1442
1443This advice acts after the HANDLE-MODE:t call to `hack-local-variables'.
1444\(MODE-ONLY in Emacs versions < 26).
1445
1446File-local header comments are currently an exception, and are processed by
1447`so-long--check-header-modes' (see which for details)."
1448 ;; The first arg to `hack-local-variables' is HANDLE-MODE since Emacs 26.1,
1449 ;; and MODE-ONLY in earlier versions. In either case we are interested in
1450 ;; whether it has the value `t'.
1451 (let ((retval (apply orig-fun handle-mode args)))
1452 (and (eq handle-mode t)
1453 retval ; A file-local mode was set.
1454 (so-long-handle-file-local-mode retval))
1455 retval))
1456
1457(defun so-long--set-auto-mode (orig-fun &rest args)
1458 ;; Advice, enabled with:
1459 ;; (advice-add 'set-auto-mode :around #'so-long--set-auto-mode)
1460 "Maybe call `so-long' for files with very long lines.
1461
1462This advice acts after `set-auto-mode' has set the buffer's major mode.
1463
1464We can't act before this point, because some major modes must be exempt
1465\(binary file modes, for example). Instead, we act only when the selected
1466major mode is a member (or derivative of a member) of `so-long-target-modes'.
1467
1468`so-long-predicate' then determines whether the mode change is needed."
1469 (setq so-long--inhibited nil) ; is permanent-local
1470 (when so-long-enabled
1471 (so-long--check-header-modes)) ; may cause `so-long--inhibited' to be set.
1472 (let ((so-long--set-auto-mode t))
1473 ;; Call `set-auto-mode'.
1474 (apply orig-fun args)) ; may cause `so-long--inhibited' to be set.
1475 ;; Test the new major mode for long lines.
1476 (and so-long-enabled
1477 (not so-long--inhibited)
1478 (not so-long--calling)
1479 (or (eq so-long-target-modes t)
1480 (apply #'derived-mode-p so-long-target-modes))
1481 (setq so-long-detected-p (funcall so-long-predicate))
1482 (so-long)))
1483
1484(defun so-long--hack-one-local-variable (orig-fun var val)
1485 ;; Advice, enabled with:
1486 ;; (advice-add 'hack-one-local-variable :around
1487 ;; #'so-long--hack-one-local-variable)
1488 "Prevent the original major mode being restored after `so-long-mode'.
1489
1490This advice is needed and enabled only for Emacs versions < 26.1.
1491
1492If the local 'mode' pseudo-variable is used, `set-auto-mode-0' will call it
1493firstly, and subsequently `hack-one-local-variable' may call it again.
1494
1495Usually `hack-one-local-variable' tries to avoid processing that second call,
1496by testing the value against `major-mode'; but as we may have changed the
1497major mode to `so-long-mode' by this point, that protection is insufficient
1498and so we need to perform our own test.
1499
1500We likewise need to support an equivalent of the `no-mode' behaviour in 26.1+
1501to ensure that `so-long-mode-revert' will not restore a file-local mode again
1502after it has already reverted to the original mode.
1503
1504The changes to `normal-mode' in Emacs 26.1 modified the execution order, and
1505makes this advice unnecessary. The relevant NEWS entry is:
1506
1507** File local and directory local variables are now initialized each
1508time the major mode is set, not just when the file is first visited.
1509These local variables will thus not vanish on setting a major mode."
1510 (if (eq var 'mode)
1511 ;; Adapted directly from `hack-one-local-variable'
1512 (let ((mode (intern (concat (downcase (symbol-name val))
1513 "-mode"))))
1514 (unless (or so-long--hack-local-variables-no-mode
1515 (let ((origmode (so-long-original 'major-mode)))
1516 ;; We bind origmode because (indirect-function nil) is an
1517 ;; error in Emacs versions < 25.1, and so we need to test
1518 ;; it first.
1519 (and origmode
1520 (eq (indirect-function mode)
1521 (indirect-function origmode)))))
1522 (funcall orig-fun var val)))
1523 ;; VAR is not the 'mode' pseudo-variable.
1524 (funcall orig-fun var val)))
1525
1526;;;###autoload
1527(defun so-long (&optional action)
1528 "Invoke `so-long-action' and run `so-long-hook'.
1529
1530This command is called automatically when long lines are detected, when
1531`global-so-long-mode' is enabled.
1532
1533The effects of the action can be undone by calling `so-long-revert'.
1534
1535If ACTION is provided, it is used instead of `so-long-action'. With a prefix
1536argument, select the action to use interactively."
1537 (interactive
1538 (list (and current-prefix-arg
1539 (intern
1540 (completing-read "Action (none): "
1541 (mapcar #'car so-long-action-alist)
1542 nil :require-match)))))
1543 (unless so-long--calling
1544 (let ((so-long--calling t))
1545 (so-long--ensure-enabled)
1546 ;; ACTION takes precedence if supplied.
1547 (when action
1548 (setq so-long-function nil
1549 so-long-revert-function nil))
1550 ;; Some of these settings need to be duplicated in `so-long-mode' to cover
1551 ;; the case when that mode is invoked directly.
1552 (setq so-long-detected-p t) ;; ensure menu is present.
1553 (unless so-long-function
1554 (setq so-long-function (so-long-function action)))
1555 (unless so-long-revert-function
1556 (setq so-long-revert-function (so-long-revert-function action)))
1557 ;; Remember original settings.
1558 (so-long-remember-all :reset)
1559 ;; Call the configured `so-long-function'.
1560 (when so-long-function
1561 (funcall so-long-function)
1562 ;; Set `so-long--active' last, as it isn't permanent-local.
1563 (setq so-long--active t))
1564 ;; Display mode line info, unless we are in `so-long-mode' (which provides
1565 ;; equivalent information in the mode line construct for the major mode).
1566 (unless (derived-mode-p 'so-long-mode)
1567 (setq so-long-mode-line-info (so-long-mode-line-info)))
1568 ;; Run `so-long-hook'.
1569 ;; By default we set `buffer-read-only', which can cause problems if hook
1570 ;; functions need to modify the buffer. We use `inhibit-read-only' to
1571 ;; side-step the issue (and likewise in `so-long-revert').
1572 (let ((inhibit-read-only t))
1573 (run-hooks 'so-long-hook)))))
1574
1575(defun so-long-revert ()
1576 "Revert the active `so-long-action' and run `so-long-revert-hook'.
1577
1578This undoes the effects of the `so-long' command (which is normally called
1579automatically by `global-so-long-mode').
1580
1581For the default action, reverting will restore the original major mode, and
1582restore the minor modes and settings which were overridden when `so-long' was
1583invoked."
1584 (interactive)
1585 (unless so-long--calling
1586 (let ((so-long--calling t))
1587 (when so-long-revert-function
1588 (funcall so-long-revert-function)
1589 (setq so-long--active nil))
1590 (let ((inhibit-read-only t))
1591 (run-hooks 'so-long-revert-hook)))))
1592
1593;; Duplicate the `so-long-revert' documentation for the menu item.
1594(put 'so-long-menu-item-revert 'function-documentation
1595 (documentation 'so-long-revert t))
1596
1597;;;###autoload
1598(defun so-long-enable ()
1599 "Enable the so-long library's functionality.
1600
1601Equivalent to calling (global-so-long-mode 1)"
1602 (interactive)
1603 (global-so-long-mode 1))
1604
1605(defun so-long-disable ()
1606 "Disable the so-long library's functionality.
1607
1608Equivalent to calling (global-so-long-mode 0)"
1609 (interactive)
1610 (global-so-long-mode 0))
1611
1612(make-obsolete 'so-long-enable 'global-so-long-mode "so-long 1.0")
1613(make-obsolete 'so-long-disable 'global-so-long-mode "so-long 1.0")
1614
1615;;;###autoload
1616(define-minor-mode global-so-long-mode
1617 "Toggle automated performance mitigations for files with long lines.
1618
1619Many Emacs modes struggle with buffers which contain excessively long lines,
1620and may consequently cause unacceptable performance issues.
1621
1622This is commonly on account of 'minified' code (i.e. code that has been
1623compacted into the smallest file size possible, which often entails removing
1624newlines should they not be strictly necessary).
1625
1626When such files are detected by `so-long-predicate', we invoke the selected
1627`so-long-action' to mitigate potential performance problems in the buffer.
1628
1629Use \\[so-long-commentary] for more information.
1630
1631Use \\[so-long-customize] to configure the behaviour."
1632 :global t
1633 :group 'so-long
1634 (if global-so-long-mode
1635 ;; Enable
1636 (progn
1637 (so-long--enable)
1638 (advice-add 'hack-local-variables :around
1639 #'so-long--hack-local-variables)
1640 (advice-add 'set-auto-mode :around
1641 #'so-long--set-auto-mode)
1642 (when (< emacs-major-version 26)
1643 (advice-add 'hack-one-local-variable :around
1644 #'so-long--hack-one-local-variable)))
1645 ;; Disable
1646 (so-long--disable)
1647 (advice-remove 'hack-local-variables #'so-long--hack-local-variables)
1648 (advice-remove 'set-auto-mode #'so-long--set-auto-mode)
1649 (when (< emacs-major-version 26)
1650 (advice-remove 'hack-one-local-variable
1651 #'so-long--hack-one-local-variable))))
1652
1653(put 'global-so-long-mode 'variable-documentation
1654 "Non-nil if the so-long library's automated functionality is enabled.
1655
1656Use \\[so-long-commentary] for more information.
1657
1658Setting this variable directly does not take effect;
1659either customize it (see the info node `Easy Customization')
1660or call the function `global-so-long-mode'.")
1661
1662(defun so-long--ensure-enabled ()
1663 "Enable essential functionality, if not already enabled."
1664 (unless so-long-enabled
1665 (so-long--enable)))
1666
1667(defun so-long--enable ()
1668 "Enable functionality other than `global-so-long-mode'."
1669 (add-to-list 'mode-line-misc-info '("" so-long-mode-line-info))
1670 (define-key-after (current-global-map) [menu-bar so-long]
1671 `(menu-item "So Long" nil
1672 ;; See also `so-long-mode-map'.
1673 :visible (or so-long--active
1674 so-long-detected-p
1675 (derived-mode-p 'so-long-mode))
1676 :filter ,(lambda (_cmd) (so-long-menu))))
1677 (setq so-long-enabled t))
1678
1679(defun so-long--disable ()
1680 "Disable functionality other than `global-so-long-mode'."
1681 (setq mode-line-misc-info
1682 (delete '("" so-long-mode-line-info) mode-line-misc-info))
1683 (define-key (current-global-map) [menu-bar so-long] nil)
1684 (setq so-long-enabled nil))
1685
1686(defun so-long-unload-function ()
1687 "Handler for `unload-feature'."
1688 (global-so-long-mode 0)
1689 nil)
1690
1691(provide 'so-long)
1692
1693;; Local Variables:
1694;; emacs-lisp-docstring-fill-column: 80
1695;; fill-column: 80
1696;; indent-tabs-mode: nil
1697;; End:
1698
1699;; So long, farewell, auf wiedersehen, goodbye
1700;; You have to go, this code is minified
1701;; Goodbye!
1702
1703;;; so-long.el ends here
diff --git a/test/lisp/so-long-tests/autoload-longlines-mode-tests.el b/test/lisp/so-long-tests/autoload-longlines-mode-tests.el
new file mode 100644
index 00000000000..5a57e049fb5
--- /dev/null
+++ b/test/lisp/so-long-tests/autoload-longlines-mode-tests.el
@@ -0,0 +1,51 @@
1;;; autoload-longlines-mode-tests.el --- Test suite for so-long.el -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2019 Free Software Foundation, Inc.
4
5;; Author: Phil Sainty <psainty@orcon.net.nz>
6;; Keywords: convenience
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
22
23;;; Code:
24
25(require 'ert)
26(load (expand-file-name "so-long-tests-helpers"
27 (file-name-directory (or load-file-name
28 default-directory))))
29
30(declare-function so-long-tests-remember "so-long-tests-helpers")
31(declare-function so-long-tests-assert-and-revert "so-long-tests-helpers")
32(defvar so-long-action)
33
34;; We're testing the initial state. We could alternatively use
35;; `unload-feature' to revert to that, but this option is simple.
36
37(autoload 'so-long "so-long")
38(autoload 'longlines-mode "longlines")
39
40(ert-deftest so-long-tests-autoload-longlines-mode ()
41 "File-local -*- so-long-action: longlines-mode; eval: (so-long) -*-"
42 (with-temp-buffer
43 (so-long-tests-remember)
44 (insert "-*- so-long-action: longlines-mode; eval: (so-long) -*-\n")
45 (put 'so-long-action 'safe-local-variable #'symbolp)
46 (push '(eval . (so-long)) safe-local-variable-values)
47 (hack-local-variables)
48 (should (eq so-long-action 'longlines-mode))
49 (so-long-tests-assert-and-revert 'longlines-mode)))
50
51;;; autoload-longlines-mode-tests.el ends here
diff --git a/test/lisp/so-long-tests/autoload-major-mode-tests.el b/test/lisp/so-long-tests/autoload-major-mode-tests.el
new file mode 100644
index 00000000000..d82cb59750c
--- /dev/null
+++ b/test/lisp/so-long-tests/autoload-major-mode-tests.el
@@ -0,0 +1,46 @@
1;;; autoload-major-mode-tests.el --- Test suite for so-long.el -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2019 Free Software Foundation, Inc.
4
5;; Author: Phil Sainty <psainty@orcon.net.nz>
6;; Keywords: convenience
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
22
23;;; Code:
24
25(require 'ert)
26(load (expand-file-name "so-long-tests-helpers"
27 (file-name-directory (or load-file-name
28 default-directory))))
29
30(declare-function so-long-tests-remember "so-long-tests-helpers")
31(declare-function so-long-tests-assert-and-revert "so-long-tests-helpers")
32
33;; We're testing the initial state. We could alternatively use
34;; `unload-feature' to revert to that, but this option is simple.
35
36(autoload 'so-long-mode "so-long")
37
38(ert-deftest so-long-tests-autoload-major-mode ()
39 "File-local -*- so-long -*-"
40 (with-temp-buffer
41 (so-long-tests-remember)
42 (insert "-*- so-long -*-\n")
43 (normal-mode)
44 (so-long-tests-assert-and-revert 'so-long-mode)))
45
46;;; autoload-major-mode-tests.el ends here
diff --git a/test/lisp/so-long-tests/autoload-minor-mode-tests.el b/test/lisp/so-long-tests/autoload-minor-mode-tests.el
new file mode 100644
index 00000000000..67f1903c09c
--- /dev/null
+++ b/test/lisp/so-long-tests/autoload-minor-mode-tests.el
@@ -0,0 +1,50 @@
1;;; autoload-minor-mode-tests.el --- Test suite for so-long.el -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2019 Free Software Foundation, Inc.
4
5;; Author: Phil Sainty <psainty@orcon.net.nz>
6;; Keywords: convenience
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
22
23;;; Code:
24
25(require 'ert)
26(load (expand-file-name "so-long-tests-helpers"
27 (file-name-directory (or load-file-name
28 default-directory))))
29
30(declare-function so-long-tests-remember "so-long-tests-helpers")
31(declare-function so-long-tests-assert-and-revert "so-long-tests-helpers")
32(defvar so-long-action)
33
34;; We're testing the initial state. We could alternatively use
35;; `unload-feature' to revert to that, but this option is simple.
36
37(autoload 'so-long "so-long")
38
39(ert-deftest so-long-tests-autoload-minor-mode ()
40 "File-local -*- so-long-action: so-long-minor-mode; eval: (so-long) -*-"
41 (with-temp-buffer
42 (so-long-tests-remember)
43 (insert "-*- so-long-action: so-long-minor-mode; eval: (so-long) -*-\n")
44 (put 'so-long-action 'safe-local-variable #'symbolp)
45 (push '(eval . (so-long)) safe-local-variable-values)
46 (hack-local-variables)
47 (should (eq so-long-action 'so-long-minor-mode))
48 (so-long-tests-assert-and-revert 'so-long-minor-mode)))
49
50;;; autoload-minor-mode-tests.el ends here
diff --git a/test/lisp/so-long-tests/so-long-tests-helpers.el b/test/lisp/so-long-tests/so-long-tests-helpers.el
new file mode 100644
index 00000000000..1a8ba451610
--- /dev/null
+++ b/test/lisp/so-long-tests/so-long-tests-helpers.el
@@ -0,0 +1,113 @@
1;;; so-long-tests-helpers.el --- Test suite for so-long.el -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2019 Free Software Foundation, Inc.
4
5;; Author: Phil Sainty <psainty@orcon.net.nz>
6;; Keywords: convenience
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
22
23;;; Code:
24
25(require 'ert)
26(require 'so-long)
27
28(defvar longlines-mode)
29(declare-function longlines-mode "longlines")
30
31(defvar so-long-tests-memory nil
32 "Original values of minor modes and variables.")
33
34(defun so-long-tests-assert-active (action)
35 "Assert that ACTION is active."
36 (cl-destructuring-bind (_key _label actionfunc revertfunc)
37 (assq action so-long-action-alist)
38 (should (eq so-long-function actionfunc))
39 (should (eq so-long-revert-function revertfunc))
40 (should (eq so-long-enabled t))
41 (should (eq so-long--active t))
42 ;; pcase fails here in Emacs 24.
43 (cl-case action
44 ('so-long-mode
45 (should (eq major-mode 'so-long-mode))
46 (so-long-tests-assert-overrides))
47 ('so-long-minor-mode
48 (should (eq so-long-minor-mode t))
49 (so-long-tests-assert-overrides))
50 ('longlines-mode
51 (should (eq longlines-mode t))))))
52
53(defun so-long-tests-assert-reverted (action)
54 "Assert that ACTION has been reverted."
55 (cl-destructuring-bind (_key _label actionfunc revertfunc)
56 (assq action so-long-action-alist)
57 (should (eq so-long-function actionfunc))
58 (should (eq so-long-revert-function revertfunc))
59 (should (eq so-long-enabled t))
60 (should (eq so-long--active nil))
61 ;; pcase fails here in Emacs 24.
62 (cl-case action
63 ('so-long-mode
64 (should-not (eq major-mode 'so-long-mode))
65 (so-long-tests-assert-overrides-reverted))
66 ('so-long-minor-mode
67 (should-not (eq so-long-minor-mode t))
68 (so-long-tests-assert-overrides-reverted))
69 ('longlines-mode
70 (should-not (eq longlines-mode t))))))
71
72(defun so-long-tests-assert-and-revert (action)
73 "Assert ACTION, revert it, and then assert the revert."
74 (so-long-tests-assert-active action)
75 (so-long-revert)
76 (so-long-tests-assert-reverted action))
77
78(defun so-long-tests-assert-overrides ()
79 "Assert that overridden modes and variables have their expected values."
80 (dolist (ovar so-long-variable-overrides)
81 (when (boundp (car ovar))
82 (should (equal (symbol-value (car ovar)) (cdr ovar)))))
83 (dolist (mode so-long-minor-modes)
84 (when (boundp mode)
85 (should (eq (symbol-value mode) nil)))))
86
87(defun so-long-tests-assert-overrides-reverted ()
88 "Assert that each remembered variable has its original value."
89 (dolist (ovar so-long-tests-memory)
90 (when (boundp (car ovar))
91 (should (equal (symbol-value (car ovar)) (cdr ovar))))))
92
93(defun so-long-tests-remember ()
94 "Remember the original states of modes and variables.
95
96Call this after setting up a buffer in the normal (not so-long)
97state for its major mode, so that after triggering a so-long
98action we can call `so-long-revert' and compare the reverted
99state against this remembered state."
100 (setq so-long-tests-memory nil)
101 (push (cons 'major-mode major-mode)
102 so-long-tests-memory)
103 (dolist (ovar so-long-variable-overrides)
104 (when (boundp (car ovar))
105 (push (cons (car ovar) (symbol-value (car ovar)))
106 so-long-tests-memory)))
107 (dolist (mode so-long-minor-modes)
108 (when (boundp mode)
109 (push (cons mode (symbol-value mode))
110 so-long-tests-memory))))
111
112(provide 'so-long-tests-helpers)
113;;; so-long-tests-helpers.el ends here
diff --git a/test/lisp/so-long-tests/so-long-tests.el b/test/lisp/so-long-tests/so-long-tests.el
new file mode 100644
index 00000000000..b1e0cb90d00
--- /dev/null
+++ b/test/lisp/so-long-tests/so-long-tests.el
@@ -0,0 +1,448 @@
1;;; so-long-tests.el --- Test suite for so-long.el -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2019 Free Software Foundation, Inc.
4
5;; Author: Phil Sainty <psainty@orcon.net.nz>
6;; Keywords: convenience
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
22
23;;; Commentary:
24
25;; Most of these tests use the shebang #!emacs to get `normal-mode' to
26;; select `emacs-lisp-mode', as using a file-local mode variable would
27;; usually trigger `so-long-file-local-mode-function'. In cases where
28;; we need to `hack-local-variables', we instead set `buffer-file-name'.
29;; (We could consistently use the latter, but the mixture of approaches
30;; means that we're testing more things.)
31
32;; Running the tests with "make lisp/so-long-tests" is like:
33;;
34;; HOME=/nonexistent EMACSLOADPATH= LC_ALL=C \
35;; EMACS_TEST_DIRECTORY=/home/phil/emacs/trunk/repository/test \
36;; "../src/emacs" --no-init-file --no-site-file --no-site-lisp \
37;; -L ":." -l ert -l lisp/so-long-tests.el --batch --eval \
38;; '(ert-run-tests-batch-and-exit (quote (not (tag :unstable))))'
39;;
40;; See also `ert-run-tests-batch-and-exit'.
41
42;;; Code:
43
44(require 'ert)
45(require 'so-long)
46(load (expand-file-name "so-long-tests-helpers"
47 (file-name-directory (or load-file-name
48 default-directory))))
49
50(declare-function so-long-tests-remember "so-long-tests-helpers")
51(declare-function so-long-tests-assert-active "so-long-tests-helpers")
52(declare-function so-long-tests-assert-reverted "so-long-tests-helpers")
53(declare-function so-long-tests-assert-and-revert "so-long-tests-helpers")
54
55;; Enable the automated behaviour for all tests.
56(global-so-long-mode 1)
57
58(ert-deftest so-long-tests-threshold-under ()
59 "Under line length threshold."
60 (with-temp-buffer
61 (insert "#!emacs\n")
62 (insert (make-string (1- so-long-threshold) ?x))
63 (normal-mode)
64 (should (eq major-mode 'emacs-lisp-mode))))
65
66(ert-deftest so-long-tests-threshold-at ()
67 "At line length threshold."
68 (with-temp-buffer
69 (insert "#!emacs\n")
70 (insert (make-string (1- so-long-threshold) ?x))
71 (normal-mode)
72 (should (eq major-mode 'emacs-lisp-mode))))
73
74(ert-deftest so-long-tests-threshold-over ()
75 "Over line length threshold."
76 (with-temp-buffer
77 (insert "#!emacs\n")
78 (normal-mode)
79 (so-long-tests-remember)
80 (insert (make-string (1+ so-long-threshold) ?x))
81 (normal-mode)
82 (so-long-tests-assert-and-revert 'so-long-mode)))
83
84(ert-deftest so-long-tests-skip-comments ()
85 "Skip leading shebang, whitespace, and comments."
86 ;; Long comment, no newline.
87 (with-temp-buffer
88 (insert "#!emacs\n")
89 (insert (make-string (1+ so-long-threshold) ?\;))
90 (normal-mode)
91 (should (eq major-mode 'emacs-lisp-mode)))
92 ;; Long comment, with newline.
93 (with-temp-buffer
94 (insert "#!emacs\n")
95 (insert (make-string (1+ so-long-threshold) ?\;))
96 (insert "\n")
97 (normal-mode)
98 (should (eq major-mode 'emacs-lisp-mode)))
99 ;; Long comment, with short text following.
100 (with-temp-buffer
101 (insert "#!emacs\n")
102 (insert (make-string (1+ so-long-threshold) ?\;))
103 (insert "\n")
104 (insert (make-string so-long-threshold ?x))
105 (normal-mode)
106 (should (eq major-mode 'emacs-lisp-mode)))
107 ;; Long comment, with long text following.
108 (with-temp-buffer
109 (insert "#!emacs\n")
110 (insert (make-string (1+ so-long-threshold) ?\;))
111 (insert "\n")
112 (insert (make-string (1+ so-long-threshold) ?x))
113 (normal-mode)
114 (should (eq major-mode 'so-long-mode))))
115
116(ert-deftest so-long-tests-max-lines ()
117 "Give up after `so-long-max-lines'."
118 (with-temp-buffer
119 (insert "#!emacs\n")
120 ;; Insert exactly `so-long-max-lines' non-comment lines, followed
121 ;; by a long line.
122 (dotimes (_ so-long-max-lines)
123 (insert "x\n"))
124 (insert (make-string (1+ so-long-threshold) ?x))
125 (normal-mode)
126 (should (eq major-mode 'emacs-lisp-mode))
127 ;; If `so-long-max-lines' is nil, don't give up the search.
128 (let ((so-long-max-lines nil))
129 (normal-mode)
130 (should (eq major-mode 'so-long-mode)))
131 ;; If `so-long-skip-leading-comments' is nil, all lines are
132 ;; counted, and so the shebang line counts, which makes the
133 ;; long line one line further away.
134 (let ((so-long-skip-leading-comments nil)
135 (so-long-max-lines (1+ so-long-max-lines)))
136 (normal-mode)
137 (should (eq major-mode 'emacs-lisp-mode))
138 (let ((so-long-max-lines (1+ so-long-max-lines)))
139 (normal-mode)
140 (should (eq major-mode 'so-long-mode))))))
141
142(ert-deftest so-long-tests-actions ()
143 "Test each of the standard actions."
144 (dolist (action (mapcar #'car so-long-action-alist))
145 (with-temp-buffer
146 (insert "#!emacs\n")
147 (normal-mode)
148 (so-long-tests-remember)
149 (insert (make-string (1+ so-long-threshold) ?x))
150 (let ((so-long-action action))
151 (normal-mode)
152 (so-long-tests-assert-and-revert action)))))
153
154(ert-deftest so-long-tests-command-so-long ()
155 "Test the `so-long' command."
156 ;; Includes argument of nil, meaning the default `so-long-mode' action.
157 (dolist (action (cons nil (mapcar #'car so-long-action-alist)))
158 (with-temp-buffer
159 (insert "#!emacs\n")
160 (normal-mode)
161 (so-long-tests-remember)
162 (insert (make-string (1+ so-long-threshold) ?x))
163 (so-long action)
164 (so-long-tests-assert-and-revert (or action 'so-long-mode)))))
165
166(ert-deftest so-long-tests-so-long-menu-item-replace-action ()
167 "Test using the `so-long-menu-item-replace-action' menu item."
168 (with-temp-buffer
169 ;; Due to (with-selected-window (so-long-menu-click-window) ...)
170 ;; (used by `so-long-menu-item-replace-action'), our temp buffer
171 ;; must be in the selected window.
172 (set-window-buffer nil (current-buffer))
173 (insert "#!emacs\n")
174 (normal-mode)
175 (so-long-tests-remember)
176 (insert (make-string (1+ so-long-threshold) ?x))
177 (let (action)
178 (dolist (item so-long-action-alist)
179 ;; n.b. Any existing action is first reverted.
180 (so-long-menu-item-replace-action item)
181 (setq action (car item))
182 (so-long-tests-assert-active action))
183 ;; After all actions have been used, revert to normal and assert
184 ;; that the most recent action to have been applied is the one
185 ;; we have just reverted.
186 (so-long-menu-item-revert)
187 (so-long-tests-assert-reverted action))))
188
189(ert-deftest so-long-tests-major-mode ()
190 "Test calling `so-long-mode' directly."
191 (with-temp-buffer
192 (insert "#!emacs\n")
193 (normal-mode)
194 (so-long-tests-remember)
195 (so-long-mode)
196 (so-long-tests-assert-and-revert 'so-long-mode)))
197
198(ert-deftest so-long-tests-minor-mode ()
199 "Test calling `so-long-minor-mode' directly."
200 (with-temp-buffer
201 (insert "#!emacs\n")
202 (normal-mode)
203 (so-long-tests-remember)
204 (so-long-minor-mode 1)
205 (so-long-tests-assert-active 'so-long-minor-mode)
206 (so-long-minor-mode 0)
207 (so-long-tests-assert-reverted 'so-long-minor-mode)))
208
209(ert-deftest so-long-tests-target-modes ()
210 "Targeted major modes."
211 ;; Test the `so-long-target-modes' user option.
212 (with-temp-buffer
213 (insert "#!emacs\n")
214 (insert (make-string (1+ so-long-threshold) ?x))
215 ;; Nil target modes.
216 (let ((so-long-target-modes nil))
217 (normal-mode)
218 (should (eq major-mode 'emacs-lisp-mode)))
219 ;; Non-matching target modes.
220 (let ((so-long-target-modes '(text-mode)))
221 (normal-mode)
222 (should (eq major-mode 'emacs-lisp-mode)))
223 ;; Matching mode (direct).
224 (let ((so-long-target-modes '(emacs-lisp-mode)))
225 (normal-mode)
226 (should (eq major-mode 'so-long-mode)))
227 ;; Matching mode (indirect).
228 (let ((so-long-target-modes '(prog-mode)))
229 (normal-mode)
230 (should (eq major-mode 'so-long-mode)))))
231
232(ert-deftest so-long-tests-predicate ()
233 "Custom predicate function."
234 ;; Test the `so-long-predicate' user option.
235 (with-temp-buffer
236 (insert "#!emacs\n")
237 ;; Always false.
238 (let ((so-long-predicate #'ignore))
239 (normal-mode)
240 (should (eq major-mode 'emacs-lisp-mode)))
241 ;; Always true.
242 (let ((so-long-predicate (lambda () t)))
243 (normal-mode)
244 (should (eq major-mode 'so-long-mode)))))
245
246(ert-deftest so-long-tests-file-local-action ()
247 "File-local action."
248 ;; Test `so-long-action' as a file-local variable.
249 ;; Only valid in Emacs26+. Refer to "Caveats" in the so-long.el Commentary.
250 (unless (version< emacs-version "26")
251 (with-temp-buffer
252 (insert "#!emacs\n")
253 (normal-mode)
254 (so-long-tests-remember))
255 ;; n.b. `run-mode-hooks' *only* runs `hack-local-variables' when there's a
256 ;; (buffer-file-name), so the #!emacs approach is insufficient here. It's
257 ;; valid for the file-locals to be on the second line after the shebang,
258 ;; but with the *.el filename we no longer need the shebang.
259 (with-temp-buffer
260 (setq buffer-file-name (expand-file-name "so-long-tests-data.el"))
261 (insert ";; -*- so-long-action:so-long-minor-mode; -*-\n")
262 (put 'so-long-action 'safe-local-variable #'symbolp)
263 (insert (make-string (1+ so-long-threshold) ?x))
264 (normal-mode)
265 (so-long-tests-assert-and-revert 'so-long-minor-mode))))
266
267(ert-deftest so-long-tests-file-local-action-eval-so-long ()
268 "File-local action and eval:(so-long)."
269 ;; As per previous test, but using file-local `eval' to call `so-long'.
270 ;; Only valid in Emacs26+. Refer to "Caveats" in the so-long.el Commentary.
271 ;; See also `so-long-tests-file-local-action' above.
272 (unless (version< emacs-version "26")
273 (with-temp-buffer
274 (insert "#!emacs\n")
275 (normal-mode)
276 (so-long-tests-remember))
277 (with-temp-buffer
278 (setq buffer-file-name (concat (make-temp-name "so-long-tests-") ".el"))
279 (insert ";; -*- so-long-action:so-long-minor-mode; eval:(so-long) -*-\n")
280 (put 'so-long-action 'safe-local-variable #'symbolp)
281 (push '(eval . (so-long)) safe-local-variable-values)
282 (normal-mode)
283 (so-long-tests-assert-and-revert 'so-long-minor-mode))))
284
285(defvar so-long-tests-local-mode 'unset
286 "Set by `so-long-tests-file-local-mode-function'.")
287
288(defun so-long-tests-file-local-mode-function (mode)
289 "A custom value for `so-long-file-local-mode-function'."
290 (setq so-long-tests-local-mode mode))
291
292;; Test `so-long-file-local-mode-function' when the file-local major
293;; mode is `emacs-lisp-mode'.
294
295(defmacro so-long-tests-deftest-file-local-emacs-lisp-mode
296 (sym docstring prop-line &optional local-vars)
297 "Generate tests for using `emacs-lisp-mode' as a file-local mode."
298 (setq prop-line (or prop-line "")
299 local-vars (or local-vars ""))
300 `(ert-deftest ,sym ()
301 ,docstring
302 (let ((orig so-long-file-local-mode-function))
303 ;; Do nothing at all when a file-local mode is used.
304 (setq-default so-long-file-local-mode-function 'so-long-inhibit)
305 (with-temp-buffer
306 (insert ,prop-line)
307 (insert (make-string (1+ so-long-threshold) ?x))
308 (insert ,local-vars)
309 (normal-mode)
310 ;; Remember the `emacs-lisp-mode' state. The other cases
311 ;; will validate the 'reverted' state against this.
312 (so-long-tests-remember)
313 (should (eq major-mode 'emacs-lisp-mode)))
314 ;; Downgrade the action from major mode to minor mode.
315 (setq-default so-long-file-local-mode-function 'so-long-mode-downgrade)
316 (with-temp-buffer
317 (insert ,prop-line)
318 (insert (make-string (1+ so-long-threshold) ?x))
319 (insert ,local-vars)
320 (normal-mode)
321 (so-long-tests-assert-and-revert 'so-long-minor-mode))
322 ;; Do not treat the file-local mode specially.
323 (setq-default so-long-file-local-mode-function nil)
324 (with-temp-buffer
325 (insert ,prop-line)
326 (insert (make-string (1+ so-long-threshold) ?x))
327 (insert ,local-vars)
328 (normal-mode)
329 (so-long-tests-assert-and-revert 'so-long-mode))
330 ;; Custom function
331 (setq-default so-long-file-local-mode-function
332 #'so-long-tests-file-local-mode-function)
333 (with-temp-buffer
334 (insert ,prop-line)
335 (insert (make-string (1+ so-long-threshold) ?x))
336 (insert ,local-vars)
337 (let (so-long-tests-local-mode)
338 (normal-mode)
339 (should (eq so-long-tests-local-mode 'emacs-lisp-mode))
340 (so-long-tests-assert-active 'so-long-mode)))
341 ;; end
342 (setq-default so-long-file-local-mode-function orig))))
343
344(so-long-tests-deftest-file-local-emacs-lisp-mode
345 so-long-tests-file-local-emacs-lisp-mode-short-form
346 "File-local mode (short form). -*- emacs-lisp -*-"
347 ";; -*- emacs-lisp -*-\n")
348
349(so-long-tests-deftest-file-local-emacs-lisp-mode
350 so-long-tests-file-local-emacs-lisp-mode-long-form
351 "File-local mode (long form). -*- emacs-lisp -*-"
352 ";; -*- mode: emacs-lisp -*-\n")
353
354(so-long-tests-deftest-file-local-emacs-lisp-mode
355 so-long-tests-file-local-emacs-lisp-mode-long-form2
356 "File-local mode (long form). -*- emacs-lisp -*-"
357 nil "\n;; Local Variables:\n;; mode: emacs-lisp\n;; End:\n")
358
359;; Test `so-long-file-local-mode-function' when the file-local major
360;; mode is `so-long-mode'. In this case we should always end up with
361;; the major mode being `so-long-mode'.
362
363(defmacro so-long-tests-deftest-file-local-so-long-mode
364 (sym docstring prop-line &optional local-vars)
365 "Generate tests for using `so-long-mode' as a file-local mode."
366 (setq prop-line (or prop-line "")
367 local-vars (or local-vars ""))
368 `(ert-deftest ,sym ()
369 ,docstring
370 (let ((orig so-long-file-local-mode-function))
371 ;; Do nothing at all when a file-local mode is used.
372 (setq-default so-long-file-local-mode-function 'so-long-inhibit)
373 (with-temp-buffer
374 ;; Remember the new-buffer state. The other cases will
375 ;; validate the 'reverted' state against this.
376 (so-long-tests-remember)
377 (insert ,prop-line)
378 (insert (make-string (1+ so-long-threshold) ?x))
379 (insert ,local-vars)
380 (normal-mode)
381 (so-long-tests-assert-and-revert 'so-long-mode))
382 ;; Downgrade from major mode to minor mode.
383 (setq-default so-long-file-local-mode-function 'so-long-mode-downgrade)
384 (with-temp-buffer
385 (insert ,prop-line)
386 (insert (make-string (1+ so-long-threshold) ?x))
387 (insert ,local-vars)
388 (normal-mode)
389 (so-long-tests-assert-and-revert 'so-long-mode))
390 ;; Do not treat the file-local mode specially.
391 (setq-default so-long-file-local-mode-function nil)
392 (with-temp-buffer
393 (insert ,prop-line)
394 (insert (make-string (1+ so-long-threshold) ?x))
395 (insert ,local-vars)
396 (normal-mode)
397 (so-long-tests-assert-and-revert 'so-long-mode))
398 ;; Custom function.
399 (setq-default so-long-file-local-mode-function
400 #'so-long-tests-file-local-mode-function)
401 (with-temp-buffer
402 (insert ,prop-line)
403 (insert (make-string (1+ so-long-threshold) ?x))
404 (insert ,local-vars)
405 (let (so-long-tests-local-mode)
406 (normal-mode)
407 (should (eq so-long--inhibited t))
408 (should (eq so-long-tests-local-mode 'so-long-mode))
409 (so-long-tests-assert-active 'so-long-mode)))
410 ;; end
411 (setq-default so-long-file-local-mode-function orig))))
412
413(so-long-tests-deftest-file-local-so-long-mode
414 so-long-tests-file-local-so-long-mode-short-form
415 "File-local mode (short form). -*- so-long -*-"
416 ";; -*- so-long -*-\n")
417
418(so-long-tests-deftest-file-local-so-long-mode
419 so-long-tests-file-local-so-long-mode-long-form
420 "File-local mode (long form). -*- mode: so-long -*-"
421 ";; -*- mode: so-long -*-\n")
422
423(so-long-tests-deftest-file-local-so-long-mode
424 so-long-tests-file-local-so-long-mode-long-form2
425 "File-local mode (long form). -*- mode: so-long -*-"
426 nil "\n;; Local Variables:\n;; mode: so-long\n;; End:\n")
427
428(ert-deftest so-long-tests-commentary ()
429 "Test the `so-long-commentary' command."
430 (so-long-commentary)
431 (should (string= (buffer-name) "*So Long: Commentary*"))
432 (should (eq major-mode 'outline-mode))
433 (should (eq view-mode t))
434 (should (looking-at "^\\* Introduction$"))
435 (goto-char (point-min))
436 (should (looking-at "^so-long\\.el$"))
437 (should (re-search-forward "^\\* Change Log:$")))
438
439(ert-deftest so-long-tests-customize ()
440 "Test the `so-long-customize' command."
441 (so-long-customize)
442 (should (string= (buffer-name) "*Customize Group: So Long*"))
443 (should (eq major-mode 'Custom-mode)))
444
445;; Page break to prevent the local vars strings above from
446;; being misinterpreted as actual local vars declarations.
447
448;;; so-long-tests.el ends here