summaryrefslogtreecommitdiff
path: root/paredit.el
diff options
context:
space:
mode:
Diffstat (limited to 'paredit.el')
-rw-r--r--paredit.el302
1 files changed, 181 insertions, 121 deletions
diff --git a/paredit.el b/paredit.el
index 4e0f45f..13bf99b 100644
--- a/paredit.el
+++ b/paredit.el
@@ -1,7 +1,7 @@
;;; -*- mode: emacs-lisp -*-
;;;;;; paredit: Parenthesis editing minor mode
-;;;;;; Version 6
+;;;;;; Version 7
;;; Taylor Campbell wrote this code; he places it in the public domain.
@@ -20,15 +20,25 @@
;;; This is only lightly tested; some of it may not work as well as one
;;; might expect. Comments, in particular, are not handled with as
;;; much grace as I'd like, but I'm not sure quite yet how to handle
-;;; them as gracefully as I'd like. There is one small but deeply
-;;; fundamental problem in this model of pretending to be a structure
-;;; editor on top of what is really a text editor, though: escapes, in
-;;; character or string literals, which can throw off the parsing of
-;;; balanced delimiters. The only way I've come up to deal with this
-;;; with any semblance of grace is to insert only completed escape
-;;; characters, by rebinding backslash to query for the character to
-;;; escape, and for the rest of the code to assume only completed
-;;; escapes. This is a crock, but an unfortunately necessary one.
+;;; them as gracefully as I'd like.
+;;;
+;;; There is one small but deeply fundamental problem in this model of
+;;; pretending to be a structure editor on top of what is really a text
+;;; editor, though: escapes, in character or string literals, which can
+;;; throw off the parsing of balanced delimiters. The only way I've
+;;; come up to deal with this with any semblance of grace is to insert
+;;; only completed escape characters, by rebinding backslash to query
+;;; for the character to escape, and for the rest of the code to assume
+;;; only completed escapes. This is a kludge, but an unfortunately
+;;; necessary one.
+;;;
+;;; Even with this kludge, it's still not perfect. The code must
+;;; assume that all backslashes are involved in completed escapes, but
+;;; it's still possible to introduce an incomplete escape -- e.g., just
+;;; put the point after a backslash and insert any character. Or,
+;;; rather, don't do that. The rebound (, ), & " keys refuse to insert
+;;; themselves thus, but that's a crock, too. If you want to rewrite a
+;;; character literal, first delete it and then type backslash again.
;;;
;;; Automatic reindentation is performed as locally as possible, to
;;; ensure that Emacs does not interfere with custom indentation used
@@ -44,7 +54,7 @@
;;; This assumes Unix-style LF line endings.
-(defconst paredit-version 6)
+(defconst paredit-version 7)
(defvar paredit-mode-map
(let ((keymap (make-sparse-keymap)))
@@ -79,7 +89,7 @@
(define-key keymap (kbd "C-{") 'backward-barf-sexp)
keymap)
- "Keymap for paredit minor mode.")
+ "Keymap for the paredit minor mode.")
(define-minor-mode paredit-mode
"Minor mode for pseudo-structurally editing Lisp code."
@@ -92,82 +102,131 @@
(defun paredit-open-list ()
"Inserts a balanced parenthesis pair.
-If in string, comment, or character literal, inserts a single opening
-parenthesis."
+If in string or comment, inserts a single opening parenthesis.
+If in a character literal, does nothing. This prevents accidentally
+changing what was in the character literal to a meaningful delimiter
+unintentionally."
(interactive)
- (if (or (paredit-in-string-p)
- (paredit-in-comment-p)
- (paredit-in-char-p))
- (insert "(")
- (insert-parentheses 0)))
+ (cond ((or (paredit-in-string-p)
+ (paredit-in-comment-p))
+ (insert "("))
+ ((not (paredit-in-char-p))
+ (insert-parentheses 0))))
(defun paredit-close-list ()
"Moves past one closing parenthesis and reindents.
-If in a string, comment, or character literal, inserts a single closing
-parenthesis."
+If in a string or comment, inserts a single closing parenthesis.
+If in a character literal, does nothing. This prevents accidentally
+changing what was in the character literal to a meaningful delimiter
+unintentionally."
(interactive)
- (if (or (paredit-in-string-p)
- (paredit-in-comment-p)
- (paredit-in-char-p))
- (insert ")")
- (move-past-close-and-reindent)
- ;; Reindent not only the current line but, if there is a valid
- ;; S-expression following the point, that too.
- (condition-case nil (indent-sexp)
- (scan-error nil)))
- (if blink-matching-paren
- (condition-case nil
- (save-excursion
- (backward-sexp)
- (forward-sexp)
- (blink-matching-open))
- (error nil))))
+ (cond ((or (paredit-in-string-p)
+ (paredit-in-comment-p))
+ (insert ")"))
+ ((not (paredit-in-char-p))
+ (paredit-move-past-close-and-reindent)
+ (if blink-matching-paren
+ (condition-case nil
+ (save-excursion
+ (backward-sexp)
+ (forward-sexp)
+ (blink-matching-open))
+ (scan-error nil))))))
+
+(defun paredit-move-past-close-and-reindent ()
+ "Moves one character past the next closing parenthesis.
+Deletes extraneous whitespace before the closing parenthesis. Comments
+are not deleted, however; if there is a comment between the point and
+the next closing parenthesis, the closing parenthesis is moved to the
+line after the comment and indented appropriately."
+ (interactive)
+ (let ((orig (point)))
+ (up-list)
+ (if (catch 'exit ; This CATCH returns T if it
+ (while t ; should delete leading spaces
+ (save-excursion ; and NIL if not.
+ (let ((before-paren (1- (point))))
+ (back-to-indentation)
+ (cond ((not (eq (point) before-paren))
+ ;; Can't call PAREDIT-DELETE-LEADING-WHITESPACE
+ ;; here -- we must return from SAVE-EXCURSION
+ ;; first.
+ (throw 'exit t))
+ ((save-excursion (previous-line)
+ (end-of-line)
+ (paredit-in-comment-p))
+ ;; Moving the closing parenthesis any further
+ ;; would put it into a comment, so we just
+ ;; indent the closing parenthesis where it is
+ ;; and abort the loop, telling its continuation
+ ;; that no leading whitespace should be deleted.
+ (lisp-indent-line)
+ (throw 'exit nil))
+ (t (delete-indentation)))))))
+ (paredit-delete-leading-whitespace)))
+ (condition-case nil (indent-sexp)
+ (scan-error nil)))
+
+(defun paredit-delete-leading-whitespace ()
+ ;; This assumes that we're on the closing parenthesis already.
+ (save-excursion
+ (backward-char)
+ (while (let ((syn (char-syntax (char-before))))
+ (and (or (eq syn ?\ ) (eq syn ?-)) ; whitespace syntax
+ ;; The above line is a perfect example of why the
+ ;; following test is necessary.
+ (not (paredit-in-char-p (1- (point))))))
+ (backward-delete-char 1))))
(defun paredit-doublequote ()
"Inserts a pair of double-quotes.
Inside a comment, inserts a literal double-quote.
At the end of a string, moves past the closing double-quote.
-In the middle of a string, inserts a backslash-escaped double-quote."
+In the middle of a string, inserts a backslash-escaped double-quote.
+If in a character literal, does nothing. This prevents accidentally
+changing a what was in the character literal to a meaningful delimiter
+unintentionally."
(interactive)
(cond ((paredit-in-string-p)
(if (eq (cdr (paredit-string-start+end-points))
(point))
- (forward-char)
+ (forward-char) ; We're on the closing quote.
(insert ?\\ ?\" )))
((paredit-in-comment-p)
(insert ?\" ))
- ;; I'm not sure what to do about the character literal case.
((not (paredit-in-char-p))
(let ((insert-space
- (lambda (endp)
+ (lambda (endp delim-syn)
(if (and (not (if endp (eobp) (bobp)))
(memq (char-syntax
(if endp (char-after) (char-before)))
(list ?w ?_
(char-syntax ?\" )
- (char-syntax ?\( )
- (if endp ;++ sloppy
- nil
- (char-syntax ?\) )))))
+ delim-syn)))
(insert " ")))))
- (funcall insert-space nil)
+ (funcall insert-space nil ?\) )
(insert ?\" )
(save-excursion
(insert ?\" )
- (funcall insert-space t))))))
+ (funcall insert-space t ?\( ))))))
(defun paredit-backslash ()
"Inserts a backslash followed by a character to escape."
(interactive)
;; This funny conditional is necessary because PAREDIT-IN-COMMENT-P
;; assumes that PAREDIT-IN-STRING-P already returned false; otherwise
- ;; it may break.
+ ;; it may give erroneous answers.
(insert ?\\ )
(if (or (paredit-in-string-p)
(not (paredit-in-comment-p)))
(let ((delp t))
(unwind-protect (setq delp
(call-interactively #'paredit-escape))
+ ;; We need this in an UNWIND-PROTECT so that the backlash is
+ ;; left in there *only* if PAREDIT-ESCAPE return NIL normally
+ ;; -- in any other case, such as the user hitting C-g or an
+ ;; error occurring, we must delete the backslash to avoid
+ ;; leaving a dangling escape.
(if delp (backward-delete-char 1))))))
;;; This auxiliary interactive function returns true if the backslash
@@ -176,9 +235,9 @@ In the middle of a string, inserts a backslash-escaped double-quote."
(defun paredit-escape (char)
;; I'm too lazy to figure out how to do this without a separate
;; interactive function.
- (interactive "cCharacter to escape: ")
- (if (eq char 127) ; luser made a typo and deleted
- t
+ (interactive "cEscaping character...")
+ (if (eq char 127) ; The luser made a typo and hit
+ t ; DEL to delete the backslash.
(insert char)
nil))
@@ -186,13 +245,15 @@ In the middle of a string, inserts a backslash-escaped double-quote."
"Inserts a newline and indents 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."
+there is one.
+Moves forward one character first if on an escaped character."
(interactive)
(if (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.
+ ;; there's only a closing parenthesis after the point, not a full
+ ;; S-expression.
(condition-case nil (indent-sexp)
(scan-error nil)))
@@ -214,11 +275,11 @@ regard for delimiter balancing."
;++ into a comment and thereby invalidate the file's form,
;++ or move random text out of a comment.
(delete-char 1))
- ((eq (char-after) ?\\ ) ; Escape -- delete both chars.
- (delete-char 2))
- ((paredit-in-char-p) ; ditto
+ ((paredit-in-char-p) ; Escape -- delete both chars.
(backward-delete-char 1)
(delete-char 1))
+ ((eq (char-after) ?\\ ) ; ditto
+ (delete-char 2))
((or (eq (char-after) ?\( )
(eq (char-after) ?\" ))
(forward-char))
@@ -234,23 +295,27 @@ regard for delimiter balancing."
(delete-char 1)))))
(defun paredit-forward-delete-in-string ()
- (cond ((paredit-in-string-escape-p)
- (backward-delete-char 1)
- (delete-char 1))
- ((eq (char-after) ?\\ )
- (delete-char 2))
- (t
- (let ((start+end (paredit-string-start+end-points)))
- (cond ((not (eq (point) (cdr start+end)))
- ;; Delete the char if it's not the close-quote.
- (delete-char 1))
- ((eq (1- (point)) (car start+end))
- ;; If it is the close-quote, delete only if we're also
- ;; right past the open-quote (i.e. it's empty), and
- ;; then delete both quotes. Otherwise we refuse to
- ;; delete it.
- (backward-delete-char 1)
- (delete-char 1)))))))
+ (let ((start+end (paredit-string-start+end-points)))
+ (cond ((not (eq (point) (cdr start+end)))
+ ;; If it's not the close-quote, it's safe to delete. But
+ ;; first handle the case that we're in a string escape.
+ (cond ((paredit-in-string-escape-p)
+ ;; We're right after the backslash, so backward
+ ;; delete it before deleting the escaped character.
+ (backward-delete-char 1))
+ ((eq (char-after) ?\\ )
+ ;; If we're not in a string escape, but we are on a
+ ;; backslash, it must start the escape for the next
+ ;; character, so delete the backslash before deleting
+ ;; the next character.
+ (delete-char 1)))
+ (delete-char 1))
+ ((eq (1- (point)) (cdr start+end))
+ ;; If it is the close-quote, delete only if we're also right
+ ;; past the open-quote (i.e. it's empty), and then delete
+ ;; both quotes. Otherwise we refuse to delete it.
+ (backward-delete-char 1)
+ (delete-char 1)))))
(defun paredit-backward-delete (&optional arg)
"Deletes a character backward or moves backward over a delimiter.
@@ -262,7 +327,7 @@ With a prefix argument, simply deletes a character backward, without
regard for delimiter balancing."
(interactive "P")
(if arg
- (backward-delete-char 1)
+ (backward-delete-char 1) ;++ should this untabify?
(cond ((paredit-in-string-p)
(paredit-backward-delete-in-string))
((paredit-in-comment-p)
@@ -288,30 +353,27 @@ regard for delimiter balancing."
(backward-delete-char-untabify 1)))))
(defun paredit-backward-delete-in-string ()
- (cond ((paredit-in-string-escape-p)
- (backward-delete-char 1)
- (delete-char 1))
- ((and (not (eq (char-before) ?\"))
- ;; Delete a whole string escape -- but first make sure we
- ;; don't run backwards out the front end of the string.
- (save-excursion (backward-char)
- (paredit-in-string-escape-p)))
- (backward-delete-char 2))
- (t
- (let ((start+end (paredit-string-start+end-points)))
- (cond ((not (eq (1- (point)) (car start+end)))
- ;; Delete the char if it's not the open-quote.
- ;; Delete twice if it's an escaped character.
- (backward-delete-char 1)
- (if (paredit-in-string-escape-p)
- (backward-delete-char 1)))
- ((eq (point) (cdr start+end))
- ;; If it is the open-quote, delete only if we're also
- ;; right past the close-quote (i.e. it's empty), and
- ;; then delete both quotes. Otherwise we refuse to
- ;; delete it.
- (backward-delete-char 1)
- (delete-char 1)))))))
+ (let ((start+end (paredit-string-start+end-points)))
+ (cond ((not (eq (1- (point)) (car start+end)))
+ ;; If it's not the open-quote, it's safe to delete.
+ (if (paredit-in-string-escape-p)
+ ;; If we're on a string escape, since we're about to
+ ;; delete the backslash, we must first delete the
+ ;; escaped char.
+ (delete-char 1))
+ (backward-delete-char 1)
+ (if (paredit-in-string-escape-p)
+ ;; If, after deleting a character, we find ourselves in
+ ;; a string escape, we must have deleted the escaped
+ ;; character, and the backslash is behind the point, so
+ ;; backward delete it.
+ (backward-delete-char 1)))
+ ((eq (point) (cdr start+end))
+ ;; If it is the open-quote, delete only if we're also right
+ ;; past the close-quote (i.e. it's empty), and then delete
+ ;; both quotes. Otherwise we refuse to delete it.
+ (backward-delete-char 1)
+ (delete-char 1)))))
(defun paredit-kill ()
"Kills a line or S-expression.
@@ -329,8 +391,10 @@ closing string delimiter."
(eq (char-after) ?\; ))))
(if (eq (char-before (point-at-eol))
?\\ )
- ;++ This is a crock: we don't want to catch an incomplete
- ;++ escape sequence.
+ ;++ This is a crock: we don't want to kill an incomplete
+ ;++ escape sequence, so we include the newline. This
+ ;++ won't work on the last line of the buffer, however, if
+ ;++ it is not followed by one empty line.
(progn (kill-region (point) (1+ (point-at-eol)))
(insert ?\n ))
(kill-line)))
@@ -409,12 +473,6 @@ Automatically indents the newly wrapped S-expression."
;;; ----------------
;;; Slurpage & barfage
-;;; This shouldn't be here.
-
-(defmacro ignore-errors (&rest body)
- `(condition-case nil (progn ,@body)
- (error nil)))
-
(defun forward-slurp-sexp (&optional n)
"Adds the S-expression following the current list into that list by
moving the closing delimiter.
@@ -427,8 +485,9 @@ their new enclosing form."
(up-list) ; Up to the end of the list to
(let ((close (char-before))) ; save and delete the closing
(backward-delete-char 1) ; delimiter.
- (ignore-errors ; Go to the end of the last
- (paredit-forward-and-indent n)) ; S-expression,
+ (condition-case nil ; Go to the end of the last
+ (paredit-forward-and-indent n); S-expression,
+ (scan-error nil)) ; (ignoring going too far)
(insert close)))) ; to insert that delimiter.
(defun forward-barf-sexp (&optional n)
@@ -443,8 +502,10 @@ respect to their new enclosing form."
(up-list) ; Up to the end of the list to
(let ((close (char-before))) ; save and delete the closing
(backward-delete-char 1) ; delimiter.
- (ignore-errors (backward-sexp n)) ; Go back to where we want to
- (skip-chars-backward " \t\n") ; insert the delimiter.
+ (condition-case nil ; Go back to where we want to
+ (backward-sexp n) ; insert the delimiter.
+ (scan-error nil)) ; Ignore scan errors, and
+ (skip-chars-backward " \t\n") ; skip leading whitespace.
(if (bobp)
(message "Barfing all subexpressions with no open-paren?"))
(insert close))
@@ -463,7 +524,8 @@ were slurped."
(backward-up-list)
(let ((open (char-after)))
(delete-char 1)
- (ignore-errors (backward-sexp n))
+ (condition-case nil (backward-sexp n)
+ (scan-error nil))
(insert open))
;; Reindent the line at the beginning of wherever we inserted the
;; opening parenthesis, and then indent the whole S-expression.
@@ -483,7 +545,8 @@ from which they were barfed."
(backward-up-list)
(let ((open (char-after)))
(delete-char 1)
- (ignore-errors (paredit-forward-and-indent n))
+ (condition-case nil (paredit-forward-and-indent n)
+ (scan-error nil))
(skip-chars-forward " \t\n") ;++ should handle comments
(if (eobp)
(message "Barfing all subexpressions with no close-paren?"))
@@ -540,16 +603,13 @@ This assumes that `paredit-in-string-p' has already returned false."
(save-excursion
(let ((orig (point)) (res nil))
(goto-char (point-at-bol))
- ;; The third T argument to SEARCH-FORWARD says to return NIL,
+ ;; The second T argument to SEARCH-FORWARD says to return NIL,
;; not to signal an error, if no match is found.
- (setq res (search-forward ";" orig t))
- (while (and res
- (or (paredit-in-string-p)
- (prog2 (backward-char)
- (paredit-in-char-p)
- (forward-char))))
- (forward-char)
- (setq res (search-forward ";" orig t)))
+ (while (progn (setq res (search-forward ";" orig t))
+ (and res
+ (or (paredit-in-string-p)
+ (paredit-in-char-p (1- (point))))))
+ (forward-char))
(and res (<= res orig)))))
(defun paredit-in-char-p (&optional arg)