diff --git a/etc/NEWS b/etc/NEWS index 95a2c87d054..48dec0a2b3b 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2854,6 +2854,34 @@ GPG key servers can now be queried for keys with the 'M-x epa-search-keys' command. Keys can then be added to your personal key ring. +** So Long + +--- +*** New 'so-long-predicate' function 'so-long-statistics-excessive-p' +efficiently detects the presence of a long line anywhere in the buffer +using 'buffer-line-statistics' (see above). This is now the default +predicate (replacing 'so-long-detected-long-line-p'). + +--- +*** 'so-long-threshold' and 'so-long-max-lines' have been raised to +10000 bytes and 500 lines respectively, to reduce the likelihood of +false-positives when 'global-so-long-mode' is enabled. The latter +value is now only used by the old predicate, as the new predicate +knows the longest line in the entire buffer. + +--- +*** 'so-long-target-modes' now includes 'fundamental-mode' by default, +meaning that 'global-so-long-mode' will also process files which were +not recognised. (This only has an effect if 'set-auto-mode' chooses +'fundamental-mode'; buffers which are simply in 'fundamental-mode' by +default are unaffected.) + +--- +*** New user options 'so-long-mode-preserved-minor-modes' and +'so-long-mode-preserved-variables' allow specified mode and variable +states to be maintained if 'so-long-mode' replaces the original major +mode. By default, these new options support 'view-mode'. + * New Modes and Packages in Emacs 28.1 diff --git a/lisp/so-long.el b/lisp/so-long.el index d765d3449ca..829afd63eab 100644 --- a/lisp/so-long.el +++ b/lisp/so-long.el @@ -8,7 +8,7 @@ ;; Keywords: convenience ;; Created: 23 Dec 2015 ;; Package-Requires: ((emacs "24.4")) -;; Version: 1.0 +;; Version: 1.1 ;; This file is part of GNU Emacs. @@ -50,16 +50,17 @@ ;; performance further, as well as making the so-long activity more obvious to ;; the user. These kinds of minified files are typically not intended to be ;; edited, so not providing the usual editing mode in such cases will rarely be -;; an issue. However, you can reinstate the original state of the buffer by -;; calling `so-long-revert' (the key binding of which is advertised when the major -;; mode change occurs). If you prefer that the major mode not be changed, you -;; can customize the `so-long-minor-mode' action. +;; an issue; however you can restore the buffer to its original state by calling +;; `so-long-revert' (the key binding of which is advertised when the major mode +;; change occurs). If you prefer that the major mode not be changed in the +;; first place, there is a `so-long-minor-mode' action available, which you can +;; select by customizing the `so-long-action' user option. ;; ;; The user options `so-long-action' and `so-long-action-alist' determine what -;; actions `so-long' and `so-long-revert' will take. This allows you to configure -;; alternative actions (including custom actions). As well as -;; the major and minor mode actions provided by this library, `longlines-mode' -;; is also supported by default as an alternative action. +;; `so-long' and `so-long-revert' will do, enabling you to configure alternative +;; actions (including custom actions). As well as the major and minor mode +;; actions provided by this library, `longlines-mode' is also supported by +;; default as an alternative action. ;; ;; Note that while the measures taken can improve performance dramatically when ;; dealing with such files, this library does not have any effect on the @@ -127,9 +128,9 @@ ;; Use M-x customize-group RET so-long RET ;; (or M-x so-long-customize RET) ;; -;; The user options `so-long-target-modes', `so-long-threshold', and -;; `so-long-max-lines' determine whether action will be taken automatically when -;; visiting a file, and `so-long-action' determines what will be done. +;; The user options `so-long-target-modes' and `so-long-threshold' determine +;; whether action will be taken automatically when visiting a file, and +;; `so-long-action' determines what will be done. ;; * Actions and menus ;; ------------------- @@ -152,7 +153,7 @@ ;; * Files with a file-local 'mode' ;; -------------------------------- ;; A file-local major mode is likely to be safe even if long lines are detected -;; (as the author of the file would otherwise be unlikely to have set that mode), +;; (the author of the file would otherwise be unlikely to have set that mode), ;; and so these files are treated as special cases. When a file-local 'mode' is ;; present, the function defined by the `so-long-file-local-mode-function' user ;; option is called. The default value will cause the `so-long-minor-mode' @@ -213,6 +214,24 @@ ;; performance or otherwise avoid undesirable behaviours. If `so-long-revert' ;; is called, then the original values are restored. +;; * Retaining minor modes and settings when switching to `so-long-mode' +;; --------------------------------------------------------------------- +;; A consequence of switching to a new major mode is that many buffer-local +;; minor modes and variables from the original major mode will be disabled. +;; For performance purposes this is a desirable trait of `so-long-mode', but +;; specified modes and variables can also be preserved across the major mode +;; transition by customizing the `so-long-mode-preserved-minor-modes' and +;; `so-long-mode-preserved-variables' user options. +;; +;; When `so-long-mode' is called, the states of any modes and variables +;; configured by these options are remembered in the original major mode, and +;; reinstated after switching to `so-long-mode'. Likewise, if `so-long-revert' +;; is used to switch back to the original major mode, these modes and variables +;; are again set to the same states. +;; +;; The default values for these options ensure that if `view-mode' was active +;; in the original mode, then it will also be active in `so-long-mode'. + ;; * Hooks ;; ------- ;; `so-long-hook' runs at the end of the `so-long' command, after the configured @@ -287,8 +306,9 @@ ;; the criteria for calling `so-long' in any given mode (plus its derivatives) ;; by setting buffer-local values for the variables in question. This includes ;; `so-long-predicate' itself, as well as any variables used by the predicate -;; when determining the result. By default this means `so-long-max-lines', -;; `so-long-skip-leading-comments', and `so-long-threshold'. E.g.: +;; when determining the result. By default this means `so-long-threshold' and +;; possibly also `so-long-max-lines' and `so-long-skip-leading-comments' (these +;; latter two are not used by default starting from Emacs 28.1). E.g.: ;; ;; (add-hook 'js-mode-hook 'my-js-mode-hook) ;; @@ -390,6 +410,13 @@ ;; * Change Log: ;; +;; 1.1 - Utilise `buffer-line-statistics' in Emacs 28+, with the new +;; `so-long-predicate' function `so-long-statistics-excessive-p'. +;; - Increase `so-long-threshold' from 250 to 10,000. +;; - Increase `so-long-max-lines' from 5 to 500. +;; - Include `fundamental-mode' in `so-long-target-modes'. +;; - New user option `so-long-mode-preserved-minor-modes'. +;; - New user option `so-long-mode-preserved-variables'. ;; 1.0 - Included in Emacs 27.1, and in GNU ELPA for prior versions of Emacs. ;; - New global mode `global-so-long-mode' to enable/disable the library. ;; - New user option `so-long-action'. @@ -442,13 +469,19 @@ (require 'cl-lib) +;; Map each :package-version to the associated Emacs version. +;; (This eliminates the need for explicit :version keywords on the +;; custom definitions.) (add-to-list 'customize-package-emacs-version-alist - '(so-long ("1.0" . "27.1"))) + '(so-long ("1.0" . "27.1") + ("1.1" . "28.1"))) -(defconst so-long--latest-version "1.0") +(defconst so-long--latest-version "1.1") +(declare-function buffer-line-statistics "fns.c" t t) ;; Emacs 28+ (declare-function longlines-mode "longlines") (defvar longlines-mode) + (defvar so-long-enabled nil ;; This was initially a renaming of the old `so-long-mode-enabled' and ;; documented as "Set to nil to prevent `so-long' from being triggered @@ -488,16 +521,24 @@ :prefix "so-long" :group 'convenience) -(defcustom so-long-threshold 250 +(defcustom so-long-threshold 10000 "Maximum line length permitted before invoking `so-long-function'. -See `so-long-detected-long-line-p' for details." - :type 'integer - :package-version '(so-long . "1.0")) +Line length is counted in either bytes or characters, depending on +`so-long-predicate'. -(defcustom so-long-max-lines 5 +This is the only variable used to determine the presence of long lines if +the `so-long-predicate' function is `so-long-statistics-excessive-p'." + :type 'integer + :package-version '(so-long . "1.1")) + +(defcustom so-long-max-lines 500 "Number of non-blank, non-comment lines to test for excessive length. +This option normally has no effect in Emacs versions >= 28.1, as the default +`so-long-predicate' sees the entire buffer. Older versions of Emacs still make +use of this option. + If nil then all lines will be tested, until either a long line is detected, or the end of the buffer is reached. @@ -507,11 +548,15 @@ be counted. See `so-long-detected-long-line-p' for details." :type '(choice (integer :tag "Limit") (const :tag "Unlimited" nil)) - :package-version '(so-long . "1.0")) + :package-version '(so-long . "1.1")) (defcustom so-long-skip-leading-comments t "Non-nil to ignore all leading comments and whitespace. +This option normally has no effect in Emacs versions >= 28.1, as the default +`so-long-predicate' sees the entire buffer. Older versions of Emacs still make +use of this option. + If the file begins with a shebang (#!), this option also causes that line to be ignored even if it doesn't match the buffer's comment syntax, to ensure that comments following the shebang will be ignored. @@ -521,7 +566,7 @@ See `so-long-detected-long-line-p' for details." :package-version '(so-long . "1.0")) (defcustom so-long-target-modes - '(prog-mode css-mode sgml-mode nxml-mode) + '(prog-mode css-mode sgml-mode nxml-mode fundamental-mode) "`so-long' affects only these modes and their derivatives. Our primary use-case is minified programming code, so `prog-mode' covers @@ -534,7 +579,7 @@ files would prevent Emacs from handling them correctly." ;; Use 'symbol', as 'function' may be unknown => mismatch. :type '(choice (repeat :tag "Specified modes" symbol) (const :tag "All modes" t)) - :package-version '(so-long . "1.0")) + :package-version '(so-long . "1.1")) (defcustom so-long-invisible-buffer-function #'so-long-deferred "Function called in place of `so-long' when the buffer is not displayed. @@ -566,7 +611,9 @@ the mentioned options might interfere with some intended processing." (function :tag "Custom function")) :package-version '(so-long . "1.0")) -(defcustom so-long-predicate 'so-long-detected-long-line-p +(defcustom so-long-predicate (if (fboundp 'buffer-line-statistics) + 'so-long-statistics-excessive-p + 'so-long-detected-long-line-p) "Function, called after `set-auto-mode' to decide whether action is needed. Only called if the major mode is a member of `so-long-target-modes'. @@ -574,10 +621,14 @@ Only called if the major mode is a member of `so-long-target-modes'. The specified function will be called with no arguments. If it returns non-nil then `so-long' will be invoked. -Defaults to `so-long-detected-long-line-p'." - :type '(radio (const so-long-detected-long-line-p) +Defaults to `so-long-statistics-excessive-p' starting from Emacs 28.1, or +`so-long-detected-long-line-p' in earlier versions. + +Note that `so-long-statistics-excessive-p' requires Emacs 28.1 or later." + :type '(radio (const so-long-statistics-excessive-p) + (const so-long-detected-long-line-p) (function :tag "Custom function")) - :package-version '(so-long . "1.0")) + :package-version '(so-long . "1.1")) ;; Silence byte-compiler warning. `so-long-action-alist' is defined below ;; as a user option; but the definition sequence required for its setter @@ -757,6 +808,7 @@ was established." display-line-numbers-mode flymake-mode flyspell-mode + glasses-mode goto-address-mode goto-address-prog-mode hi-lock-mode @@ -776,6 +828,8 @@ was established." hl-sexp-mode idle-highlight-mode rainbow-delimiters-mode + smartparens-mode + smartparens-strict-mode ) ;; It's not clear to me whether all of these would be problematic, but they ;; seemed like reasonable targets. Some are certainly excessive in smaller @@ -800,7 +854,7 @@ disabled modes are re-enabled by calling them with the numeric argument 1. Please submit bug reports to recommend additional modes for this list, whether they are in Emacs core, GNU ELPA, or elsewhere." :type '(repeat symbol) ;; not function, as may be unknown => mismatch. - :package-version '(so-long . "1.0")) + :package-version '(so-long . "1.1")) (defcustom so-long-variable-overrides '((bidi-inhibit-bpa . t) @@ -848,6 +902,44 @@ intended to be edited manually." (which-func-mode boolean)) :package-version '(so-long . "1.0")) +(defcustom so-long-mode-preserved-minor-modes + '(view-mode) + "List of buffer-local minor modes to preserve in `so-long-mode'. + +These will be enabled or disabled after switching to `so-long-mode' (by calling +them with the numeric argument 1 or 0) in accordance with their state in the +buffer's original major mode. Unknown modes, and modes which are already in the +desired state, are ignored. + +This happens before `so-long-variable-overrides' and `so-long-minor-modes' +have been processed. + +By default this happens only if `so-long-action' is set to `so-long-mode'. +If `so-long-revert' is subsequently invoked, then the modes are again set +to their original state after the original major mode has been called. + +See also `so-long-mode-preserved-variables' (processed after this)." + :type '(repeat symbol) ;; not function, as may be unknown => mismatch. + :package-version '(so-long . "1.1")) + +(defcustom so-long-mode-preserved-variables + '(view-old-buffer-read-only) + "List of buffer-local variables to preserve in `so-long-mode'. + +The original value of each variable will be maintained after switching to +`so-long-mode'. Unknown variables are ignored. + +This happens before `so-long-variable-overrides' and `so-long-minor-modes' +have been processed. + +By default this happens only if `so-long-action' is set to `so-long-mode'. +If `so-long-revert' is subsequently invoked, then the variables are again +set to their original values after the original major mode has been called. + +See also `so-long-mode-preserved-minor-modes' (processed before this)." + :type '(repeat variable) + :package-version '(so-long . "1.1")) + (defcustom so-long-hook nil "List of functions to call after `so-long' is called. @@ -934,9 +1026,16 @@ If RESET is non-nil, remove any existing values before storing the new ones." (setq so-long-original-values nil)) (so-long-remember 'so-long-variable-overrides) (so-long-remember 'so-long-minor-modes) + (so-long-remember 'so-long-mode-preserved-variables) + (so-long-remember 'so-long-mode-preserved-minor-modes) (dolist (ovar so-long-variable-overrides) (so-long-remember (car ovar))) (dolist (mode so-long-minor-modes) + (when (and (boundp mode) mode) + (so-long-remember mode))) + (dolist (var so-long-mode-preserved-variables) + (so-long-remember var)) + (dolist (mode so-long-mode-preserved-minor-modes) (when (and (boundp mode) mode) (so-long-remember mode)))) @@ -1077,12 +1176,23 @@ serves the same purpose.") ;; We change automatically to faster code ;; And then I won't feel so mad +(defun so-long-statistics-excessive-p () + "Non-nil if the buffer contains a line longer than `so-long-threshold' bytes. + +This uses `buffer-line-statistics' (available from Emacs 28.1) to establish the +longest line in the buffer (counted in bytes rather than characters). + +This is the default value of `so-long-predicate' in Emacs versions >= 28.1. +\(In earlier versions `so-long-detected-long-line-p' is used by default.)" + (> (cadr (buffer-line-statistics)) + so-long-threshold)) + (defun so-long-detected-long-line-p () "Determine whether the current buffer contains long lines. Following any initial comments and blank lines, the next N lines of the buffer -will be tested for excessive length (where \"excessive\" means above -`so-long-threshold', and N is `so-long-max-lines'). +will be tested for excessive length (where \"excessive\" means greater than +`so-long-threshold' characters, and N is `so-long-max-lines'). Returns non-nil if any such excessive-length line is detected. @@ -1090,7 +1200,9 @@ If `so-long-skip-leading-comments' is nil then the N lines will be counted starting from the first line of the buffer. In this instance you will likely want to increase `so-long-max-lines' to allow for possible comments. -This is the default value of `so-long-predicate'." +This is the default `so-long-predicate' function in Emacs versions < 28.1. +\(Starting from 28.1, the default and recommended predicate function is +`so-long-statistics-excessive-p', which is faster and sees the entire buffer.)" (let ((count 0) start) (save-excursion (goto-char (point-min)) @@ -1191,7 +1303,8 @@ This minor mode is a standard `so-long-action' option." ;; Housekeeping. `so-long-minor-mode' might be invoked directly rather ;; than via `so-long', so replicate the necessary behaviours. The minor ;; mode also cares about whether `so-long' was already active, as we do - ;; not want to remember values which were potentially overridden already. + ;; not want to remember values which were (potentially) overridden + ;; already. (unless (or so-long--calling so-long--active) (so-long--ensure-enabled) (setq so-long--active t @@ -1321,6 +1434,16 @@ This advice acts before `so-long-mode', with the previous mode still active." "Run by `so-long-mode' in `after-change-major-mode-hook'. Calls `so-long-disable-minor-modes' and `so-long-override-variables'." + ;; Check/set the state of 'preserved' variables and minor modes. + ;; (See also `so-long-mode-revert'.) + ;; The "modes before variables" sequence is important for the default + ;; preserved mode `view-mode' which remembers the `buffer-read-only' state + ;; (which is also permanent-local). That causes problems unless we restore + ;; the original value of `view-old-buffer-read-only' after; otherwise the + ;; sequence `view-mode' -> `so-long' -> `so-long-revert' -> `view-mode' + ;; results in `view-mode' being disabled but the buffer still read-only. + (so-long-mode-maintain-preserved-minor-modes) + (so-long-mode-maintain-preserved-variables) ;; Disable minor modes. (so-long-disable-minor-modes) ;; Override variables (again). We already did this in `so-long-mode' in @@ -1334,14 +1457,15 @@ Calls `so-long-disable-minor-modes' and `so-long-override-variables'." (defun so-long-disable-minor-modes () "Disable any active minor modes listed in `so-long-minor-modes'." (dolist (mode (so-long-original 'so-long-minor-modes)) - (when (and (boundp mode) mode) + (when (and (boundp mode) + (symbol-value mode)) (funcall mode 0)))) (defun so-long-restore-minor-modes () "Restore the minor modes which were disabled. The modes are enabled in accordance with what was remembered in `so-long'." - (dolist (mode so-long-minor-modes) + (dolist (mode (so-long-original 'so-long-minor-modes)) (when (and (so-long-original mode) (boundp mode) (not (symbol-value mode))) @@ -1356,7 +1480,7 @@ The modes are enabled in accordance with what was remembered in `so-long'." "Restore the remembered values for the overridden variables. The variables are set in accordance with what was remembered in `so-long'." - (dolist (ovar so-long-variable-overrides) + (dolist (ovar (so-long-original 'so-long-variable-overrides)) (so-long-restore-variable (car ovar)))) (defun so-long-restore-variable (variable) @@ -1364,7 +1488,7 @@ The variables are set in accordance with what was remembered in `so-long'." ;; In the instance where `so-long-mode-revert' has just reverted the major ;; mode, note that `kill-all-local-variables' was already called by the ;; original mode function, and so these 'overridden' variables may now have - ;; global rather than buffer-local values. + ;; global rather than buffer-local values (if they are not permanent-local). (let* ((remembered (so-long-original variable :exists)) (originally-local (nth 2 remembered))) (if originally-local @@ -1380,6 +1504,24 @@ The variables are set in accordance with what was remembered in `so-long'." ;; the old value as a buffer-local value, so we keep it simple. (kill-local-variable variable)))) +(defun so-long-mode-maintain-preserved-variables () + "Set any 'preserved' variables. + +The variables are set in accordance with what was remembered in `so-long'." + (dolist (var (so-long-original 'so-long-mode-preserved-variables)) + (so-long-restore-variable var))) + +(defun so-long-mode-maintain-preserved-minor-modes () + "Enable or disable 'preserved' minor modes. + +The modes are set in accordance with what was remembered in `so-long'." + (dolist (mode (so-long-original 'so-long-mode-preserved-minor-modes)) + (when (boundp mode) + (let ((original (so-long-original mode)) + (current (symbol-value mode))) + (unless (equal current original) + (funcall mode (if original 1 0))))))) + (defun so-long-mode-revert () "Call the `major-mode' which was selected before `so-long-mode' replaced it. @@ -1407,6 +1549,10 @@ This is the `so-long-revert-function' for `so-long-mode'." ;; `kill-all-local-variables' was already called by the original mode ;; function, so we may be seeing global values. (so-long-restore-variables) + ;; Check/set the state of 'preserved' variables and minor modes. + ;; (Refer to `so-long-after-change-major-mode' regarding the sequence.) + (so-long-mode-maintain-preserved-minor-modes) + (so-long-mode-maintain-preserved-variables) ;; Restore the mode line construct. (unless (derived-mode-p 'so-long-mode) (setq so-long-mode-line-info (so-long-mode-line-info))))) @@ -1648,8 +1794,7 @@ invoking the new action." (when so-long--active (so-long-revert)) ;; Invoke the new action. - (let ((so-long--calling t) - (view-mode-active view-mode)) + (let ((so-long--calling t)) (so-long--ensure-enabled) ;; ACTION takes precedence if supplied. (when action @@ -1678,10 +1823,7 @@ invoking the new action." ;; functions need to modify the buffer. We use `inhibit-read-only' to ;; side-step the issue (and likewise in `so-long-revert'). (let ((inhibit-read-only t)) - (run-hooks 'so-long-hook)) - ;; Restore `view-mode'. - (when view-mode-active - (view-mode))))) + (run-hooks 'so-long-hook))))) (defun so-long-revert () "Revert the active `so-long-action' and run `so-long-revert-hook'. @@ -1896,7 +2038,7 @@ If it appears in `%s', you should remove it." (unless global-so-long-mode (global-so-long-mode 1))) (makunbound 'so-long-mode-enabled)) - ;; Update to version 1.N: + ;; Update to version 1.N from earlier versions: ;; (when (version< so-long-version "1.N") ...) ;; ;; All updates completed. diff --git a/test/lisp/so-long-tests/so-long-tests-helpers.el b/test/lisp/so-long-tests/so-long-tests-helpers.el index ab4d9c6c137..dd2331e6e4a 100644 --- a/test/lisp/so-long-tests/so-long-tests-helpers.el +++ b/test/lisp/so-long-tests/so-long-tests-helpers.el @@ -43,7 +43,8 @@ (cl-case action ('so-long-mode (should (eq major-mode 'so-long-mode)) - (so-long-tests-assert-overrides)) + (so-long-tests-assert-overrides) + (so-long-tests-assert-preserved)) ('so-long-minor-mode (should (eq so-long-minor-mode t)) (so-long-tests-assert-overrides)) @@ -62,7 +63,8 @@ (cl-case action ('so-long-mode (should-not (eq major-mode 'so-long-mode)) - (so-long-tests-assert-overrides-reverted)) + (so-long-tests-assert-overrides-reverted) + (so-long-tests-assert-preserved)) ('so-long-minor-mode (should-not (eq so-long-minor-mode t)) (so-long-tests-assert-overrides-reverted)) @@ -90,6 +92,17 @@ (when (boundp (car ovar)) (should (equal (symbol-value (car ovar)) (cdr ovar)))))) +(defun so-long-tests-assert-preserved () + "Assert that preserved modes and variables have their expected values." + (dolist (var so-long-mode-preserved-variables) + (when (boundp var) + (should (equal (symbol-value var) + (alist-get var so-long-tests-memory))))) + (dolist (mode so-long-mode-preserved-minor-modes) + (when (boundp mode) + (should (equal (symbol-value mode) + (alist-get mode so-long-tests-memory)))))) + (defun so-long-tests-remember () "Remember the original states of modes and variables. @@ -105,9 +118,24 @@ state against this remembered state." (push (cons (car ovar) (symbol-value (car ovar))) so-long-tests-memory))) (dolist (mode so-long-minor-modes) + (when (boundp mode) + (push (cons mode (symbol-value mode)) + so-long-tests-memory))) + (dolist (var so-long-mode-preserved-variables) + (when (boundp var) + (push (cons var (symbol-value var)) + so-long-tests-memory))) + (dolist (mode so-long-mode-preserved-minor-modes) (when (boundp mode) (push (cons mode (symbol-value mode)) so-long-tests-memory)))) +(defun so-long-tests-predicates () + "Return the list of testable predicate functions." + (if (fboundp 'buffer-line-statistics) + '(so-long-statistics-excessive-p + so-long-detected-long-line-p) + '(so-long-detected-long-line-p))) + (provide 'so-long-tests-helpers) ;;; so-long-tests-helpers.el ends here diff --git a/test/lisp/so-long-tests/so-long-tests.el b/test/lisp/so-long-tests/so-long-tests.el index a6d8721ffc8..8e4597c946c 100644 --- a/test/lisp/so-long-tests/so-long-tests.el +++ b/test/lisp/so-long-tests/so-long-tests.el @@ -57,101 +57,131 @@ (declare-function so-long-tests-assert-active "so-long-tests-helpers") (declare-function so-long-tests-assert-reverted "so-long-tests-helpers") (declare-function so-long-tests-assert-and-revert "so-long-tests-helpers") +(declare-function so-long-tests-predicates "so-long-tests-helpers") -;; Enable the automated behavior for all tests. +;; Enable the automated behaviour for all tests. (global-so-long-mode 1) (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) - (should (eq major-mode 'emacs-lisp-mode)))) + (dolist (so-long-predicate (so-long-tests-predicates)) + (with-temp-buffer + (display-buffer (current-buffer)) + (insert "#!emacs\n") + (insert (make-string (1- so-long-threshold) ?x)) + (normal-mode) + (should (eq major-mode 'emacs-lisp-mode))))) (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) - (should (eq major-mode 'emacs-lisp-mode)))) + (dolist (so-long-predicate (so-long-tests-predicates)) + (with-temp-buffer + (display-buffer (current-buffer)) + (insert "#!emacs\n") + (insert (make-string (1- so-long-threshold) ?x)) + (normal-mode) + (should (eq major-mode 'emacs-lisp-mode))))) (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) - (insert (make-string (1+ so-long-threshold) ?x)) - (normal-mode) - (so-long-tests-assert-and-revert 'so-long-mode))) + (dolist (so-long-predicate (so-long-tests-predicates)) + (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)))) (ert-deftest so-long-tests-skip-comments () "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") - (normal-mode) - (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") - (insert (make-string so-long-threshold ?x)) - (normal-mode) - (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") - (insert (make-string (1+ so-long-threshold) ?x)) - (normal-mode) - (should (eq major-mode 'so-long-mode)))) + ;; Only for `so-long-detected-long-line-p' -- comments are not + ;; treated differently when using `so-long-statistics-excessive-p'. + (dolist (so-long-predicate (so-long-tests-predicates)) + ;; 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 + (cond ((eq so-long-predicate #'so-long-detected-long-line-p) + 'emacs-lisp-mode) + ((eq so-long-predicate #'so-long-statistics-excessive-p) + 'so-long-mode))))) + ;; Long comment, with newline. + (with-temp-buffer + (display-buffer (current-buffer)) + (insert "#!emacs\n") + (insert (make-string (1+ so-long-threshold) ?\;)) + (insert "\n") + (normal-mode) + (should (eq major-mode + (cond ((eq so-long-predicate #'so-long-detected-long-line-p) + 'emacs-lisp-mode) + ((eq so-long-predicate #'so-long-statistics-excessive-p) + 'so-long-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") + (insert (make-string so-long-threshold ?x)) + (normal-mode) + (should (eq major-mode + (cond ((eq so-long-predicate #'so-long-detected-long-line-p) + 'emacs-lisp-mode) + ((eq so-long-predicate #'so-long-statistics-excessive-p) + 'so-long-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") + (insert (make-string (1+ so-long-threshold) ?x)) + (normal-mode) + (should (eq major-mode 'so-long-mode))))) (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. - (dotimes (_ so-long-max-lines) - (insert "x\n")) - (insert (make-string (1+ so-long-threshold) ?x)) - (normal-mode) - (should (eq major-mode 'emacs-lisp-mode)) - ;; If `so-long-max-lines' is nil, don't give up the search. - (let ((so-long-max-lines nil)) + ;; Only for `so-long-detected-long-line-p' -- the whole buffer is + ;; 'seen' when using `so-long-statistics-excessive-p'. + (dolist (so-long-predicate (so-long-tests-predicates)) + (with-temp-buffer + (display-buffer (current-buffer)) + (insert "#!emacs\n") + ;; Insert exactly `so-long-max-lines' non-comment lines, followed + ;; by a long line. + (dotimes (_ so-long-max-lines) + (insert "x\n")) + (insert (make-string (1+ so-long-threshold) ?x)) (normal-mode) - (should (eq major-mode 'so-long-mode))) - ;; If `so-long-skip-leading-comments' is nil, all lines are - ;; counted, and so the shebang line counts, which makes the - ;; long line one line further away. - (let ((so-long-skip-leading-comments nil) - (so-long-max-lines (1+ so-long-max-lines))) - (normal-mode) - (should (eq major-mode 'emacs-lisp-mode)) - (let ((so-long-max-lines (1+ so-long-max-lines))) + (should (eq major-mode + (cond ((eq so-long-predicate #'so-long-detected-long-line-p) + 'emacs-lisp-mode) + ((eq so-long-predicate #'so-long-statistics-excessive-p) + 'so-long-mode)))) + ;; If `so-long-max-lines' is nil, don't give up the search. + (let ((so-long-max-lines nil)) (normal-mode) - (should (eq major-mode 'so-long-mode)))))) + (should (eq major-mode 'so-long-mode))) + ;; If `so-long-skip-leading-comments' is nil, all lines are + ;; counted, and so the shebang line counts, which makes the + ;; long line one line further away. + (let ((so-long-skip-leading-comments nil) + (so-long-max-lines (1+ so-long-max-lines))) + (normal-mode) + (should (eq major-mode + (cond ((eq so-long-predicate #'so-long-detected-long-line-p) + 'emacs-lisp-mode) + ((eq so-long-predicate #'so-long-statistics-excessive-p) + 'so-long-mode)))) + (let ((so-long-max-lines (1+ so-long-max-lines))) + (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." @@ -180,7 +210,7 @@ ;; 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 behavior. Refer to: + ;; so we cannot test under this revised behaviour. Refer to: ;; https://lists.gnu.org/r/emacs-devel/2019-10/msg00971.html ;; For interactive (non-batch) test runs, calling `redisplay' ;; does do the trick; so do that first. @@ -195,7 +225,9 @@ ;; Emacs adds the framework necessary to make `redisplay' work ;; in batch mode. (unless (eq so-long--active t) - (run-window-configuration-change-hook)))) + (with-suppressed-warnings + ((obsolete run-window-configuration-change-hook)) + (run-window-configuration-change-hook))))) (so-long-tests-assert-and-revert 'so-long-mode)) ;; `so-long-invisible-buffer-function' is `nil'. (with-temp-buffer @@ -230,7 +262,9 @@ (redisplay) (when noninteractive (unless (eq so-long--active t) - (run-window-configuration-change-hook)))) + (with-suppressed-warnings + ((obsolete run-window-configuration-change-hook)) + (run-window-configuration-change-hook))))) (should (eq major-mode 'emacs-lisp-mode)))) (ert-deftest so-long-tests-actions () @@ -323,20 +357,76 @@ (normal-mode) (should (eq major-mode 'so-long-mode))))) -(ert-deftest so-long-tests-predicate () - "Custom predicate function." - ;; Test the `so-long-predicate' user option. +(ert-deftest so-long-tests-preserved-variables-and-modes () + "Preserved variables and minor modes when using `so-long-mode'." + ;; Test the user options `so-long-mode-preserved-variables' and + ;; `so-long-mode-preserved-minor-modes'. The minor mode `view-mode' + ;; is 'preserved' by default (using both options). + (with-temp-buffer + (display-buffer (current-buffer)) + (insert "#!emacs\n") + (normal-mode) + ;; We enable `view-mode' before triggering `so-long'. + (insert (make-string (1+ so-long-threshold) ?x)) + (view-mode 1) + (should (eq view-mode t)) + (should (eq buffer-read-only t)) + (so-long-tests-remember) + (let ((so-long-action 'so-long-mode) + (menu (so-long-menu))) + (so-long) + (so-long-tests-assert-active 'so-long-mode) + (should (eq view-mode t)) + (should (eq buffer-read-only t)) + ;; Revert. + (funcall (lookup-key menu [so-long-revert])) + (so-long-tests-assert-reverted 'so-long-mode) + (should (eq view-mode t)) + (should (eq buffer-read-only t)) + ;; Disable `view-mode'. Note that without the preserved + ;; variables, the conflict between how `view-mode' and `so-long' + ;; each deal with the buffer's original `buffer-read-only' value + ;; would lead to a situation whereby the buffer would still be + ;; read-only after `view-mode' had been disabled. + (view-mode 0) + (should (eq view-mode nil)) + (should (eq buffer-read-only nil)))) + ;; Without `view-mode'. + (with-temp-buffer + (display-buffer (current-buffer)) + (insert "#!emacs\n") + (normal-mode) + (insert (make-string (1+ so-long-threshold) ?x)) + (should (eq view-mode nil)) + (so-long-tests-remember) + (let ((so-long-action 'so-long-mode) + (menu (so-long-menu))) + (so-long) + (so-long-tests-assert-active 'so-long-mode) + (should (eq view-mode nil)) + ;; Revert. + (funcall (lookup-key menu [so-long-revert])) + (so-long-tests-assert-reverted 'so-long-mode) + (should (eq view-mode nil))))) + +(ert-deftest so-long-tests-predicate () + "Custom predicate function." + ;; Test the `so-long-predicate' user option. + ;; Always true. Trigger when we normally wouldn't. (with-temp-buffer (display-buffer (current-buffer)) (insert "#!emacs\n") - ;; Always false. - (let ((so-long-predicate #'ignore)) - (normal-mode) - (should (eq major-mode 'emacs-lisp-mode))) - ;; Always true. (let ((so-long-predicate (lambda () t))) (normal-mode) - (should (eq major-mode 'so-long-mode))))) + (should (eq major-mode 'so-long-mode)))) + ;; Always false. Don't trigger when we normally would. + (with-temp-buffer + (display-buffer (current-buffer)) + (insert "#!emacs\n") + (insert (make-string (1+ so-long-threshold) ?x)) + (let ((so-long-predicate #'ignore)) + (normal-mode) + (should (eq major-mode 'emacs-lisp-mode))))) (ert-deftest so-long-tests-file-local-action () "File-local action." @@ -405,7 +495,10 @@ (insert ,local-vars) (normal-mode) ;; Remember the `emacs-lisp-mode' state. The other cases - ;; will validate the 'reverted' state against this. + ;; will validate the 'reverted' state against this. (Note + ;; that we haven't displayed the buffer, and therefore only + ;; `so-long-invisible-buffer-function' has acted, so we are + ;; still remembering the 'before' state.) (so-long-tests-remember) (should (eq major-mode 'emacs-lisp-mode))) ;; Downgrade the action from major mode to minor mode. diff --git a/test/lisp/so-long-tests/spelling-tests.el b/test/lisp/so-long-tests/spelling-tests.el index 0be8555bdd2..f778b646635 100644 --- a/test/lisp/so-long-tests/spelling-tests.el +++ b/test/lisp/so-long-tests/spelling-tests.el @@ -57,7 +57,7 @@ (unwind-protect (cl-letf (((symbol-function 'ispell-command-loop) (lambda (_miss _guess word _start _end) - (message "Unrecognized word: %s." word) + (message "Unrecognised word: %s." word) (throw 'mistake t)))) (catch 'mistake (find-library "so-long")