From 90290745d74b18f8a824ea90fe6c6bf5110d716d Mon Sep 17 00:00:00 2001 From: Phil Sainty Date: Tue, 22 Oct 2019 01:01:28 +1300 Subject: [PATCH 1/7] ; Documentation and spelling * lisp/so-long.el: Documentation fixes. For the purposes of consistency, this reverts some of the changes made in commit 41ba8231ef072571e1a6feabc15d113e5cf57556, including one which had introduced inconsistent spelling. ispell configuration and LocalWords have been added such that `ispell-buffer' should find no misspellings for this library. * test/lisp/so-long-tests/spelling-tests.el (so-long-spelling): New test to check the spelling using `ispell-buffer'. --- lisp/so-long.el | 54 ++++++++++++------ test/lisp/so-long-tests/spelling-tests.el | 69 +++++++++++++++++++++++ 2 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 test/lisp/so-long-tests/spelling-tests.el diff --git a/lisp/so-long.el b/lisp/so-long.el index b5eb1242156..8d9b0dc0627 100644 --- a/lisp/so-long.el +++ b/lisp/so-long.el @@ -325,7 +325,7 @@ ;; * Caveats ;; --------- -;; The variables affecting the automated behavior of this library (such as +;; The variables affecting the automated behaviour of this library (such as ;; `so-long-action') can be used as file- or dir-local values in Emacs 26+, but ;; not in previous versions of Emacs. This is on account of improvements made ;; to `normal-mode' in 26.1, which altered the execution order with respect to @@ -413,7 +413,7 @@ Has no effect if `global-so-long-mode' is not enabled.") "Non-nil while `set-auto-mode' is executing.") (defvar so-long--hack-local-variables-no-mode nil ; internal use - "Non-nil to prevent `hack-local-variables' applying a 'mode' variable.") + "Non-nil to prevent `hack-local-variables' applying a `mode' variable.") (defvar-local so-long--inhibited nil ; internal use "When non-nil, prevents the `set-auto-mode' advice from calling `so-long'.") @@ -654,7 +654,7 @@ an example." ;; `provided-mode-derived-p' was added in 26.1 (unless (fboundp 'provided-mode-derived-p) (defun provided-mode-derived-p (mode &rest modes) - "Return non-nil if MODE is derived from one of MODES. + "Non-nil if MODE is derived from one of MODES. Uses the `derived-mode-parent' property of the symbol to trace backwards. If you just want to check `major-mode', use `derived-mode-p'." (while (and (not (memq mode modes)) @@ -707,7 +707,7 @@ was established." "List of buffer-local minor modes to explicitly disable. The ones which were originally enabled in the buffer are disabled by calling -them with the numeric argument 0. Unknown modes, and modes which were were not +them with the numeric argument 0. Unknown modes, and modes which were not enabled, are ignored. This happens after any globalized minor modes have acted, so that buffer-local @@ -742,8 +742,8 @@ If `so-long-revert' is subsequently invoked, then the variables are restored to their original states. The combination of `line-move-visual' (enabled) and `truncate-lines' (disabled) -is important for avoiding performance hits when moving vertically between -excessively long lines, as otherwise the full length of the line may need to be +is important for maximising responsiveness when moving vertically within an +extremely long line, as otherwise the full length of the line may need to be scanned to find the next position." :type '(alist :key-type (variable :tag "Variable") :value-type (sexp :tag "Value")) @@ -1174,11 +1174,11 @@ enabled, and `so-long-predicate' has detected that the file contains long lines. Many Emacs modes struggle with buffers which contain excessively long lines, and may consequently cause unacceptable performance issues. -This is commonly on account of \"minified\" code (i.e., code compacted -into the smallest file size possible, which often entails removing newlines -should they not be strictly necessary). These kinds of files are typically -not intended to be edited, so not providing the usual editing mode in these -cases will rarely be an issue. +This is commonly on account of \"minified\" code (i.e. code that has been +compacted into the smallest file size possible, which often entails removing +newlines should they not be strictly necessary). These kinds of files are +typically not intended to be edited, so not providing the usual editing mode +in these cases will rarely be an issue. This major mode disables any active minor modes listed in `so-long-minor-modes' for the current buffer, and buffer-local values are assigned to variables in @@ -1189,7 +1189,7 @@ values), despite potential performance issues, type \\[so-long-revert]. Use \\[so-long-commentary] for more information. -Use \\[so-long-customize] to configure the behavior." +Use \\[so-long-customize] to configure the behaviour." ;; Housekeeping. `so-long-mode' might be invoked directly rather than via ;; `so-long', so replicate the necessary behaviours. We could use this same ;; test in `so-long-after-change-major-mode' to run `so-long-hook', but that's @@ -1344,7 +1344,7 @@ This is the `so-long-revert-function' for `so-long-mode'." A buffer-local \"downgrade\" from `so-long-mode' to `so-long-minor-mode'. -When `so-long-function' is set to `so-long-mode', then we change it to to +When `so-long-function' is set to `so-long-mode', then we change it to `turn-on-so-long-minor-mode' instead -- retaining the file-local major mode, but still doing everything else that `so-long-mode' would have done. `so-long-revert-function' is likewise updated. @@ -1379,7 +1379,7 @@ and cannot be conveniently intercepted, so we are forced to replicate it here. This special-case code will ultimately be removed from Emacs, as it exists to deal with a deprecated feature; but until then we need to replicate it in order -to inhibit our own behavior in the presence of a header comment `mode' +to inhibit our own behaviour in the presence of a header comment `mode' declaration. If a file-local mode is detected in the header comment, then we call the @@ -1626,9 +1626,9 @@ Equivalent to calling (global-so-long-mode 0)" Many Emacs modes struggle with buffers which contain excessively long lines, and may consequently cause unacceptable performance issues. -This is commonly on account of \"minified\" code (i.e., code compacted into the -smallest file size possible, which often entails removing newlines should they -not be strictly necessary). +This is commonly on account of \"minified\" code (i.e. code that has been +compacted into the smallest file size possible, which often entails removing +newlines should they not be strictly necessary). When such files are detected by `so-long-predicate', we invoke the selected `so-long-action' to mitigate potential performance problems in the buffer. @@ -1695,14 +1695,34 @@ or call the function `global-so-long-mode'.") (global-so-long-mode 0) nil) + (provide 'so-long) ;; Local Variables: ;; emacs-lisp-docstring-fill-column: 80 ;; fill-column: 80 ;; indent-tabs-mode: nil +;; ispell-check-comments: exclusive +;; ispell-local-dictionary: "british" ;; End: +;; This library is extensively documented in British English, contrary to the +;; preference for American English in Emacs. I hope the benefits of the library +;; will outweigh any discontent you may experience regarding the spelling (or +;; that you find the spelling to be an agreeable bonus). Certain standard Emacs +;; terminology, and text quoted from elsewhere in Emacs, retains its original +;; spelling. The following LocalWords should result in no misspellings from +;; M-x ispell-buffer (using aspell). + +; LocalWords: LocalWords british ispell aspell hunspell emacs elisp el init dir +; LocalWords: customize customized customizing Customization globalized amongst +; LocalWords: initialized profiler boolean minified pre redisplay config keymap +; LocalWords: noerror selectable mapc sgml nxml hl flydiff defs arg Phil Sainty +; LocalWords: defadvice nadvice whitespace ie bos eos eobp origmode un Un cXXXr +; LocalWords: docstring auf wiedersehen longlines alist autoload Refactored Inc +; LocalWords: MERCHANTABILITY RET REGEXP VAR ELPA WS mitigations EmacsWiki eval +; LocalWords: setq rx filename filenames + ;; So long, farewell, auf wiedersehen, goodbye ;; You have to go, this code is minified ;; Goodbye! diff --git a/test/lisp/so-long-tests/spelling-tests.el b/test/lisp/so-long-tests/spelling-tests.el new file mode 100644 index 00000000000..d5bae1ef0ce --- /dev/null +++ b/test/lisp/so-long-tests/spelling-tests.el @@ -0,0 +1,69 @@ +;;; spelling-tests.el --- Test suite for so-long.el -*- lexical-binding: t; -*- + +;; Copyright (C) 2019 Free Software Foundation, Inc. + +;; Author: Phil Sainty +;; Keywords: convenience + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Code: + +(require 'ert) +(require 'ispell) +(require 'cl-lib) + +;; This test is tagged as :unstable on the basis that there may be +;; inconsistencies between spell-checking facilities on different +;; systems, which may cause the test to be unreliable in practice. +;; As such the Emacs test Makefile will skip it by default, but you +;; can run it manually with: +;; +;; make lisp/so-long-tests/spelling-tests SELECTOR=t + +;; Only define the test if spell-checking is possible. +(when (and ispell-program-name + (executable-find ispell-program-name) + (condition-case () + (progn (ispell-check-version) t) + (error nil)) + (member "british" (ispell-valid-dictionary-list))) + (ert-deftest so-long-spelling () + "Check the spelling in the source code." + :tags '(:unstable) ;; It works for me, but I'm not sure about others. + ;; There could be different "british" dictionaries yielding different + ;; results, for instance. + ;; + ;; The Emacs test Makefile's use of HOME=/nonexistent triggers an error + ;; when starting the inferior ispell process, so we set HOME to a valid + ;; (but empty) temporary directory for this test. + (let* ((tmpdir (make-temp-file "so-long." :dir ".ispell")) + (process-environment (cons (format "HOME=%s" tmpdir) + process-environment)) + (find-spelling-mistake + (unwind-protect + (cl-letf (((symbol-function 'ispell-command-loop) + (lambda (_miss _guess word _start _end) + (message "Unrecognised word: %s." word) + (throw 'mistake t)))) + (catch 'mistake + (find-library "so-long") + (ispell-buffer) + nil)) + (delete-directory tmpdir)))) + (should (not find-spelling-mistake))))) + +;;; spelling-tests.el ends here From e9dca2b5aa43116980321259682ad0c68fd0f937 Mon Sep 17 00:00:00 2001 From: Phil Sainty Date: Mon, 21 Oct 2019 23:52:29 +1300 Subject: [PATCH 2/7] Defer triggering `so-long' until the buffer is displayed * lisp/so-long.el (so-long-invisible-buffer-function): New user option. (so-long--set-auto-mode): Use so-long-invisible-buffer-function. (so-long-deferred): New function/value for so-long-invisible-buffer-function. (so-long, so-long--disable): Support for so-long-deferred. * test/lisp/so-long-tests/autoload-longlines-mode-tests.el * test/lisp/so-long-tests/autoload-major-mode-tests.el * test/lisp/so-long-tests/autoload-minor-mode-tests.el * test/lisp/so-long-tests/so-long-tests.el: Support for so-long-deferred. Pre-existing tests have been updated to ensure the buffer is already displayed in cases where a call to `normal-mode' is the (potential) trigger for `so-long'. --- lisp/so-long.el | 77 +++++++++++- .../autoload-longlines-mode-tests.el | 1 + .../autoload-major-mode-tests.el | 1 + .../autoload-minor-mode-tests.el | 1 + test/lisp/so-long-tests/so-long-tests.el | 116 +++++++++++++++++- 5 files changed, 190 insertions(+), 6 deletions(-) diff --git a/lisp/so-long.el b/lisp/so-long.el index 8d9b0dc0627..f7dfc8a79ca 100644 --- a/lisp/so-long.el +++ b/lisp/so-long.el @@ -162,6 +162,23 @@ ;; this option can also be configured to inhibit so-long entirely in this ;; scenario, or to not treat a file-local mode as a special case at all. +;; * Buffers which are not displayed in a window +;; --------------------------------------------- +;; When a file with long lines is visited and the buffer is not displayed right +;; away, it may be that it is not intended to be displayed at all, and that it +;; has instead been visited for behind-the-scenes processing by some library. +;; Invisible buffers are less likely to cause performance issues, and it also +;; might be surprising to the other library if such a buffer were manipulated by +;; `so-long' (which might in turn lead to confusing errors for the user); so in +;; these situations the `so-long-invisible-buffer-function' value is called +;; instead. By default this arranges for `so-long' to be invoked on the buffer +;; if and when it is displayed, but not otherwise. +;; +;; This 'deferred call' is actually the most common scenario -- even when a +;; visited file is displayed "right away", it is normal for the buffer to be +;; invisible when `global-so-long-mode' processes it, and the gap between +;; "arranging to call" and "calling" `so-long' is simply extremely brief. + ;; * Inhibiting and disabling minor modes ;; -------------------------------------- ;; Certain minor modes cause significant performance issues in the presence of @@ -345,6 +362,7 @@ ;; - New user option `so-long-variable-overrides'. ;; - New user option `so-long-skip-leading-comments'. ;; - New user option `so-long-file-local-mode-function'. +;; - New user option `so-long-invisible-buffer-function'. ;; - New user option `so-long-predicate'. ;; - New variable and function `so-long-function'. ;; - New variable and function `so-long-revert-function'. @@ -487,6 +505,37 @@ files would prevent Emacs from handling them correctly." :package-version '(so-long . "1.0") :group 'so-long) +(defcustom so-long-invisible-buffer-function #'so-long-deferred + "Function called in place of `so-long' when the buffer is not displayed. + +This affects the behaviour of `global-so-long-mode'. + +We treat invisible buffers differently from displayed buffers because, in +cases where a library is using a buffer for behind-the-scenes processing, +it might be surprising if that buffer were unexpectedly manipulated by +`so-long' (which might in turn lead to confusing errors for the user). +Invisible buffers are less likely to cause performance issues related to +long lines, so this differentiation is generally satisfactory. + +The default value `so-long-deferred' prevents `global-so-long-mode' from +triggering `so-long' for any given buffer until such time as the buffer is +displayed in a window. + +\(Note that buffers are normally invisible at this point -- when `find-file' +is used, the buffer is not displayed in a window until a short time after +`global-so-long-mode' has seen it.) + +The value nil or `so-long' means that `so-long' will be called directly; in +which case it may be problematic for `so-long-variable-overrides' to enable +`buffer-read-only', or for `so-long-action' to be set to `so-long-mode'. +This is because the buffer may not be intended to be displayed at all, and +the mentioned options might interfere with some intended processing." + :type '(radio (const so-long-deferred) + (const :tag "nil: Call so-long as normal" nil) + (function :tag "Custom function")) + :package-version '(so-long . "1.0") + :group 'so-long) + (defcustom so-long-predicate 'so-long-detected-long-line-p "Function, called after `set-auto-mode' to decide whether action is needed. @@ -1486,7 +1535,17 @@ major mode is a member (or derivative of a member) of `so-long-target-modes'. (or (eq so-long-target-modes t) (apply #'derived-mode-p so-long-target-modes)) (setq so-long-detected-p (funcall so-long-predicate)) - (so-long))) + ;; `so-long' should be called; but only if and when the buffer is + ;; displayed in a window. Long lines in invisible buffers are generally + ;; not problematic, whereas it might cause problems if an invisible + ;; buffer being used for behind-the-scenes processing is manipulated + ;; unexpectedly. The default `so-long-invisible-buffer-function' value + ;; is `so-long-deferred', which arranges to call `so-long' as soon as + ;; the buffer is displayed. + (if (or (get-buffer-window (current-buffer) t) + (not so-long-invisible-buffer-function)) + (so-long) + (funcall so-long-invisible-buffer-function)))) (defun so-long--hack-one-local-variable (orig-fun var val) ;; Advice, enabled with: @@ -1530,6 +1589,14 @@ These local variables will thus not vanish on setting a major mode." ;; VAR is not the 'mode' pseudo-variable. (funcall orig-fun var val))) +(defun so-long-deferred () + "Arrange to call `so-long' if the current buffer is displayed in a window." + ;; The first time that a window-configuration change results in the buffer + ;; being displayed in a window, `so-long' will be called (with the window + ;; selected and the buffer set as current). Because `so-long' removes this + ;; buffer-local hook value, it triggers once at most. + (add-hook 'window-configuration-change-hook #'so-long nil :local)) + ;;;###autoload (defun so-long (&optional action) "Invoke `so-long-action' and run `so-long-hook'. @@ -1547,6 +1614,8 @@ argument, select the action to use interactively." (completing-read "Action (none): " (mapcar #'car so-long-action-alist) nil :require-match))))) + ;; Ensure that `so-long-deferred' only triggers `so-long' once (at most). + (remove-hook 'window-configuration-change-hook #'so-long :local) (unless so-long--calling (let ((so-long--calling t)) (so-long--ensure-enabled) @@ -1693,6 +1762,12 @@ or call the function `global-so-long-mode'.") (defun so-long-unload-function () "Handler for `unload-feature'." (global-so-long-mode 0) + ;; Remove buffer-local `window-configuration-change-hook' values set by + ;; `so-long-deferred'. + (dolist (buf (buffer-list)) + (with-current-buffer buf + (remove-hook 'window-configuration-change-hook #'so-long :local))) + ;; Return nil. Refer to `unload-feature'. nil) diff --git a/test/lisp/so-long-tests/autoload-longlines-mode-tests.el b/test/lisp/so-long-tests/autoload-longlines-mode-tests.el index 5a57e049fb5..c94aeaef24b 100644 --- a/test/lisp/so-long-tests/autoload-longlines-mode-tests.el +++ b/test/lisp/so-long-tests/autoload-longlines-mode-tests.el @@ -40,6 +40,7 @@ (ert-deftest so-long-tests-autoload-longlines-mode () "File-local -*- so-long-action: longlines-mode; eval: (so-long) -*-" (with-temp-buffer + (display-buffer (current-buffer)) (so-long-tests-remember) (insert "-*- so-long-action: longlines-mode; eval: (so-long) -*-\n") (put 'so-long-action 'safe-local-variable #'symbolp) diff --git a/test/lisp/so-long-tests/autoload-major-mode-tests.el b/test/lisp/so-long-tests/autoload-major-mode-tests.el index d82cb59750c..a8f6f9e7b32 100644 --- a/test/lisp/so-long-tests/autoload-major-mode-tests.el +++ b/test/lisp/so-long-tests/autoload-major-mode-tests.el @@ -38,6 +38,7 @@ (ert-deftest so-long-tests-autoload-major-mode () "File-local -*- so-long -*-" (with-temp-buffer + (display-buffer (current-buffer)) (so-long-tests-remember) (insert "-*- so-long -*-\n") (normal-mode) diff --git a/test/lisp/so-long-tests/autoload-minor-mode-tests.el b/test/lisp/so-long-tests/autoload-minor-mode-tests.el index 67f1903c09c..600a35de0a9 100644 --- a/test/lisp/so-long-tests/autoload-minor-mode-tests.el +++ b/test/lisp/so-long-tests/autoload-minor-mode-tests.el @@ -39,6 +39,7 @@ (ert-deftest so-long-tests-autoload-minor-mode () "File-local -*- so-long-action: so-long-minor-mode; eval: (so-long) -*-" (with-temp-buffer + (display-buffer (current-buffer)) (so-long-tests-remember) (insert "-*- so-long-action: so-long-minor-mode; eval: (so-long) -*-\n") (put 'so-long-action 'safe-local-variable #'symbolp) diff --git a/test/lisp/so-long-tests/so-long-tests.el b/test/lisp/so-long-tests/so-long-tests.el index b1e0cb90d00..99af5e91ba0 100644 --- a/test/lisp/so-long-tests/so-long-tests.el +++ b/test/lisp/so-long-tests/so-long-tests.el @@ -29,13 +29,19 @@ ;; (We could consistently use the latter, but the mixture of approaches ;; means that we're testing more things.) -;; Running the tests with "make lisp/so-long-tests" is like: +;; Running manually: ;; -;; HOME=/nonexistent EMACSLOADPATH= LC_ALL=C \ -;; EMACS_TEST_DIRECTORY=/home/phil/emacs/trunk/repository/test \ +;; for test in lisp/so-long-tests/*-tests.el; do make ${test%.el}; done \ +;; 2>&1 | egrep -v '^(Loading|Source file|make|Changed to so-long-mode)' +;; +;; Which is equivalent to: +;; +;; for test in lisp/so-long-tests/*-tests.el; do \ +;; HOME=/nonexistent EMACSLOADPATH= LC_ALL=C EMACS_TEST_DIRECTORY=. \ ;; "../src/emacs" --no-init-file --no-site-file --no-site-lisp \ -;; -L ":." -l ert -l lisp/so-long-tests.el --batch --eval \ -;; '(ert-run-tests-batch-and-exit (quote (not (tag :unstable))))' +;; -L ":." -l ert -l "$test" --batch --eval \ +;; '(ert-run-tests-batch-and-exit (quote (not (tag :unstable))))'; \ +;; done 2>&1 | egrep -v '^(Loading|Source file|Changed to so-long-mode)' ;; ;; See also `ert-run-tests-batch-and-exit'. @@ -58,6 +64,7 @@ (ert-deftest so-long-tests-threshold-under () "Under line length threshold." (with-temp-buffer + (display-buffer (current-buffer)) (insert "#!emacs\n") (insert (make-string (1- so-long-threshold) ?x)) (normal-mode) @@ -66,6 +73,7 @@ (ert-deftest so-long-tests-threshold-at () "At line length threshold." (with-temp-buffer + (display-buffer (current-buffer)) (insert "#!emacs\n") (insert (make-string (1- so-long-threshold) ?x)) (normal-mode) @@ -74,6 +82,7 @@ (ert-deftest so-long-tests-threshold-over () "Over line length threshold." (with-temp-buffer + (display-buffer (current-buffer)) (insert "#!emacs\n") (normal-mode) (so-long-tests-remember) @@ -85,12 +94,14 @@ "Skip leading shebang, whitespace, and comments." ;; Long comment, no newline. (with-temp-buffer + (display-buffer (current-buffer)) (insert "#!emacs\n") (insert (make-string (1+ so-long-threshold) ?\;)) (normal-mode) (should (eq major-mode 'emacs-lisp-mode))) ;; Long comment, with newline. (with-temp-buffer + (display-buffer (current-buffer)) (insert "#!emacs\n") (insert (make-string (1+ so-long-threshold) ?\;)) (insert "\n") @@ -98,6 +109,7 @@ (should (eq major-mode 'emacs-lisp-mode))) ;; Long comment, with short text following. (with-temp-buffer + (display-buffer (current-buffer)) (insert "#!emacs\n") (insert (make-string (1+ so-long-threshold) ?\;)) (insert "\n") @@ -106,6 +118,7 @@ (should (eq major-mode 'emacs-lisp-mode))) ;; Long comment, with long text following. (with-temp-buffer + (display-buffer (current-buffer)) (insert "#!emacs\n") (insert (make-string (1+ so-long-threshold) ?\;)) (insert "\n") @@ -116,6 +129,7 @@ (ert-deftest so-long-tests-max-lines () "Give up after `so-long-max-lines'." (with-temp-buffer + (display-buffer (current-buffer)) (insert "#!emacs\n") ;; Insert exactly `so-long-max-lines' non-comment lines, followed ;; by a long line. @@ -139,10 +153,91 @@ (normal-mode) (should (eq major-mode 'so-long-mode)))))) +(ert-deftest so-long-tests-invisible-buffer-function () + "Call `so-long-invisible-buffer-function' in invisible buffers." + ;; Visible buffer. + (with-temp-buffer + (display-buffer (current-buffer)) + (insert "#!emacs\n") + (normal-mode) + (so-long-tests-remember) + (insert (make-string (1+ so-long-threshold) ?x)) + (normal-mode) + (so-long-tests-assert-and-revert 'so-long-mode)) + ;; Invisible buffer. + (with-temp-buffer + (insert "#!emacs\n") + (normal-mode) + (so-long-tests-remember) + (insert (make-string (1+ so-long-threshold) ?x)) + (normal-mode) + (should (eq major-mode 'emacs-lisp-mode)) + (should (eq nil (get-buffer-window))) + ;; Displaying the buffer should invoke `so-long'. + (display-buffer (current-buffer)) + (should (window-live-p (get-buffer-window))) + (unless (version< emacs-version "27") + ;; From Emacs 27 the `display-buffer' call is insufficient. + ;; The various 'window change functions' are now invoked by the + ;; redisplay, and redisplay does nothing at all in batch mode, + ;; so we cannot test under this revised behaviour. Refer to: + ;; https://lists.gnu.org/archive/html/emacs-devel/2019-10/msg00971.html + ;; For interactive (non-batch) test runs, calling `redisplay' + ;; does do the trick; so do that first. + (redisplay) + (when noninteractive + ;; In batch mode we need to cheat, and just pretend that + ;; `redisplay' triggered `window-configuration-change-hook'. + ;; This means the test is not as useful, but it still covers + ;; part of the process, and so it's better than nothing. + ;; + ;; Also test `so-long--active', in case a future version of + ;; Emacs adds the framework necessary to make `redisplay' work + ;; in batch mode. + (unless (eq so-long--active t) + (run-window-configuration-change-hook)))) + (so-long-tests-assert-and-revert 'so-long-mode)) + ;; `so-long-invisible-buffer-function' is `nil'. + (with-temp-buffer + (insert "#!emacs\n") + (normal-mode) + (so-long-tests-remember) + (insert (make-string (1+ so-long-threshold) ?x)) + (let ((so-long-invisible-buffer-function nil)) + (normal-mode)) + (so-long-tests-assert-and-revert 'so-long-mode)) + ;; `so-long-invisible-buffer-function' is `so-long'. + (with-temp-buffer + (insert "#!emacs\n") + (normal-mode) + (so-long-tests-remember) + (insert (make-string (1+ so-long-threshold) ?x)) + (let ((so-long-invisible-buffer-function #'so-long)) + (normal-mode)) + (so-long-tests-assert-and-revert 'so-long-mode)) + ;; `so-long-invisible-buffer-function' is `ignore'. + (with-temp-buffer + (insert "#!emacs\n") + (normal-mode) + (so-long-tests-remember) + (insert (make-string (1+ so-long-threshold) ?x)) + (let ((so-long-invisible-buffer-function #'ignore)) + (normal-mode)) + (should (eq major-mode 'emacs-lisp-mode)) + (display-buffer (current-buffer)) + (unless (version< emacs-version "27") + ;; See the "Invisible buffer" case earlier in this function. + (redisplay) + (when noninteractive + (unless (eq so-long--active t) + (run-window-configuration-change-hook)))) + (should (eq major-mode 'emacs-lisp-mode)))) + (ert-deftest so-long-tests-actions () "Test each of the standard actions." (dolist (action (mapcar #'car so-long-action-alist)) (with-temp-buffer + (display-buffer (current-buffer)) (insert "#!emacs\n") (normal-mode) (so-long-tests-remember) @@ -210,6 +305,7 @@ "Targeted major modes." ;; Test the `so-long-target-modes' user option. (with-temp-buffer + (display-buffer (current-buffer)) (insert "#!emacs\n") (insert (make-string (1+ so-long-threshold) ?x)) ;; Nil target modes. @@ -233,6 +329,7 @@ "Custom predicate function." ;; Test the `so-long-predicate' user option. (with-temp-buffer + (display-buffer (current-buffer)) (insert "#!emacs\n") ;; Always false. (let ((so-long-predicate #'ignore)) @@ -257,6 +354,7 @@ ;; valid for the file-locals to be on the second line after the shebang, ;; but with the *.el filename we no longer need the shebang. (with-temp-buffer + (display-buffer (current-buffer)) (setq buffer-file-name (expand-file-name "so-long-tests-data.el")) (insert ";; -*- so-long-action:so-long-minor-mode; -*-\n") (put 'so-long-action 'safe-local-variable #'symbolp) @@ -275,6 +373,7 @@ (normal-mode) (so-long-tests-remember)) (with-temp-buffer + (display-buffer (current-buffer)) (setq buffer-file-name (concat (make-temp-name "so-long-tests-") ".el")) (insert ";; -*- so-long-action:so-long-minor-mode; eval:(so-long) -*-\n") (put 'so-long-action 'safe-local-variable #'symbolp) @@ -314,6 +413,7 @@ ;; Downgrade the action from major mode to minor mode. (setq-default so-long-file-local-mode-function 'so-long-mode-downgrade) (with-temp-buffer + (display-buffer (current-buffer)) (insert ,prop-line) (insert (make-string (1+ so-long-threshold) ?x)) (insert ,local-vars) @@ -322,6 +422,7 @@ ;; Do not treat the file-local mode specially. (setq-default so-long-file-local-mode-function nil) (with-temp-buffer + (display-buffer (current-buffer)) (insert ,prop-line) (insert (make-string (1+ so-long-threshold) ?x)) (insert ,local-vars) @@ -331,6 +432,7 @@ (setq-default so-long-file-local-mode-function #'so-long-tests-file-local-mode-function) (with-temp-buffer + (display-buffer (current-buffer)) (insert ,prop-line) (insert (make-string (1+ so-long-threshold) ?x)) (insert ,local-vars) @@ -371,6 +473,7 @@ ;; Do nothing at all when a file-local mode is used. (setq-default so-long-file-local-mode-function 'so-long-inhibit) (with-temp-buffer + (display-buffer (current-buffer)) ;; Remember the new-buffer state. The other cases will ;; validate the 'reverted' state against this. (so-long-tests-remember) @@ -382,6 +485,7 @@ ;; Downgrade from major mode to minor mode. (setq-default so-long-file-local-mode-function 'so-long-mode-downgrade) (with-temp-buffer + (display-buffer (current-buffer)) (insert ,prop-line) (insert (make-string (1+ so-long-threshold) ?x)) (insert ,local-vars) @@ -390,6 +494,7 @@ ;; Do not treat the file-local mode specially. (setq-default so-long-file-local-mode-function nil) (with-temp-buffer + (display-buffer (current-buffer)) (insert ,prop-line) (insert (make-string (1+ so-long-threshold) ?x)) (insert ,local-vars) @@ -399,6 +504,7 @@ (setq-default so-long-file-local-mode-function #'so-long-tests-file-local-mode-function) (with-temp-buffer + (display-buffer (current-buffer)) (insert ,prop-line) (insert (make-string (1+ so-long-threshold) ?x)) (insert ,local-vars) From cb7b63d6e2da14eb92f8f535837fdae04c89dfb2 Mon Sep 17 00:00:00 2001 From: Phil Sainty Date: Mon, 21 Oct 2019 23:57:55 +1300 Subject: [PATCH 3/7] ; * lisp/so-long.el (so-long-predicate): Custom type consistency This is for consistency with the other function options, which all used the 'radio' custom type. --- lisp/so-long.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lisp/so-long.el b/lisp/so-long.el index f7dfc8a79ca..e76ab791fcd 100644 --- a/lisp/so-long.el +++ b/lisp/so-long.el @@ -545,8 +545,8 @@ The specified function will be called with no arguments. If it returns non-nil then `so-long' will be invoked. Defaults to `so-long-detected-long-line-p'." - :type '(choice (const so-long-detected-long-line-p) - (function :tag "Custom function")) + :type '(radio (const so-long-detected-long-line-p) + (function :tag "Custom function")) :package-version '(so-long . "1.0") :group 'so-long) From bf29fc46c81114909bcfe9c4b60bd419651b49a7 Mon Sep 17 00:00:00 2001 From: Phil Sainty Date: Tue, 22 Oct 2019 00:00:16 +1300 Subject: [PATCH 4/7] * lisp/so-long.el (so-long-unload-function): Improved feature unload --- lisp/so-long.el | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/lisp/so-long.el b/lisp/so-long.el index e76ab791fcd..5c9711b0bb0 100644 --- a/lisp/so-long.el +++ b/lisp/so-long.el @@ -1761,14 +1761,45 @@ or call the function `global-so-long-mode'.") (defun so-long-unload-function () "Handler for `unload-feature'." - (global-so-long-mode 0) - ;; Remove buffer-local `window-configuration-change-hook' values set by - ;; `so-long-deferred'. - (dolist (buf (buffer-list)) - (with-current-buffer buf - (remove-hook 'window-configuration-change-hook #'so-long :local))) - ;; Return nil. Refer to `unload-feature'. - nil) + (condition-case err + (progn + (global-so-long-mode 0) + ;; Process existing buffers. + (dolist (buf (buffer-list)) + (with-current-buffer buf + ;; Remove buffer-local `window-configuration-change-hook' values set + ;; by `so-long-deferred'. + (remove-hook 'window-configuration-change-hook #'so-long :local) + ;; Call `so-long-revert' in all buffers where so-long is active. + (when (bound-and-true-p so-long--active) + (so-long-revert)))) + ;; Un-define our buffer-local variables, as `unload-feature' will not do + ;; this automatically. We remove them from `unload-function-defs-list' + ;; as well, to prevent them being redefined. n.b.: `so-long--active' is + ;; tested (above) using `bound-and-true-p' because that is one of the + ;; variables which we unbind (below); and if something subsequent to + ;; this handler signals an error, the user may need to call this again. + (defvar unload-function-defs-list) + (dolist (var '(so-long--active + so-long--inhibited + so-long-detected-p + so-long-file-local-mode-function + so-long-function + so-long-minor-mode + so-long-mode-abbrev-table + so-long-mode-line-info + so-long-mode-syntax-table + so-long-original-values + so-long-revert-function)) + (makunbound var) + (setq unload-function-defs-list + (delq var unload-function-defs-list))) + ;; Return nil if unloading was successful. Refer to `unload-feature'. + nil) + ;; If any error occurred, return non-nil. + (error (progn + (message "Error unloading so-long: %S %S" (car err) (cdr err)) + t)))) (provide 'so-long) From b28f35ac65631243b8e29856a2d13e70d2567b53 Mon Sep 17 00:00:00 2001 From: Phil Sainty Date: Tue, 22 Oct 2019 00:01:59 +1300 Subject: [PATCH 5/7] Backwards-compatibility function definitions for so-long.el * so-long.el (so-long-inhibit-whitespace-mode) (so-long-make-buffer-read-only, so-long-revert-buffer-read-only) (so-long-inhibit-global-hl-line-mode): Restore dummy definitions of now-obsolete hook functions used by earlier versions of so-long.el, to support users who have saved these symbols in their customized values for the hooks in question. --- lisp/so-long.el | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lisp/so-long.el b/lisp/so-long.el index 5c9711b0bb0..982a699667a 100644 --- a/lisp/so-long.el +++ b/lisp/so-long.el @@ -1801,6 +1801,23 @@ or call the function `global-so-long-mode'.") (message "Error unloading so-long: %S %S" (car err) (cdr err)) t)))) +;; Backwards-compatibility definitions. +;; +;; The following obsolete functions may exist in the user's customized hook +;; values dating from versions < 1.0, so we need to ensure that such saved +;; values will not trigger errors. +(cl-flet ((ignore () nil)) + (dolist (hookfunc '((so-long-inhibit-whitespace-mode . so-long-hook) + (so-long-make-buffer-read-only . so-long-hook) + (so-long-revert-buffer-read-only . so-long-revert-hook) + (so-long-inhibit-global-hl-line-mode . so-long-mode-hook))) + (defalias (car hookfunc) #'ignore + (format "Obsolete function. It now does nothing. + +If it appears in `%s', you should remove it." + (cdr hookfunc))) + (make-obsolete (car hookfunc) nil "so-long.el version 1.0"))) + (provide 'so-long) From a94f961e625aa53deaf8605f892fd8993a4f0cb8 Mon Sep 17 00:00:00 2001 From: Phil Sainty Date: Tue, 22 Oct 2019 00:05:50 +1300 Subject: [PATCH 6/7] Support loading so-long.el on top of an earlier version * so-long.el (so-long-version, so-long--latest-version): New variables. This enables users to safely load version 1.0 of so-long.el on top of an earlier version, as well as making provisions for doing likewise following any incompatible changes arising in future versions. --- lisp/so-long.el | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/lisp/so-long.el b/lisp/so-long.el index 982a699667a..2d551affa18 100644 --- a/lisp/so-long.el +++ b/lisp/so-long.el @@ -411,6 +411,8 @@ (add-to-list 'customize-package-emacs-version-alist '(so-long ("1.0" . "27.1"))) +(defconst so-long--latest-version "1.0") + (declare-function longlines-mode "longlines") (defvar longlines-mode) @@ -1818,6 +1820,54 @@ If it appears in `%s', you should remove it." (cdr hookfunc))) (make-obsolete (car hookfunc) nil "so-long.el version 1.0"))) +;; Live upgrades, for when a newer version is loaded over an older one. +;; +;; If `so-long-version' was already bound then that tells us which version we +;; should upgrade from. If `so-long-version' is unbound then most likely there +;; was no older version loaded; however, prior to version 1.0 `so-long-version' +;; was not defined at all, and so we also need to detect that scenario, which +;; we can do by testing for the presence of a symbol which was removed in 1.0. +;; +;; The variable `so-long-mode-enabled' covers versions 0.5 - 0.7.6, which is +;; every pre-1.0 release using the name "so-long.el". +(defvar so-long-version (if (boundp 'so-long-mode-enabled) + "0.5" ;; >= 0.5 and < 1.0 + so-long--latest-version) + "The loaded version of so-long.el.") + +;; Version-specific updates. +(when (version< so-long-version so-long--latest-version) + ;; Perform each update in sequence, as necessary. + ;; Update to version 1.0 from earlier versions: + (when (version< so-long-version "1.0") + (remove-hook 'change-major-mode-hook 'so-long-change-major-mode) + (require 'advice) + (when (ad-find-advice 'hack-local-variables 'after 'so-long--file-local-mode) + (ad-remove-advice 'hack-local-variables 'after 'so-long--file-local-mode) + (ad-activate 'hack-local-variables)) + (when (ad-find-advice 'set-auto-mode 'around 'so-long--set-auto-mode) + (ad-remove-advice 'set-auto-mode 'around 'so-long--set-auto-mode) + (ad-activate 'set-auto-mode)) + (when (boundp 'so-long-mode-map) + (define-key so-long-mode-map [remap so-long-mode-revert] #'so-long-revert)) + (dolist (var '(so-long-mode--inhibited + so-long-original-mode)) + (makunbound var)) + (dolist (func '(so-long-change-major-mode + so-long-check-header-modes + so-long-line-detected-p)) + (fmakunbound func)) + (defvar so-long-mode-enabled) + (when so-long-mode-enabled + (unless global-so-long-mode + (global-so-long-mode 1))) + (makunbound 'so-long-mode-enabled)) + ;; Update to version 1.N: + ;; (when (version< so-long-version "1.N") ...) + ;; + ;; All updates completed. + (setq so-long-version so-long--latest-version)) + (provide 'so-long) From 77c3bc0297b41283da941f80a59c96fc8fc14936 Mon Sep 17 00:00:00 2001 From: Phil Sainty Date: Sat, 9 Nov 2019 19:52:06 +1300 Subject: [PATCH 7/7] Make so-long disable flymake, flyspell, flycheck * so-long.el (so-long-minor-modes): Add flymake-mode, flyspell-mode, and flycheck-mode. --- lisp/so-long.el | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lisp/so-long.el b/lisp/so-long.el index 2d551affa18..78fa32508ae 100644 --- a/lisp/so-long.el +++ b/lisp/so-long.el @@ -731,6 +731,8 @@ was established." '(font-lock-mode ;; (Generally the most important). ;; Other standard minor modes: display-line-numbers-mode + flymake-mode + flyspell-mode goto-address-mode goto-address-prog-mode hi-lock-mode @@ -746,6 +748,7 @@ was established." diff-hl-flydiff-mode diff-hl-mode dtrt-indent-mode + flycheck-mode hl-sexp-mode idle-highlight-mode rainbow-delimiters-mode