diff options
-rw-r--r-- | lisp/so-long.el | 240 | ||||
-rw-r--r-- | test/lisp/so-long-tests/autoload-longlines-mode-tests.el | 1 | ||||
-rw-r--r-- | test/lisp/so-long-tests/autoload-major-mode-tests.el | 1 | ||||
-rw-r--r-- | test/lisp/so-long-tests/autoload-minor-mode-tests.el | 1 | ||||
-rw-r--r-- | test/lisp/so-long-tests/so-long-tests.el | 116 | ||||
-rw-r--r-- | test/lisp/so-long-tests/spelling-tests.el | 69 |
6 files changed, 401 insertions, 27 deletions
diff --git a/lisp/so-long.el b/lisp/so-long.el index b5eb1242156..78fa32508ae 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 @@ -325,7 +342,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 @@ -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'. @@ -393,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) @@ -413,7 +433,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'.") @@ -487,6 +507,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. @@ -496,8 +547,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) @@ -654,7 +705,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)) @@ -680,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 @@ -695,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 @@ -707,7 +761,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 +796,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 +1228,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 +1243,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 +1398,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 +1433,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 @@ -1486,7 +1540,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 +1594,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 +1619,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) @@ -1626,9 +1700,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. @@ -1692,17 +1766,139 @@ or call the function `global-so-long-mode'.") (defun so-long-unload-function () "Handler for `unload-feature'." - (global-so-long-mode 0) - 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)))) + +;; 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"))) + +;; 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) ;; 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/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) 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 <psainty@orcon.net.nz> +;; 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 <https://www.gnu.org/licenses/>. + +;;; 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 |