From 9aebdd3d4c5f14a3b64958b6a573d72c898dd684 Mon Sep 17 00:00:00 2001 From: Jeff Valk Date: Sun, 25 Jun 2023 12:01:37 -0400 Subject: [PATCH] Generalize indentation skipping; add user-specified predicate This extends the ability to skip indentation based on text length and prefix argument to save calls as well as yank. It also gives the user full control of when to skip indenting via a customizable predicate function. --- README.md | 17 ++++++----- snap-indent-tests.el | 53 +++++++++++++++++++++++++++++++- snap-indent.el | 72 +++++++++++++++++++++++++------------------- 3 files changed, 102 insertions(+), 40 deletions(-) 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