diff options
| author | Phil Sainty | 2018-11-02 14:51:21 +1300 |
|---|---|---|
| committer | Phil Sainty | 2019-07-13 18:51:55 +1200 |
| commit | 4ac905f88f10439ca0795b217a046e3a62895fc4 (patch) | |
| tree | c89c4f7382342f9109794c842aa96bf7f03ba908 | |
| parent | 04cbdde94d256d9b3fbfcc67981374a55d339fcd (diff) | |
| download | emacs-4ac905f88f10439ca0795b217a046e3a62895fc4.tar.gz emacs-4ac905f88f10439ca0795b217a046e3a62895fc4.zip | |
Add so-long library
* lisp/so-long.el: New library.
* doc/emacs/trouble.texi (Long Lines): New node covering so-long.el.
* doc/emacs/emacs.texi (Top): Add menu entry for the Long Lines node.
* etc/NEWS: Include under "New Modes and Packages in Emacs 27.1"
| -rw-r--r-- | doc/emacs/emacs.texi | 1 | ||||
| -rw-r--r-- | doc/emacs/trouble.texi | 25 | ||||
| -rw-r--r-- | etc/NEWS | 8 | ||||
| -rw-r--r-- | lisp/so-long.el | 1703 |
4 files changed, 1737 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 | ||
| 1180 | Reporting Bugs | 1181 | Reporting 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. | |||
| 457 | emergency escape---but there are cases where it won't work, when a | 458 | emergency escape---but there are cases where it won't work, when a |
| 458 | system call hangs or when Emacs is stuck in a tight loop in C code. | 459 | system 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 | ||
| 466 | redisplay code and the complex range of possibilities it handles; | ||
| 467 | others of which are due to modes and features which do not scale well | ||
| 468 | in unusual circumstances), Emacs can perform poorly when extremely | ||
| 469 | long lines are present (where ``extremely long'' usually means at | ||
| 470 | least many thousands of characters). | ||
| 471 | |||
| 472 | A particular problem is that Emacs may ``hang'' for a long time at | ||
| 473 | the point of visiting a file with extremely long lines, and this case | ||
| 474 | can be mitigated by enabling the @file{so-long} library, which detects | ||
| 475 | when a visited file contains abnormally long lines, and takes steps to | ||
| 476 | disable features which are liable to cause slowness in that situation. | ||
| 477 | |||
| 478 | This library can also significantly improve performance when moving | ||
| 479 | and editing in such a buffer -- performance is still likely to degrade | ||
| 480 | as you get deeper into the long lines, but the improvements can | ||
| 481 | nevertheless be substantial. | ||
| 482 | |||
| 483 | Use @kbd{M-x so-long-commentary} to view the documentation for this | ||
| 484 | library and learn how to enable and configure it. | ||
| 460 | @node Bugs | 485 | @node Bugs |
| 461 | @section Reporting Bugs | 486 | @section Reporting Bugs |
| 462 | 487 | ||
| @@ -1705,6 +1705,14 @@ expansion to backtrace buffers produced by the Lisp debugger, Edebug | |||
| 1705 | and ERT. See the node "(elisp) Backtraces" in the Elisp manual for | 1705 | and ERT. See the node "(elisp) Backtraces" in the Elisp manual for |
| 1706 | documentation of the new mode and its commands. | 1706 | documentation of the new mode and its commands. |
| 1707 | 1707 | ||
| 1708 | +++ | ||
| 1709 | ** so-long.el helps to mitigate performance problems with long lines. | ||
| 1710 | When 'global-so-long-mode' has been enabled, visiting a file with very | ||
| 1711 | long lines will (subject to configuration) cause the user's preferred | ||
| 1712 | 'so-long-action' to be automatically invoked (by default, the buffer's | ||
| 1713 | major mode is replaced by 'so-long-mode'). In extreme cases this can | ||
| 1714 | prevent delays of several minutes, and make Emacs responsive almost | ||
| 1715 | immediately. 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 | |||
| 407 | Has 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 | |||
| 441 | See `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 | |||
| 449 | If nil then all lines will be tested, until either a long line is detected, | ||
| 450 | or the end of the buffer is reached. | ||
| 451 | |||
| 452 | If `so-long-skip-leading-comments' is nil then comments and blank lines will | ||
| 453 | be counted. | ||
| 454 | |||
| 455 | See `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 | |||
| 464 | If the file begins with a shebang (#!), this option also causes that line to be | ||
| 465 | ignored even if it doesn't match the buffer's comment syntax, to ensure that | ||
| 466 | comments following the shebang will be ignored. | ||
| 467 | |||
| 468 | See `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 | |||
| 477 | Our primary use-case is minified programming code, so `prog-mode' covers | ||
| 478 | most cases, but there are some exceptions to this. | ||
| 479 | |||
| 480 | If t, then all modes are targeted. Note that this is only useful with a | ||
| 481 | custom `so-long-predicate', as many file types (archives and binary files, | ||
| 482 | for example) can safely contain long lines, and invoking `so-long' on such | ||
| 483 | files 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 | |||
| 493 | Only called if the major mode is a member of `so-long-target-modes'. | ||
| 494 | |||
| 495 | The specified function will be called with no arguments. If it returns non-nil | ||
| 496 | then `so-long' will be invoked. | ||
| 497 | |||
| 498 | Defaults 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 | |||
| 540 | Each element is a list comprising (KEY LABEL ACTION REVERT) | ||
| 541 | |||
| 542 | KEY is a symbol which is a valid value for `so-long-action', and LABEL is a | ||
| 543 | string which describes and represents the key in that option's customize | ||
| 544 | interface, and in the \"So Long\" menu. ACTION and REVERT are functions: | ||
| 545 | |||
| 546 | ACTION will be the `so-long-function' value when `so-long' is called, and | ||
| 547 | REVERT will be the `so-long-revert-function' value, if `so-long-revert' is | ||
| 548 | subsequently 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 | |||
| 563 | The value is a key to one of the options defined by `so-long-action-alist'. | ||
| 564 | |||
| 565 | The default action is to replace the original major mode with `so-long-mode'. | ||
| 566 | Alternatively, the `so-long-minor-mode' action retains the original major mode | ||
| 567 | while still disabling minor modes and overriding variables. These are the only | ||
| 568 | standard values for which `so-long-minor-modes' and `so-long-variable-overrides' | ||
| 569 | will be automatically processed; but custom actions can also do these things. | ||
| 570 | |||
| 571 | The value `longlines-mode' causes that minor mode to be enabled. See | ||
| 572 | longlines.el for more details. | ||
| 573 | |||
| 574 | Each action likewise determines the behaviour of `so-long-revert'. | ||
| 575 | |||
| 576 | If the value is nil, or not defined in `so-long-action-alist', then no action | ||
| 577 | will 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 | |||
| 585 | This should be set in conjunction with `so-long-revert-function'. This usually | ||
| 586 | happens automatically, based on the value of `so-long-action'. | ||
| 587 | |||
| 588 | The 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 | |||
| 595 | This should be set in conjunction with `so-long-function'. This usually | ||
| 596 | happens automatically, based on the value of `so-long-action'. | ||
| 597 | |||
| 598 | The 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 | |||
| 605 | If 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 | |||
| 615 | If 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 | |||
| 625 | The specified function will be called with a single argument, being the | ||
| 626 | file-local mode which was established. | ||
| 627 | |||
| 628 | This happens before `so-long' is called, and so this function can modify the | ||
| 629 | subsequent action. | ||
| 630 | |||
| 631 | The value `so-long-mode-downgrade' means `so-long-minor-mode' will be used in | ||
| 632 | place of `so-long-mode' -- therefore respecting the file-local mode value, yet | ||
| 633 | still overriding minor modes and variables (as if `so-long-action' had been set | ||
| 634 | to `so-long-minor-mode'). | ||
| 635 | |||
| 636 | The value `so-long-inhibit' means that so-long will not take any action at all | ||
| 637 | for this file. | ||
| 638 | |||
| 639 | If nil, then do not treat files with file-local modes any differently to other | ||
| 640 | files. | ||
| 641 | |||
| 642 | Note that this function is called if a file-local mode is set even if `so-long' | ||
| 643 | will not be called, and also if the file-local mode is `so-long-mode'. Custom | ||
| 644 | functions may need to test for these cases -- see `so-long-mode-downgrade' for | ||
| 645 | an 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. | ||
| 658 | Uses the `derived-mode-parent' property of the symbol to trace backwards. | ||
| 659 | If 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 | |||
| 667 | The function is called with one argument, MODE, being the file-local mode which | ||
| 668 | was 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 | |||
| 709 | The ones which were originally enabled in the buffer are disabled by calling | ||
| 710 | them with the numeric argument 0. Unknown modes, and modes which were were not | ||
| 711 | enabled, are ignored. | ||
| 712 | |||
| 713 | This happens after any globalized minor modes have acted, so that buffer-local | ||
| 714 | modes controlled by globalized modes can also be targeted. | ||
| 715 | |||
| 716 | By default this happens if `so-long-action' is set to either `so-long-mode' | ||
| 717 | or `so-long-minor-mode'. If `so-long-revert' is subsequently invoked, then the | ||
| 718 | disabled 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 | |||
| 722 | Please submit bug reports to recommend additional modes for this list, whether | ||
| 723 | they 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 | |||
| 738 | The 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 | |||
| 741 | If `so-long-revert' is subsequently invoked, then the variables are restored | ||
| 742 | to 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 | |||
| 758 | See 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 | |||
| 766 | See 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 | |||
| 774 | If 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 | |||
| 800 | Any 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 | ||
| 802 | original states for `so-long-variable-overrides' and `so-long-minor-modes', | ||
| 803 | so these values are available to custom actions by default. | ||
| 804 | |||
| 805 | See 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 | |||
| 811 | If you need to differentiate between a stored value of nil and no stored value | ||
| 812 | at all, make EXISTS non-nil. This then returns the result of `assq' directly: | ||
| 813 | nil 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 | |||
| 821 | We additionally store a boolean value which indicates whether that value was | ||
| 822 | buffer-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 | |||
| 834 | Stores the existing value for each entry in `so-long-variable-overrides'. | ||
| 835 | Stores the name of each enabled mode from the list `so-long-minor-modes'. | ||
| 836 | |||
| 837 | If 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 | |||
| 890 | Commands in the mode-line menu may be triggered by mouse when some other window | ||
| 891 | is 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 | |||
| 908 | REPLACEMENT 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 | |||
| 963 | Displayed as part of `mode-line-misc-info'. | ||
| 964 | |||
| 965 | `so-long-mode-line-label' defines the text to be displayed (if any). | ||
| 966 | |||
| 967 | Face `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 | |||
| 970 | Not displayed when `so-long-mode' is enabled, as the major mode construct | ||
| 971 | serves 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 | |||
| 1006 | Following any initial comments and blank lines, the next N lines of the buffer | ||
| 1007 | will be tested for excessive length (where \"excessive\" means above | ||
| 1008 | `so-long-threshold', and N is `so-long-max-lines'). | ||
| 1009 | |||
| 1010 | Returns non-nil if any such excessive-length line is detected. | ||
| 1011 | |||
| 1012 | If `so-long-skip-leading-comments' is nil then the N lines will be counted | ||
| 1013 | starting from the first line of the buffer. In this instance you will likely | ||
| 1014 | want to increase `so-long-max-lines' to allow for possible comments. | ||
| 1015 | |||
| 1016 | This 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 | |||
| 1104 | Any active minor modes listed in `so-long-minor-modes' are disabled for the | ||
| 1105 | current buffer, and buffer-local values are assigned to variables in accordance | ||
| 1106 | with `so-long-variable-overrides'. | ||
| 1107 | |||
| 1108 | This 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 | |||
| 1164 | The normal reason for this mode being active is that `global-so-long-mode' is | ||
| 1165 | enabled, and `so-long-predicate' has detected that the file contains long lines. | ||
| 1166 | |||
| 1167 | Many Emacs modes struggle with buffers which contain excessively long lines, | ||
| 1168 | and may consequently cause unacceptable performance issues. | ||
| 1169 | |||
| 1170 | This is commonly on account of 'minified' code (i.e. code has been compacted | ||
| 1171 | into the smallest file size possible, which often entails removing newlines | ||
| 1172 | should they not be strictly necessary). These kinds of files are typically | ||
| 1173 | not intended to be edited, so not providing the usual editing mode in these | ||
| 1174 | cases will rarely be an issue. | ||
| 1175 | |||
| 1176 | This major mode disables any active minor modes listed in `so-long-minor-modes' | ||
| 1177 | for the current buffer, and buffer-local values are assigned to variables in | ||
| 1178 | accordance with `so-long-variable-overrides'. | ||
| 1179 | |||
| 1180 | To restore the original major mode (along with the minor modes and variable | ||
| 1181 | values), despite potential performance issues, type \\[so-long-revert]. | ||
| 1182 | |||
| 1183 | Use \\[so-long-commentary] for more information. | ||
| 1184 | |||
| 1185 | Use \\[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 | |||
| 1230 | This 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 | |||
| 1244 | Calls `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 | |||
| 1264 | The 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 | |||
| 1279 | The 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 | |||
| 1307 | Re-process local variables, and restore overridden variables and minor modes. | ||
| 1308 | |||
| 1309 | This 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 | |||
| 1338 | A buffer-local 'downgrade' from `so-long-mode' to `so-long-minor-mode'. | ||
| 1339 | |||
| 1340 | When `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 | ||
| 1342 | mode, but still doing everything else that `so-long-mode' would have done. | ||
| 1343 | `so-long-revert-function' is likewise updated. | ||
| 1344 | |||
| 1345 | If `so-long-function' has any value other than `so-long-mode', we do nothing, | ||
| 1346 | as if `so-long-file-local-mode-function' was nil. | ||
| 1347 | |||
| 1348 | We also do nothing if MODE (the file-local mode) has the value `so-long-mode', | ||
| 1349 | because 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 | |||
| 1362 | This 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 | ||
| 1370 | when set in the header comment. This runs outside of `hack-local-variables' | ||
| 1371 | and cannot be conveniently intercepted, so we are forced to replicate it here. | ||
| 1372 | |||
| 1373 | This special-case code will ultimately be removed from Emacs, as it exists to | ||
| 1374 | deal with a deprecated feature; but until then we need to replicate it in order | ||
| 1375 | to inhibit our own behaviour in the presence of a header comment 'mode' | ||
| 1376 | declaration. | ||
| 1377 | |||
| 1378 | If a file-local mode is detected in the header comment, then we call the | ||
| 1379 | function 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 | |||
| 1440 | If a file-local mode is detected, then we call the function defined by | ||
| 1441 | `so-long-file-local-mode-function'. | ||
| 1442 | |||
| 1443 | This advice acts after the HANDLE-MODE:t call to `hack-local-variables'. | ||
| 1444 | \(MODE-ONLY in Emacs versions < 26). | ||
| 1445 | |||
| 1446 | File-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 | |||
| 1462 | This advice acts after `set-auto-mode' has set the buffer's major mode. | ||
| 1463 | |||
| 1464 | We 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 | ||
| 1466 | major 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 | |||
| 1490 | This advice is needed and enabled only for Emacs versions < 26.1. | ||
| 1491 | |||
| 1492 | If the local 'mode' pseudo-variable is used, `set-auto-mode-0' will call it | ||
| 1493 | firstly, and subsequently `hack-one-local-variable' may call it again. | ||
| 1494 | |||
| 1495 | Usually `hack-one-local-variable' tries to avoid processing that second call, | ||
| 1496 | by testing the value against `major-mode'; but as we may have changed the | ||
| 1497 | major mode to `so-long-mode' by this point, that protection is insufficient | ||
| 1498 | and so we need to perform our own test. | ||
| 1499 | |||
| 1500 | We likewise need to support an equivalent of the `no-mode' behaviour in 26.1+ | ||
| 1501 | to ensure that `so-long-mode-revert' will not restore a file-local mode again | ||
| 1502 | after it has already reverted to the original mode. | ||
| 1503 | |||
| 1504 | The changes to `normal-mode' in Emacs 26.1 modified the execution order, and | ||
| 1505 | makes this advice unnecessary. The relevant NEWS entry is: | ||
| 1506 | |||
| 1507 | ** File local and directory local variables are now initialized each | ||
| 1508 | time the major mode is set, not just when the file is first visited. | ||
| 1509 | These 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 | |||
| 1530 | This command is called automatically when long lines are detected, when | ||
| 1531 | `global-so-long-mode' is enabled. | ||
| 1532 | |||
| 1533 | The effects of the action can be undone by calling `so-long-revert'. | ||
| 1534 | |||
| 1535 | If ACTION is provided, it is used instead of `so-long-action'. With a prefix | ||
| 1536 | argument, 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 | |||
| 1578 | This undoes the effects of the `so-long' command (which is normally called | ||
| 1579 | automatically by `global-so-long-mode'). | ||
| 1580 | |||
| 1581 | For the default action, reverting will restore the original major mode, and | ||
| 1582 | restore the minor modes and settings which were overridden when `so-long' was | ||
| 1583 | invoked." | ||
| 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 | |||
| 1601 | Equivalent 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 | |||
| 1608 | Equivalent 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 | |||
| 1619 | Many Emacs modes struggle with buffers which contain excessively long lines, | ||
| 1620 | and may consequently cause unacceptable performance issues. | ||
| 1621 | |||
| 1622 | This is commonly on account of 'minified' code (i.e. code that has been | ||
| 1623 | compacted into the smallest file size possible, which often entails removing | ||
| 1624 | newlines should they not be strictly necessary). | ||
| 1625 | |||
| 1626 | When 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 | |||
| 1629 | Use \\[so-long-commentary] for more information. | ||
| 1630 | |||
| 1631 | Use \\[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 | |||
| 1656 | Use \\[so-long-commentary] for more information. | ||
| 1657 | |||
| 1658 | Setting this variable directly does not take effect; | ||
| 1659 | either customize it (see the info node `Easy Customization') | ||
| 1660 | or 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 | ||