summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--paredit.el348
1 files changed, 212 insertions, 136 deletions
diff --git a/paredit.el b/paredit.el
index e95ea6b..b8da727 100644
--- a/paredit.el
+++ b/paredit.el
@@ -1,9 +1,9 @@
;;; -*- Mode: Emacs-Lisp; outline-regexp: " \n;;;;+" -*-
;;;;;; Paredit: Parenthesis-Editing Minor Mode
-;;;;;; Version 18
+;;;;;; Version 19
-;;; This code is written by Taylor Campbell (except where explicitly
+;;; This code is written by Taylor R. Campbell (except where explicitly
;;; noted) and placed in the Public Domain. All warranties are
;;; disclaimed.
@@ -40,18 +40,33 @@
;;; than 21 or so.
;;; This mode changes the keybindings for a number of simple keys,
-;;; notably (, ), ", \, and ;. The round bracket keys are defined to
-;;; insert parenthesis pairs and move past the close, respectively;
-;;; the double-quote key is multiplexed to do both, and also insert an
-;;; escape if within a string; backslashes prompt the user for the
-;;; next character to input, because a lone backslash can break
-;;; structure inadvertently; and semicolons ensure that they do not
-;;; accidentally comment valid structure. (Use M-; to comment an
+;;; notably (, ), ", \, and ;. The bracket keys (round or square) are
+;;; defined to insert parenthesis pairs and move past the close,
+;;; respectively; the double-quote key is multiplexed to do both, and
+;;; also insert an escape if within a string; backslashes prompt the
+;;; user for the next character to input, because a lone backslash can
+;;; break structure inadvertently; and semicolons ensure that they do
+;;; not accidentally comment valid structure. (Use M-; to comment an
;;; expression.) These all have their ordinary behaviour when inside
;;; comments, and, outside comments, if truly necessary, you can insert
;;; them literally with C-q.
;;;
-;;; It also changes several standard editing keybindings including
+;;; These keybindings are set up for my preference. One particular
+;;; preference which I've seen vary greatly from person to person is
+;;; whether the command to move past a closing delimiter ought to
+;;; insert a newline. Since I find this behaviour to be more common
+;;; than that which inserts no newline, I have ) bound to it, and the
+;;; more involved M-) to perform the less common action. This bothers
+;;; some users, though, and they prefer the other way around. This
+;;; code, which you can use `eval-after-load' to put in your .emacs,
+;;; will exchange the bindings:
+;;;
+;;; (define-key paredit-mode-map (kbd ")")
+;;; 'paredit-close-parenthesis)
+;;; (define-key paredit-mode-map (kbd "M-)")
+;;; 'paredit-close-parenthesis-and-newline)
+;;;
+;;; Paredit also changes several standard editing keybindings including
;;; RET, C-j, C-d, DEL, & C-k. RET & C-j are transposed from their
;;; usual paired meaning, where RET inserts a newline and C-j fancily
;;; adds a new line with indentation &c., but I find the transposition
@@ -80,7 +95,7 @@
;;; This assumes Unix-style LF line endings.
-(defconst paredit-version 18)
+(defconst paredit-version 19)
;;;; Minor Mode Definition
@@ -136,18 +151,30 @@ Deprecated: use `paredit-mode' instead."
(progn (setq paredit-commands
`(
"Basic Insertion Commands"
- ("(" paredit-open-list
+ ("(" paredit-open-parenthesis
("(a b |c d)"
"(a b (|) c d)")
("(foo \"bar |baz\" quux)"
"(foo \"bar (|baz\" quux)"))
- (")" paredit-close-list-and-newline
+ (")" paredit-close-parenthesis-and-newline
("(defun f (x| ))"
- "(defun f (x)\n |)"))
- ("M-)" paredit-close-list
+ "(defun f (x)\n |)")
+ ("; (Foo.|"
+ "; (Foo.)|"))
+ ("M-)" paredit-close-parenthesis
("(a b |c )" "(a b c)|")
("; Hello,| world!"
"; Hello,)| world!"))
+ ("[" paredit-open-bracket
+ ("(a b |c d)"
+ "(a b [|] c d)")
+ ("(foo \"bar |baz\" quux)"
+ "(foo \"bar [baz\" quux)"))
+ ("]" paredit-close-bracket
+ ("(define-key keymap [frob| ] 'frobnicate)"
+ "(define-key keymap [frob]| 'frobnicate)")
+ ("; [Bar.|"
+ "; [Bar.]|"))
("\"" paredit-doublequote
("(frob grovel |full lexical)"
"(frob grovel \"|\" full lexical)")
@@ -443,45 +470,62 @@ Deprecated: use `paredit-mode' instead."
" </tr>")))))
(insert "</table>\n"))
-;;;; Basic Editing Commands
+;;;; Delimiter Insertion
-(defun paredit-open-list (&optional n)
- "Insert a balanced parenthesis pair.
-With a prefix argument N, put the closing parentheses after N
+(eval-when-compile
+ (defun paredit-name (&rest strings)
+ (intern (apply 'concat strings)))
+ (defmacro define-paredit-pair (open close name)
+ `(progn
+ (defun ,(paredit-name "paredit-open-" name) (&optional n)
+ ,(concat "Insert a balanced " name " pair.
+With a prefix argument N, put the closing " name " after N
S-expressions forward.
-If in string or comment, insert a single opening parenthesis.
-If in a character literal, do nothing. This prevents accidentally
- changing what was in the character literal to a meaningful delimiter
- unintentionally."
- (interactive "P")
- (cond ((or (paredit-in-string-p)
- (paredit-in-comment-p))
- (insert "("))
- ((not (paredit-in-char-p))
- (insert-parentheses (or n 0)))))
-
-(defun paredit-close-list ()
- "Move past one closing parenthesis and reindent.
-If in a string or comment, insert a single closing parenthesis.
-If in a character literal, do nothing. This prevents accidentally
- changing what was in the character literal to a meaningful delimiter
- unintentionally."
- (interactive)
+If the region is active, `transient-mark-mode' is enabled, and the
+ region's start and end fall in the same parenthesis depth, insert a
+ " name " pair around the region.
+If in a string or a comment, insert a single " name ".
+If in a character literal, do nothing. This prevents changing what was
+ in the character literal to a meaningful delimiter unintentionally.")
+ (interactive "P")
+ (cond ((or (paredit-in-string-p)
+ (paredit-in-comment-p))
+ (insert ,open))
+ ((not (paredit-in-char-p))
+ (paredit-insert-pair n ,open ,close 'goto-char))))
+ (defun ,(paredit-name "paredit-close-" name) ()
+ ,(concat "Move past one closing delimiter and reindent.
+\(Agnostic to the specific closing delimiter.)
+If in a string or comment, insert a single closing " name ".
+If in a character literal, do nothing. This prevents changing what was
+ in the character literal to a meaningful delimiter unintentionally.")
+ (interactive)
+ (paredit-move-past-close ,close))
+ (defun ,(paredit-name "paredit-close-" name "-and-newline") ()
+ ,(concat "Move past one closing delimiter, add a newline,"
+ " and reindent.
+If there was a margin comment after the closing delimiter, preserve it
+ on the same line.")
+ (interactive)
+ (paredit-move-past-close-and-newline ,close)))))
+
+(define-paredit-pair ?\( ?\) "parenthesis")
+(define-paredit-pair ?\[ ?\] "bracket")
+(define-paredit-pair ?\{ ?\} "brace")
+(define-paredit-pair ?\< ?\> "brocket")
+
+(defun paredit-move-past-close (close)
(cond ((or (paredit-in-string-p)
(paredit-in-comment-p))
- (insert ")"))
+ (insert close))
((not (paredit-in-char-p))
(paredit-move-past-close-and-reindent)
(paredit-blink-paren-match nil))))
-(defun paredit-close-list-and-newline ()
- "Move past one closing delimiter, add a newline, and reindent.
-If there was a margin comment after the closing delimiter, preserve
- the margin comment on the same line."
- (interactive)
+(defun paredit-move-past-close-and-newline (close)
(cond ((or (paredit-in-string-p)
(paredit-in-comment-p))
- (insert ")"))
+ (insert close))
(t (if (paredit-in-char-p) (forward-char))
(paredit-move-past-close-and-reindent)
(let ((comment.point (paredit-find-comment-on-line)))
@@ -519,13 +563,57 @@ If such a comment exists, delete the comment (including all leading
(cons comment (- start (point-at-bol))))))
(throw 'return nil))))))
+(defun paredit-insert-pair (n open close forward)
+ (let* ((regionp (and (paredit-region-active-p)
+ (paredit-region-safe-for-insert-p)))
+ (end (and regionp
+ (not n)
+ (prog1 (region-end)
+ (goto-char (region-beginning))))))
+ (if (or n regionp) (paredit-skip-whitespace t))
+ (let ((spacep (paredit-space-for-delimiter-p nil open)))
+ (if spacep (insert " "))
+ (insert open)
+ (save-excursion
+ ;; Move past the desired region.
+ (cond (n (funcall forward (save-excursion (forward-sexp n)
+ (point))))
+ (regionp (funcall forward (+ end (if spacep 2 1)))))
+ (insert close)
+ (if (paredit-space-for-delimiter-p t close)
+ (insert " "))))))
+
+(defun paredit-region-safe-for-insert-p ()
+ (save-excursion
+ (let ((beginning (region-beginning))
+ (end (region-end)))
+ (goto-char beginning)
+ (let* ((beginning-state (paredit-current-parse-state))
+ (end-state (parse-partial-sexp beginning end
+ nil nil beginning-state)))
+ (and (= (nth 0 beginning-state) ; 0. depth in parens
+ (nth 0 end-state))
+ (eq (nth 3 beginning-state) ; 3. non-nil if inside a
+ (nth 3 end-state)) ; string
+ (eq (nth 4 beginning-state) ; 4. comment status, yada
+ (nth 4 end-state))
+ (eq (nth 5 beginning-state) ; 5. t if following char
+ (nth 5 end-state))))))) ; quote
+
+(defun paredit-space-for-delimiter-p (endp delimiter)
+ ;; If at the buffer limit, don't insert a space. If there is a word,
+ ;; symbol, other quote, or non-matching parenthesis delimiter (i.e. a
+ ;; close when want an open the string or an open when we want to
+ ;; close the string), do insert a space.
+ (and (not (if endp (eobp) (bobp)))
+ (memq (char-syntax (if endp
+ (char-after)
+ (char-before)))
+ (list ?w ?_ ?\"
+ (let ((matching (matching-paren delimiter)))
+ (and matching (char-syntax matching)))))))
+
(defun paredit-move-past-close-and-reindent ()
- "Move one character past the next closing parenthesis.
-Delete extraneous whitespace before the closing parenthesis. Do not
- delete comments, however; if there is a comment between the point and
- the next closing parenthesis, move the closing parenthesis to the
- line after the comment and indent appropriately."
- (interactive)
(let ((orig (point)))
(up-list)
(if (catch 'return ; This CATCH returns T if it
@@ -577,6 +665,10 @@ Delete extraneous whitespace before the closing parenthesis. Do not
"Insert a pair of double-quotes.
With a prefix argument N, wrap the following N S-expressions in
double-quotes, escaping intermediate characters if necessary.
+If the region is active, `transient-mark-mode' is enabled, and the
+ region's start and end fall in the same parenthesis depth, insert a
+ pair of double-quotes around the region, again escaping intermediate
+ characters if necessary.
Inside a comment, insert a literal double-quote.
At the end of a string, move past the closing double-quote.
In the middle of a string, insert a backslash-escaped double-quote.
@@ -592,26 +684,13 @@ If in a character literal, do nothing. This prevents accidentally
((paredit-in-comment-p)
(insert ?\" ))
((not (paredit-in-char-p))
- (if n (paredit-skip-whitespace t))
- (let* ((end (and n (save-excursion (paredit-forward-for-quote
- (prefix-numeric-value n))
- (point))))
- (spacep (paredit-space-for-quote-p nil ?\) )))
- (if spacep (insert " "))
- (insert ?\" )
- (save-excursion
- ;; Move past the S-expressions we counted, if we were to
- ;; count them. Account for the quote and optionally the
- ;; space, which we just inserted.
- (if n (goto-char (+ end 1 (if spacep 1 0))))
- (insert ?\" )
- (if (paredit-space-for-quote-p t ?\( )
- (insert " ")))))))
+ (paredit-insert-pair n ?\" ?\" 'paredit-forward-for-quote))))
(defun paredit-meta-doublequote (&optional n)
"Move to the end of the string, insert a newline, and indent.
If not in a string, act as `paredit-doublequote'; if no prefix argument
- is specified, the default is to wrap one S-expression, however, not
+ is specified and the region is not active or `transient-mark-mode' is
+ disabled, the default is to wrap one S-expression, however, not
zero."
(interactive "p")
(if (not (paredit-in-string-p))
@@ -622,25 +701,13 @@ If not in a string, act as `paredit-doublequote'; if no prefix argument
(lisp-indent-line)
(condition-case () (indent-sexp)
(scan-error nil)))))
-
-(defun paredit-space-for-quote-p (endp delim-syn)
- ;; If at the buffer limit, don't insert a space. If there is a word,
- ;; symbol, other quote, or non-matching parenthesis delimiter (i.e. a
- ;; close when want an open the string or an open when we want to
- ;; close the string), do insert a space.
- (and (not (if endp (eobp) (bobp)))
- (memq (char-syntax (if endp
- (char-after)
- (char-before)))
- (list ?w ?_ ?\" delim-syn))))
-(defun paredit-forward-for-quote (n)
- (let ((end (save-excursion (forward-sexp n) (point)))
- (state (paredit-current-parse-state)))
+(defun paredit-forward-for-quote (end)
+ (let ((state (paredit-current-parse-state)))
(while (< (point) end)
(let ((new-state (parse-partial-sexp (point) (1+ (point))
nil nil state)))
- (if (not (paredit-in-string-p new-state))
+ (if (paredit-in-string-p new-state)
(if (not (paredit-in-string-escape-p))
(setq state new-state)
;; Escape character: turn it into an escaped escape
@@ -666,6 +733,8 @@ If not in a string, act as `paredit-doublequote'; if no prefix argument
;; Advance the end point for the same reason as above.
(setq end (1+ end)))))))
+;;;; Escape Insertion
+
(defun paredit-backslash ()
"Insert a backslash followed by a character to escape."
(interactive)
@@ -698,7 +767,29 @@ If not in a string, act as `paredit-doublequote'; if no prefix argument
(insert char) ; (Is there a better way to
nil)) ; express the rubout char?
; ?\^? works, but ugh...)
+
+;;; The placement of this function in this file is totally random.
+
+(defun paredit-newline ()
+ "Insert a newline and indent it.
+This is like `newline-and-indent', but it not only indents the line
+ that the point is on but also the S-expression following the point,
+ if there is one.
+Move forward one character first if on an escaped character.
+If in a string, just insert a literal newline."
+ (interactive)
+ (if (paredit-in-string-p)
+ (newline)
+ (if (and (not (paredit-in-comment-p)) (paredit-in-char-p))
+ (forward-char))
+ (newline-and-indent)
+ ;; Indent the following S-expression, but don't signal an error if
+ ;; there's only a closing parenthesis after the point.
+ (condition-case () (indent-sexp)
+ (scan-error nil))))
+;;;; Comment Insertion
+
(defun paredit-semicolon (&optional n)
"Insert a semicolon, moving any code after the point to a new line.
If in a string, comment, or character literal, insert just a literal
@@ -743,7 +834,7 @@ At the top level, where indentation is calculated to be at column 0,
semicolon margin comment at `comment-column'."
(interactive "*P")
(comment-normalize-vars)
- (cond ((and mark-active transient-mark-mode)
+ (cond ((paredit-region-active-p)
(comment-or-uncomment-region (region-beginning)
(region-end)
arg))
@@ -805,27 +896,9 @@ At the top level, where indentation is calculated to be at column 0,
(progn (indent-to comment-column
1) ; 1 -> force one leading space
(insert ?\; ))))))
-
-;;; The placement of this function in this file is totally random.
-
-(defun paredit-newline ()
- "Insert a newline and indent it.
-This is like `newline-and-indent', but it not only indents the line
- that the point is on but also the S-expression following the point,
- if there is one.
-Move forward one character first if on an escaped character.
-If in a string, just insert a literal newline."
- (interactive)
- (if (paredit-in-string-p)
- (newline)
- (if (and (not (paredit-in-comment-p)) (paredit-in-char-p))
- (forward-char))
- (newline-and-indent)
- ;; Indent the following S-expression, but don't signal an error if
- ;; there's only a closing parenthesis after the point.
- (condition-case () (indent-sexp)
- (scan-error nil))))
+;;;; Character Deletion
+
(defun paredit-forward-delete (&optional arg)
"Delete a character forward or move forward over a delimiter.
If on an opening S-expression delimiter, move forward into the
@@ -943,6 +1016,8 @@ With a prefix argument, simply delete a character backward, without
(backward-delete-char 1)
(delete-char 1)))))
+;;;; Killing
+
(defun paredit-kill (&optional arg)
"Kill a line as if with `kill-line', but respecting delimiters.
In a string, act exactly as `kill-line' but do not kill past the
@@ -984,14 +1059,14 @@ Otherwise, kill all S-expressions that start after the point."
(if (paredit-in-char-p) ; Move past the \ and prefix.
(backward-char 2)) ; (# in Scheme/CL, ? in elisp)
(let ((beginning (point))
- (eol (point-at-eol))
- (end-of-list-p (paredit-forward-sexps-to-kill)))
- ;; If we got to the end of the list and it's on the same line,
- ;; move backward past the closing delimiter before killing. (This
- ;; allows something like killing the whitespace in ( ).)
- (if end-of-list-p (progn (up-list) (backward-char)))
- (if kill-whole-line
- (paredit-kill-sexps-on-whole-line beginning)
+ (eol (point-at-eol)))
+ (let ((end-of-list-p (paredit-forward-sexps-to-kill beginning eol)))
+ ;; If we got to the end of the list and it's on the same line,
+ ;; move backward past the closing delimiter before killing. (This
+ ;; allows something like killing the whitespace in ( ).)
+ (if end-of-list-p (progn (up-list) (backward-char)))
+ (if kill-whole-line
+ (paredit-kill-sexps-on-whole-line beginning)
(kill-region beginning
;; If all of the S-expressions were on one line,
;; i.e. we're still on that line after moving past
@@ -1002,30 +1077,28 @@ Otherwise, kill all S-expressions that start after the point."
(if (and (not end-of-list-p)
(eq (point-at-eol) eol))
eol
- (point))))))
+ (point)))))))
-(defun paredit-forward-sexps-to-kill ()
- (let ((beginning (point))
- (eol (point-at-eol))
- (end-of-list-p nil))
+(defun paredit-forward-sexps-to-kill (beginning eol)
+ (let ((end-of-list-p nil))
;; Move to the end of the last S-expression that started on this
;; line, or to the closing delimiter if the last S-expression in
;; this list is on the line.
(catch 'return
- (while (and (not (eobp))
- (save-excursion
- (condition-case ()
- (forward-sexp)
- (scan-error
- (up-list)
- (setq end-of-list-p (eq (point-at-eol) eol))
- (throw 'return nil)))
- ;; We have to deal with a weird special case here
- ;; of kill
- (and (condition-case ()
- (progn (backward-sexp) t)
- (scan-error nil))
- (eq (point-at-eol) eol))))
+ (while t
+ (save-excursion
+ (condition-case ()
+ (forward-sexp)
+ (scan-error
+ (up-list)
+ (setq end-of-list-p (eq (point-at-eol) eol))
+ (throw 'return nil)))
+ (if (or (eobp)
+ (not (condition-case ()
+ (progn (backward-sexp) t)
+ (scan-error nil)))
+ (not (eq (point-at-eol) eol)))
+ (throw 'return nil)))
(forward-sexp)))
end-of-list-p))
@@ -1180,7 +1253,7 @@ With a prefix argument N, encompass all N S-expressions forward."
(goto-line (+ start-line (/ lines-on-sexps 2)))
(recenter)))))
-;;;; Wrappage, Splicage, & Raisage
+;;;; Depth-Changing Commands: Wrapping, Splicing, & Raising
(defun paredit-wrap-sexp (&optional n)
"Wrap the following S-expression in a list.
@@ -1188,17 +1261,16 @@ If a prefix argument N is given, wrap N S-expressions.
Automatically indent the newly wrapped S-expression.
As a special case, if the point is at the end of a list, simply insert
a pair of parentheses, rather than insert a lone opening parenthesis
- and then signal an error, in the interest of preserving structural
- validity."
+ and then signal an error, in the interest of preserving structure."
(interactive "p")
(condition-case ()
- (insert-parentheses (or n 1))
+ (paredit-insert-pair (or n 1) ?\( ?\) 'goto-char)
(scan-error (insert ?\) )
(backward-char)))
(save-excursion (backward-up-list) (indent-sexp)))
;;; Thanks to Marco Baringer for the suggestion of a prefix argument
-;;; for PAREDIT-SPLICE-SEXP. (I, Taylor Campbell, however, still
+;;; for PAREDIT-SPLICE-SEXP. (I, Taylor R. Campbell, however, still
;;; implemented it, in case any of you lawyer-folk get confused by the
;;; remark in the top of the file about explicitly noting code written
;;; by other people.)
@@ -1226,7 +1298,7 @@ With a numerical prefix argument N, kill N S-expressions backward in
(progn (backward-up-list) ; Reindent, now that the
(indent-sexp)) ; structure has changed.
(scan-error nil))))
-
+
(defun paredit-kill-surrounding-sexps-for-splice (arg)
(if (and arg (not (eq arg 0)))
(cond ((numberp arg)
@@ -1257,7 +1329,7 @@ With a numerical prefix argument N, kill N S-expressions backward in
(scan-error nil))
(kill-region beginning (point))))))
(t (error "Bizarre prefix argument: %s" arg)))))
-
+
(defun paredit-splice-sexp-killing-backward (&optional n)
"Splice the list the point is on by removing its delimiters, and
also kill all S-expressions before the point in the current list.
@@ -1501,6 +1573,10 @@ If TRAILING-P is nil, skip leading whitespace; otherwise, skip trailing
(funcall (if trailing-p #'skip-chars-forward #'skip-chars-backward)
" \t\n " ; This should skip using the syntax table, but LF
limit)) ; is a comment end, not newline, in Lisp mode.
+
+(defun paredit-region-active-p ()
+ "Return T if the region is active and NIL if not."
+ (and mark-active transient-mark-mode))
;;;;; S-expression Parsing Utilities