diff --git a/README.md b/README.md index f1276f6..23e9d79 100644 --- a/README.md +++ b/README.md @@ -52,13 +52,14 @@ To configure via `use-package`, adapt the following example as desired: ### Customization -| Variable | Type | Default | Description | -| :--------------------------------------------- | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- | -| `snap-indent-excluded-modes` | symbol list | `(cmake-ts-mode coffee-mode conf-mode elm-mode haml-mode haskell-mode makefile-automake-mode makefile-bsdmake-mode makefile-gmake-mode makefile-imake-mode makefile-makepp-mode makefile-mode occam-mode python-mode python-ts-mode slim-mode yaml-mode yaml-ts-mode)` | Major modes in which to ignore activation | -| `snap-indent-format` | function or list | `nil` | Additional formatting to apply when indenting | -| `snap-indent-on-save` | boolean | `nil` | Whether to indent the entire buffer on save | -| `snap-indent-yank-threshold` | boolean | `nil` | Do not indent yanked text if its length exceeds the threshold | -| `snap-indent-yank-skip-indent-with-prefix-arg` | boolean | `nil` | Do not indent yanked text if yank was invoked with a prefix arg | +| Variable | Type | Default | Description | +|:---------------------------------|:-----------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------| +| `snap-indent-excluded-modes` | symbol list | `(cmake-ts-mode coffee-mode conf-mode elm-mode haml-mode haskell-mode makefile-automake-mode makefile-bsdmake-mode makefile-gmake-mode makefile-imake-mode makefile-makepp-mode makefile-mode occam-mode python-mode python-ts-mode slim-mode yaml-mode yaml-ts-mode)` | Major modes in which to ignore activation | +| `snap-indent-format` | function or list | `nil` | Additional formatting to apply when indenting | +| `snap-indent-on-save` | boolean | `nil` | Whether to indent the entire buffer on save | +| `snap-indent-length-limit` | integer | `nil` | Maximum text length to indent | +| `snap-indent-skip-on-prefix-arg` | boolean | `nil` | Whether a prefix command argument causes indentation to be skipped | +| `snap-indent-skip-on-condition` | function | `nil` | Predicate function to cause indentation to be skipped | ### Additional formatting @@ -73,6 +74,6 @@ Each function must accept two arguments: the beginning and end positions of the ## License -Copyright © 2022 Jeff Valk +Copyright © 2023 Jeff Valk Distributed under the GNU General Public License, version 3 diff --git a/snap-indent-tests.el b/snap-indent-tests.el index dd31a0c..b7c2ea4 100644 --- a/snap-indent-tests.el +++ b/snap-indent-tests.el @@ -1,6 +1,6 @@ ;;; snap-indent-tests.el --- Snap-indent tests -*- lexical-binding: t; -*- -;; Copyright (C) 2022 Jeff Valk +;; Copyright (C) 2023 Jeff Valk ;; Author: Jeff Valk @@ -102,6 +102,57 @@ pp-text unindented-text))))))) +(ert-deftest snap-indent-maybe-indent-test () + "Test conditional indentation." + ;; The tests below rely on equivalence of indentation behavior between elisp + ;; major mode formatting and pretty printing. If these ever break suddenly, + ;; check this assumption. + (let* ((inhibit-message t) ; run tests quietly + (forms '(lorem ipsum dolor + (sit amet consectetur (adipiscing elit)) + (sed () do eiusmod + (tempor incididunt) (ut (labore (et (dolore ()))))) + (magna aliqua ut) + (((enim)) ad (minim) veniam) + (quis nostrud exercitation ullamco laboris nisi))) + (pp-text (pp-to-string forms)) + (unindented-text (replace-regexp-in-string "^ *" "" pp-text))) + ;; Skip when exceeding length limit + (with-temp-buffer + (emacs-lisp-mode) + (snap-indent-mode) + (insert unindented-text) + (let ((snap-indent-length-limit 10)) ; over limit + (snap-indent-maybe-indent (point-min) (point-max)) + (should (string-equal (buffer-string) unindented-text))) + (let ((snap-indent-length-limit 10000)) ; under limit + (snap-indent-maybe-indent (point-min) (point-max)) + (should (string-equal (buffer-string) pp-text)))) + ;; Skip when prefix arg is specified + (with-temp-buffer + (emacs-lisp-mode) + (snap-indent-mode) + (insert unindented-text) + (let ((snap-indent-skip-on-prefix-arg t) + (current-prefix-arg '(4))) ; prefixed + (snap-indent-maybe-indent (point-min) (point-max)) + (should (string-equal (buffer-string) unindented-text))) + (let ((snap-indent-skip-on-prefix-arg t) + (current-prefix-arg nil)) ; not prefixed + (snap-indent-maybe-indent (point-min) (point-max)) + (should (string-equal (buffer-string) pp-text)))) + ;; Skip according to user-defined predicate + (with-temp-buffer + (emacs-lisp-mode) + (snap-indent-mode) + (insert unindented-text) + (let ((snap-indent-skip-on-condition (lambda (_ _) t))) ; pred: t + (snap-indent-maybe-indent (point-min) (point-max)) + (should (string-equal (buffer-string) unindented-text))) + (let ((snap-indent-skip-on-condition (lambda (_ _) nil))) ; pred: nil + (snap-indent-maybe-indent (point-min) (point-max)) + (should (string-equal (buffer-string) pp-text)))))) + (provide 'snap-indent-tests) ;;; snap-indent-tests.el ends here diff --git a/snap-indent.el b/snap-indent.el index bfcf43c..aecacee 100644 --- a/snap-indent.el +++ b/snap-indent.el @@ -1,11 +1,11 @@ ;;; snap-indent.el --- Simple automatic indentation -*- lexical-binding: t; -*- -;; Copyright (C) 2022 Jeff Valk +;; Copyright (C) 2023 Jeff Valk ;; Author: Jeff Valk ;; URL: https://github.com/jeffvalk/snap-indent ;; Keywords: indent tools convenience -;; Version: 1.0 +;; Version: 1.1 ;; Package-Requires: ((emacs "24.1")) ;; This file is NOT part of GNU Emacs. @@ -89,6 +89,31 @@ tab/space conversion and `delete-trailing-whitespace'." (repeat :tag "List of functions" function)) :group 'snap-indent) +(defcustom snap-indent-length-limit nil + "Maximum text length to indent. +Set this to prevent any performance issues with large blocks of text. +When nil, no limit is applied." + :type 'integer + :group 'snap-indent) + +(defcustom snap-indent-skip-on-prefix-arg nil + "Whether a prefix command argument causes indentation to be skipped. +When non-nil, this lets you skip indentation for a single operation without +disabling `snap-indent-mode'." + :type 'boolean + :group 'snap-indent) + +(defcustom snap-indent-skip-on-condition nil + "Predicate function to cause indentation to be skipped. +When specified, this lets you skip indentation for a single operation without +disabling `snap-indent-mode' according to any logic you choose. + +The function must accept two arguments, which specify the start and end +positions of the region on which to (potentially) operate. The function should +return non-nil to skip indentation, and nil otherwise." + :type 'function + :group 'snap-indent) + ;; To make user configuration more expressive and less error-prone, ;; `snap-indent-format' may be either a function or a list of functions; if the ;; former, we'll wrap it in a list. Caveat when checking for this: lambdas are @@ -96,22 +121,6 @@ tab/space conversion and `delete-trailing-whitespace'." ;; returns the form itself.) Hence, to distinguish what should be wrapped, we ;; must test the value's function-ness not just its list-ness. -(defcustom snap-indent-yank-threshold nil - "Do not indent yanked text if its length exceeds the threshold. - -This can help prevent performance issues when yanking large -blocks of text. - -When nil, no threshold is applied." - :type 'number) - -(defcustom snap-indent-yank-skip-indent-with-prefix-arg nil - "Do not indent yanked text if yank was invoked with a prefix arg. - -This can be useful as it lets you skip indent for a single yank -operation without disabling `snap-indent-mode'." - :type 'boolean) - (defun snap-indent-as-list (function-or-list) "Return FUNCTION-OR-LIST as a list, treating lambda forms as atoms." (if (or (not (listp function-or-list)) (functionp function-or-list)) @@ -127,24 +136,25 @@ operation without disabling `snap-indent-mode'." (let ((end* (+ end (- (point-max) orig-max)))) ; account for prior changes (funcall format beg end*))))) +(defun snap-indent-maybe-indent (beg end) + "If the region between BEG and END should be indented, dispatch that action." + (unless (or (and snap-indent-length-limit + (> (- end beg) snap-indent-length-limit)) + (and snap-indent-skip-on-prefix-arg + current-prefix-arg) + (and snap-indent-skip-on-condition + (funcall snap-indent-skip-on-condition beg end))) + (snap-indent-indent beg end))) + (defun snap-indent-save-handler () "Indent buffer text on save as specified." (when snap-indent-on-save - (snap-indent-indent (point-min) (point-max)))) + (snap-indent-maybe-indent (point-min) (point-max)))) (defun snap-indent-command-handler () - "Indent region text on yank. - -When `snap-indent-yank-threshold' is not nil, do not trigger -snap-indent if the region length exceeds the threshold." - (when (and (memq this-command '(yank yank-pop)) - (or (not snap-indent-yank-skip-indent-with-prefix-arg) - (not current-prefix-arg))) - (let ((beg (region-beginning)) - (end (region-end))) - (when (or (not snap-indent-yank-threshold) - (<= (- end beg) snap-indent-yank-threshold)) - (snap-indent-indent beg end))))) + "Indent region text on yank." + (when (memq this-command '(yank yank-pop)) + (snap-indent-maybe-indent (region-beginning) (region-end)))) ;;;###Autoload (define-minor-mode snap-indent-mode