From http://lists.gnu.org/archive/html/emacs-devel/2013-07/msg00612.html > The motivating idea is this: When reading Lisp, I find I pay > attention to open parens (because foo is not (foo) is not ((foo))) > and just the close parens whose opener is on the same line. When a > sexp spans more than one line, I deduce the close paren from > indentation. If that's how we read Lisp, then why not edit Lisp that > way: change the indentation and let the close parens adjust > themselves to be consistent. > > This would create an editing experience somewhat like python-mode. > There are differences because lisp-mode knows a bit more due to > existing parens. I've been using these commands and find they are useful in practice. Is the feature suitable for core lisp-mode, or should I create a new ELPA package to provide the commands? I've made some updates to the code I posted before: • lisp-indent-adjust-parens potentially calls indent-for-tab-command, so as it can be suitable for binding to TAB • Implemented a prefix arg to specify levels of indentation to increase or decrease • Fixed a couple of edge case bugs Enable in a lisp-mode-hook like: (local-set-key (kbd "TAB") 'lisp-indent-adjust-parens) (local-set-key (kbd "") 'lisp-dedent-adjust-parens) Code: (require 'cl) (defun last-sexp-with-relative-depth (from-pos to-pos rel-depth) "Parsing sexps from FROM-POS (inclusive) to TO-POS (exclusive), return the position of the last sexp that had depth REL-DEPTH relative to FROM-POS. Returns nil if REL-DEPTH is not reached. Examples: Region: a (b c (d)) e (f g (h i)) j 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) Returns: position of (h i) This function assumes FROM-POS is not in a string or comment." (save-excursion (goto-char from-pos) (let (the-last-pos (parse-state '(0 nil nil nil nil nil nil nil nil))) (while (< (point) to-pos) (setq parse-state (parse-partial-sexp (point) to-pos nil t ; Stop before sexp parse-state)) (and (not (eq (point) to-pos)) (eq (car parse-state) rel-depth) (setq the-last-pos (point))) ;; The previous parse may not advance. To advance and maintain ;; correctness of depth, we parse over the next char. (setq parse-state (parse-partial-sexp (point) (1+ (point)) nil nil parse-state))) the-last-pos))) (defun adjust-close-paren-for-indent () "Adjust a close parentheses of a sexp so as lisp-indent-adjust-parens can indent that many levels. If a close paren was moved, returns a two element list of positions: where the close paren was moved from and the position following where 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) (backward-sexp) ;; Account for edge case when point has no sexp before it (if (bobp) 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)))))) (when deleted-paren-pos (let ((sexp-to-close (last-sexp-with-relative-depth (point) (progn (end-of-line) (point)) 0))) (when sexp-to-close (goto-char sexp-to-close) (forward-sexp)) ;; Note: when no sexp-to-close found, line is empty. So put ;; close paren after point. (insert ")") (list deleted-paren-pos (point))))))) (defun adjust-close-paren-for-dedent () "Adjust a close parentheses of a sexp so as lisp-dedent-adjust-parens can dedent that many levels. If a close paren was moved, returns a two element list of positions: where the close paren was moved from and the position following where 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))))) (when deleted-paren-pos (let ((sexp-to-close ;; Needs to work when dedenting in an empty list, in ;; which case backward-sexp will signal scan-error and ;; sexp-to-close will be nil. (condition-case nil (progn (backward-sexp) (point)) (scan-error nil)))) ;; Move point to where to insert close paren (if sexp-to-close (forward-sexp) (backward-up-list) (forward-char 1)) (insert ")") ;; The insertion makes deleted-paren-pos off by 1 (list (1+ deleted-paren-pos) (point))))))) (defun adjust-parens-p () "Whether to adjust parens." (save-excursion (let ((orig-pos (point))) (back-to-indentation) (and (not (use-region-p)) (<= orig-pos (point)))))) (defun adjust-parens-and-indent (adjust-function prefix-arg) "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 prefix-arg 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 prefix-arg) "Indent Lisp code to the next level while adjusting sexp balanced expressions to be consistent. 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 prefix-arg) (indent-for-tab-command prefix-arg))) (defun lisp-dedent-adjust-parens (&optional prefix-arg) "Dedent Lisp code to the previous level while adjusting sexp balanced expressions to be consistent. Binding to (ie Shift-Tab) is a sensible choice." (interactive "P") (when (adjust-parens-p) (adjust-parens-and-indent 'adjust-close-paren-for-dedent prefix-arg)))