]> code.delx.au - gnu-emacs-elpa/blobdiff - packages/adjust-parens/adjust-parens.el
Merge commit 'aecfbcbc10cf03ec94062ac3e590f8118e5a0434'
[gnu-emacs-elpa] / packages / adjust-parens / adjust-parens.el
index 003302d90cd321cbd8951357e77b2675e334215b..58a66323a1c9ab7322617b7c951e1cd2b3bc6928 100644 (file)
@@ -3,7 +3,7 @@
 ;; Copyright (C) 2013  Free Software Foundation, Inc.
 
 ;; Author: Barry O'Reilly <gundaetiapo@gmail.com>
-;; Version: 1.1
+;; Version: 3.0
 
 ;; This program is free software; you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
@@ -21,8 +21,8 @@
 ;;; Commentary:
 ;;
 ;; This package provides commands for indenting and dedenting Lisp
-;; code such that close parentheses are automatically adjusted to be
-;; consistent with the new level of indentation.
+;; code such that close parentheses and brackets are automatically
+;; adjusted to be consistent with the new level of indentation.
 ;;
 ;; When reading Lisp, the programmer pays attention to open parens and
 ;; the close parens on the same line. But when a sexp spans more than
@@ -36,6 +36,9 @@
 ;;
 ;; To use:
 ;;   (require 'adjust-parens)
+;;   (add-hook 'emacs-lisp-mode-hook #'adjust-parens-mode)
+;;   (add-hook 'clojure-mode-hook #'adjust-parens-mode)
+;;   ;; etc
 ;;
 ;; This binds two keys in Lisp Mode:
 ;;   (local-set-key (kbd "TAB") 'lisp-indent-adjust-parens)
 ;;   - Consider taking a region as input in order to indent a sexp and
 ;;     its siblings in the region. Dedenting would not take a region.
 
-(require 'cl)
+(require 'cl-lib)
 
 (defun last-sexp-with-relative-depth (from-pos to-pos rel-depth)
   "Parsing sexps from FROM-POS (inclusive) to TO-POS (exclusive),
@@ -116,7 +119,7 @@ Examples:
   Evaluate: (last-sexp-with-relative-depth pos-a (1+ pos-j) 0)
   Returns:  position of j
 
-  Evaluate: (last-sexp-with-relative-depth pos-a (1+ pos-j) -1)
+  Evaluate: (last-sexp-with-relative-depth pos-a (1+ pos-j) 1)
   Returns:  position of (h i)
 
 This function assumes FROM-POS is not in a string or comment."
@@ -167,22 +170,25 @@ it moved to.
 If there's no close parens to move, either return nil or allow
 scan-error to propogate up."
   (save-excursion
-    (let ((deleted-paren-pos
-           (save-excursion
-             (beginning-of-line)
-             ;; Account for edge case when point has no sexp before it
-             ;;
-             ;; This is primarily to avoid funny behavior when there
-             ;; is no sexp between bob and point.
-             (if (not (adjust-parens-check-prior-sexp))
-                 nil
-               ;; If the sexp at point is a list,
-               ;; delete its closing paren
-               (when (eq (scan-lists (point) 1 0)
-                         (scan-sexps (point) 1))
-                 (forward-sexp)
-                 (delete-char -1)
-                 (point))))))
+    (let* ((deleted-paren-char nil)
+           (deleted-paren-pos
+            (save-excursion
+              (beginning-of-line)
+              ;; Account for edge case when point has no sexp before it
+              ;;
+              ;; This is primarily to avoid funny behavior when there
+              ;; is no sexp between bob and point.
+              (if (not (adjust-parens-check-prior-sexp))
+                  nil
+                ;; If the sexp at point is a list,
+                ;; delete its closing paren
+                (when (eq (scan-lists (point) 1 0)
+                          (scan-sexps (point) 1))
+                  (forward-sexp)
+                  (setq deleted-paren-char (char-before))
+                  (delete-char -1)
+                  (point))))))
+      ;; Invariant: deleted-paren-pos nil iff deleted-paren-char nil
       (when deleted-paren-pos
         (let ((sexp-to-close
                (save-excursion
@@ -195,7 +201,7 @@ scan-error to propogate up."
             (forward-sexp))
           ;; Note: when no sexp-to-close found, line is empty. So put
           ;; close paren after point.
-          (insert ")")
+          (insert deleted-paren-char)
           (list deleted-paren-pos (point)))))))
 
 (defun adjust-close-paren-for-dedent ()
@@ -209,13 +215,16 @@ it moved to.
 If there's no close parens to move, either return nil or allow
 scan-error to propogate up."
   (save-excursion
-    (let ((deleted-paren-pos
-           (save-excursion
-             (when (< (point)
-                      (progn (up-list)
-                             (point)))
-               (delete-char -1)
-               (point)))))
+    (let* ((deleted-paren-char nil)
+           (deleted-paren-pos
+            (save-excursion
+              (when (< (point)
+                       (progn (up-list)
+                              (point)))
+                (setq deleted-paren-char (char-before))
+                (delete-char -1)
+                (point)))))
+      ;; Invariant: deleted-paren-pos nil iff deleted-paren-char nil
       (when deleted-paren-pos
         (let ((sexp-to-close
                ;; Needs to work when dedenting in an empty list, in
@@ -230,7 +239,7 @@ scan-error to propogate up."
               (forward-sexp)
             (backward-up-list)
             (forward-char 1))
-          (insert ")")
+          (insert deleted-paren-char)
           ;; The insertion makes deleted-paren-pos off by 1
           (list (1+ deleted-paren-pos)
                 (point)))))))
@@ -240,59 +249,103 @@ scan-error to propogate up."
   (save-excursion
     (let ((orig-pos (point)))
       (back-to-indentation)
-      (and (not (use-region-p))
-           (<= orig-pos (point))))))
-
-(defun adjust-parens-and-indent (adjust-function parg)
+      (and (= orig-pos (point))
+           (not (use-region-p))
+           ;; Current line indented?
+           (let ((indent (calculate-lisp-indent)))
+             (and indent
+                  (= (current-column)
+                     (if (listp indent)
+                         (car indent)
+                       indent))))))))
+
+(defun adjust-parens-and-indent (raw-parg
+                                 adjust-function
+                                 adjust-function-negative
+                                 fallback-function)
   "Adjust close parens and indent the region over which the parens
 moved."
-  (let ((region-of-change (list (point) (point))))
-    (cl-loop for i from 1 to (or parg 1)
-             with finished = nil
-             while (not finished)
-             do
-             (condition-case err
-                 (let ((close-paren-movement
-                        (funcall adjust-function)))
-                   (if close-paren-movement
-                       (setq region-of-change
-                             (list (min (car region-of-change)
-                                        (car close-paren-movement)
-                                        (cadr close-paren-movement))
-                                   (max (cadr region-of-change)
-                                        (car close-paren-movement)
-                                        (cadr close-paren-movement))))
-                     (setq finished t)))
-               (scan-error (setq finished err))))
-    (apply 'indent-region region-of-change))
-  (back-to-indentation))
-
-(defun lisp-indent-adjust-parens (&optional parg)
+  (if (adjust-parens-p)
+      (let* ((parg (prefix-numeric-value raw-parg))
+             (adjust-function (if (and parg (< parg 0))
+                                  adjust-function-negative
+                                adjust-function))
+             (region-of-change (list (point) (point))))
+        (cl-loop for i from 1 to (or (and parg (abs parg)) 1)
+                 with finished = nil
+                 while (not finished)
+                 do
+                 (condition-case err
+                     (let ((close-paren-movement
+                            (funcall adjust-function)))
+                       (if close-paren-movement
+                           (setq region-of-change
+                                 (list (min (car region-of-change)
+                                            (car close-paren-movement)
+                                            (cadr close-paren-movement))
+                                       (max (cadr region-of-change)
+                                            (car close-paren-movement)
+                                            (cadr close-paren-movement))))
+                         (setq finished t)))
+                   (scan-error (setq finished err))))
+        (apply 'indent-region region-of-change)
+        (back-to-indentation)
+        t)
+    (funcall fallback-function raw-parg)))
+
+(defcustom adjust-parens-fallback-indent-function 'indent-for-tab-command
+  "The function to call with prefix arg instead of
+adjust-parens-and-indent when adjust-parens-p returns false."
+  :type 'function
+  :group 'adjust-parens)
+(defun lisp-indent-adjust-parens (&optional raw-parg)
   "Indent Lisp code to the next level while adjusting sexp balanced
 expressions to be consistent.
 
+Returns t if adjust-parens changed the buffer, else returns the
+result of calling adjust-parens-fallback-indent-function.
+
 This command can be bound to TAB instead of indent-for-tab-command. It
 potentially calls the latter."
   (interactive "P")
-  (if (adjust-parens-p)
-      (adjust-parens-and-indent 'adjust-close-paren-for-indent
-                                parg)
-    (indent-for-tab-command parg)))
-
-(defun lisp-dedent-adjust-parens (&optional parg)
+  (adjust-parens-and-indent raw-parg
+                            #'adjust-close-paren-for-indent
+                            #'adjust-close-paren-for-dedent
+                            adjust-parens-fallback-indent-function))
+
+(defcustom adjust-parens-fallback-dedent-function 'indent-for-tab-command
+  "The function to call with prefix arg instead of
+adjust-parens-and-indent when adjust-parens-p returns false."
+  :type 'function
+  :group 'adjust-parens)
+(defun lisp-dedent-adjust-parens (&optional raw-parg)
   "Dedent Lisp code to the previous level while adjusting sexp
 balanced expressions to be consistent.
 
+Returns t if adjust-parens changed the buffer, else returns the
+result of calling adjust-parens-fallback-dedent-function.
+
 Binding to <backtab> (ie Shift-Tab) is a sensible choice."
   (interactive "P")
-  (when (adjust-parens-p)
-    (adjust-parens-and-indent 'adjust-close-paren-for-dedent
-                              parg)))
-
-(add-hook 'emacs-lisp-mode-hook
-          (lambda ()
-            (local-set-key (kbd "TAB") 'lisp-indent-adjust-parens)
-            (local-set-key (kbd "<backtab>") 'lisp-dedent-adjust-parens)))
+  (adjust-parens-and-indent raw-parg
+                            #'adjust-close-paren-for-dedent
+                            #'adjust-close-paren-for-indent
+                            adjust-parens-fallback-dedent-function))
+
+(defgroup adjust-parens nil
+  "Indent and dedent Lisp code, automatically adjust close parens."
+  :prefix "adjust-parens-"
+  :group 'convenience)
+
+(defvar adjust-parens-mode-map (make-sparse-keymap)
+  "Keymap for `adjust-parens-mode'")
+(define-key adjust-parens-mode-map (kbd "TAB") 'lisp-indent-adjust-parens)
+(define-key adjust-parens-mode-map (kbd "<backtab>") 'lisp-dedent-adjust-parens)
+
+(define-minor-mode adjust-parens-mode
+  "Indent and dedent Lisp code, automatically adjust close parens."
+  :group 'adjust-parens
+  :keymap adjust-parens-mode-map)
 
 (provide 'adjust-parens)