diff options
author | Taylor R Campbell <campbell@mumble.net> | 2008-09-28 13:11:27 +0000 |
---|---|---|
committer | Taylor R Campbell <campbell@mumble.net> | 2008-09-28 13:11:27 +0000 |
commit | 8be6d99412d0d4a313c8ce3925a78bd8d6cdb3fa (patch) | |
tree | 72b82d949ac3f406932ec0780ff908ee60af8425 |
Initial revision.v1
darcs-hash:20080928131127-00fcc-7af7487ee9835f4f8a88b30a93ef8fe43a60e59c
-rw-r--r-- | paredit.el | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/paredit.el b/paredit.el new file mode 100644 index 0000000..abc29e9 --- /dev/null +++ b/paredit.el @@ -0,0 +1,348 @@ +;;; -*- mode: emacs-lisp -*- + +;;;;;; paredit: Parenthesis editing minor mode +;;;;;; Version 1 + +;;; Taylor Campbell wrote this code; he places it in the public domain. + +;;; Add this to your .emacs after adding paredit.el to /path/to/elisp/: +;;; +;;; (add-to-list 'load-path "/path/to/elisp/") +;;; (require 'paredit) +;;; (add-hook '...-mode-hook (lambda () (paredit-mode 1))) +;;; +;;; Usually the ... will be lisp or scheme or both. + +;;; This assumes Unix-style LF line endings. + +(defconst paredit-version 1) + +(defvar paredit-mode-map + (let ((keymap (make-sparse-keymap))) + (define-key keymap "(" 'paredit-open-list) + (define-key keymap ")" 'paredit-close-list) + (define-key keymap "\"" 'paredit-doublequote) + (define-key keymap (kbd "M-\"") 'paredit-insert-doublequote) + + (define-key keymap (kbd "RET") 'newline-and-indent) + (define-key keymap (kbd "DEL") 'paredit-backspace) + + (define-key keymap (kbd "C-k") 'paredit-kill) + + ;; In case something broke and you really, really need to insert a + ;; literal parenthesis. Don't use these often. + (define-key keymap (kbd "C-M-(") + (lambda () (interactive) (insert "("))) + (define-key keymap (kbd "C-M-)") + (lambda () (interactive) (insert ")"))) + + ;; C-up & C-down are by default useless paragraph commands, while + ;; C-M-up & C-M-down are BACKWARD-UP-LIST & BACKWARD-DOWN-LIST. + (define-key keymap (kbd "<C-up>") 'up-list) + (define-key keymap (kbd "<C-down>") 'down-list) + + (define-key keymap (kbd "M-(") 'forward-wrap-sexp) + (define-key keymap (kbd "M-)") 'backward-wrap-sexp) + (define-key keymap (kbd "M-/") 'splice-sexp) + (define-key keymap (kbd "M-\\") 'join-sexps) + + (define-key keymap (kbd "C-)") 'forward-slurp-sexp) + (define-key keymap (kbd "C-}") 'forward-barf-sexp) + (define-key keymap (kbd "C-(") 'backward-slurp-sexp) + (define-key keymap (kbd "C-{") 'backward-barf-sexp) + + keymap) + "Keymap for paredit minor mode.") + +(define-minor-mode paredit-mode + "Minor mode for pseudo-structurally editing Lisp code." + :lighter " Paredit") + + + +;;; ---------------- +;;; Basic editing commands + +(defun paredit-open-list () + "Inserts a balanced parenthesis pair. +If in string, comment, or character literal, inserts a single opening +parenthesis." + (interactive) + (if (or (paredit-in-comment-p) + (paredit-in-string-p) + (eq (char-before) ?\\ )) + (insert "(") + (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." + (interactive) + (if (or (paredit-in-comment-p) + (paredit-in-string-p) + (eq (char-before) ?\\ )) + (insert ")") + (move-past-close-and-reindent))) + +(defun paredit-doublequote () + "Inserts a pair of double-quotes. +Inside a comment or character literal, inserts a literal double-quote. +Inside a string, moves to the end of the string." + (interactive) + (cond ((or (paredit-in-comment-p) + (eq (char-before) ?\\ )) + ;; Special case for when we're on character literals, just to + ;; be convenient. + (insert "\"")) + ((paredit-in-string-p) + (while (not (eq (char-after) ?\" )) + (forward-char) + (if (eq (char-after) ?\\ ) ; Skip escaped characters. + (forward-char))) + (forward-char)) + (t (insert-pair 0 ?\" ?\")))) + +(defun paredit-insert-doublequote (&optional n) + "Inserts a single literal double-quote. +Inside a string, inserts an escaped double-quote: \\\" +Outside of a string, comment, or character literal, displays a message +to the user and inserts a single literal double-quote nevertheless." + (interactive "p") + (let ((string + (cond ((or (paredit-in-comment-p) + (eq (char-before) ?\\ )) + "\"") ; plain doublequote + ((paredit-in-string-p) + "\\\"") ; escaped doublequote + (t (message "Inserting naked doublequote%s..." + (if (> n 1) "s" "")) + "\"")))) ; plain doublequote + (while (< 0 n) + (insert string) + (setq n (1- n))))) + +(defun paredit-backspace () + "Deletes a character backward or moves backward over a delimiter. +If at the start of an empty string literal, deletes the whole string, +including both delimiters. If at the start of a non-empty string +literal, moves back outside of the string literal. If anywhere else in +a string literal, deletes a single character. +If on a closing parenthesis, moves backward one character. +If on an opening parenthesis followed by a closing parenthesis, deletes +both parentheses. +If on any other opening parenthesis, does nothing. +Anywhere else, deletes a character backward." + (interactive) + (cond ((paredit-in-comment-p) + (backward-delete-char 1)) + ((paredit-in-string-p) + (if (and (eq (char-after) ?\" ) + (eq (char-before) ?\" ) + (not (eq (char-before (1- (point))) + ?\\ ))) + (progn (backward-char) + (kill-sexp)) + (backward-delete-char 1))) + ((and (or (eq (char-before) ?\) ) + (eq (char-before) ?\" )) + (not (eq (char-before (1- (point))) + ?\\ ))) + (backward-char)) + ;++ This should test more thoroughly, e.g. for ( ). + ((and (eq (char-before) ?\( ) + (eq (char-after) ?\) )) + (backward-char) + (kill-sexp)) + ;; Delete it, unless it's an opening parenthesis not preceded + ;; by a backslash (i.e. not a character literal). + ((or (not (eq (char-before) ?\( )) + (eq (char-before (1- (point))) + ?\\ )) + (backward-delete-char-untabify 1)))) + +(defun paredit-kill () + "Kills a line or S-expression. +If an S-expression starts on the same line as the point, kills that +S-expression; otherwise, behaves as `kill-line'." + (interactive) + (cond ((or (eq (char-after) ?\n ) + (paredit-in-comment-p) + (save-excursion + (skip-chars-forward " \t\n" (point-at-eol)) + (or (eq (point) (point-at-eol)) + (eq (char-after) ?\; )))) + (kill-line)) + ((paredit-in-string-p) + (if (eq (char-after) ?\n ) + ;; Delete the newline only if we're at the end of the + ;; line. (The ordinary Emacs behaviour is to do this also + ;; if there's only whitespace following, but I hate that + ;; behaviour.) + (delete-char) + (while (not (or (eq (char-after) ?\n ) + (eq (char-after) ?\" ))) + (cond ((eq (char-after) ?\\ ) + (delete-char) + ;; The one after the backslash is escaped, so eat + ;; it (most importantly if it's a doublequote), + ;; unless it's a newline. + (if (not (eq (char-after (1+ point)) + ?\n )) + (delete-char))) + (t (delete-char)))))) + (t (kill-sexp)))) + + + +;;; ---------------- +;;; Wrappage, splicage, & joinage + +(defun forward-wrap-sexp (&optional n) + "Wraps the following S-expression in a list. +If a prefix argument N is given, N S-expressions are contained in the +list." + (interactive "p") + (insert-parentheses (or n 1)) + (save-excursion (beginning-of-defun) + (indent-sexp))) + +(defun backward-wrap-sexp (&optional n) + "Wraps the preceding S-expression in a list. +If a prefix argument N is given, N S-expressions are contained in the +list." + (interactive "p") + (insert-parentheses (- (or n 1))) + (save-excursion (beginning-of-defun) + (indent-sexp))) + +(defun splice-sexp () + "Splices the list the point is on by removing its delimiters." + (interactive) + (save-excursion + (backward-up-list) ; Go up to the beginning... + (save-excursion + (forward-sexp) ; Go forward an expression, to + (backward-delete-char 1)) ; delete the end delimiter. + (delete-char 1) ; ...to delete the open char. + (beginning-of-defun) ; Reindent, now that the + (indent-sexp))) ; structure has changed. + +(defun join-sexps () + "Joins two adjacent S-expressions into one S-expression." + (interactive) + (save-excursion + (backward-sexp) ; Go to the end of the + (forward-sexp) ; preceding expression. + (backward-delete-char 1) ; Delete the closing delimiter. + (forward-sexp) ; Go to the start of the + (backward-sexp) ; following expression. + (delete-char 1) ; Delete the opening delimiter. + (beginning-of-defun) ; Reindent the whole defun, now + (indent-sexp))) ; that its structure changed. + + + +;;; ---------------- +;;; 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. +If a prefix argument N is given, N S-expressions are slurped into the +current list." + (interactive "p") + (save-excursion + (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 (forward-sexp n)) ; Go to the end of the last exp + (insert close)) ; to insert that delimiter. + (beginning-of-defun) ; Reindent the form, now that + (indent-sexp))) ; the structure has changed. + +(defun forward-barf-sexp (&optional n) + "Removes the last S-expression in the current list from that list by +moving the closing delimiter. +If a prefix argument N is given, the last N S-expressions are barfed +out of the current list." + (interactive "p") + (save-excursion + (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. + (if (eq (point) (point-min)) + (error "Barfing all subexpressions with no open-paren?") + (insert close))) + (beginning-of-defun) ; Reindent: structure has + (indent-sexp))) ; changed. + +(defun backward-slurp-sexp (&optional n) + "Adds the S-expression preceding the current list into that list by +moving the closing delimiter. +If a prefix argument N is given, N S-expressions are slurped into the +current list." + (interactive "p") + (save-excursion + (backward-up-list) + (let ((open (char-after))) + (delete-char 1) + (ignore-errors (backward-sexp n)) + (insert open)) + (beginning-of-defun) + (indent-sexp))) + +(defun backward-barf-sexp (&optional n) + "Removes the first S-expression in the current list from that list by +moving the closing delimiter. +If a prefix argument N is given, the first N S-expressions are barfed +out of the current list." + (interactive "p") + (save-excursion + (backward-up-list) + (let ((open (char-after))) + (delete-char 1) + (ignore-errors (forward-sexp n)) + (skip-chars-forward " \t\n") + (if (eq (point) (point-max)) + (error "Barfing all subexpressions with no close-paren?") + (insert open))) + (beginning-of-defun) + (indent-sexp))) + + + +;;; ---------------- +;;; Two utility functions + +(defun paredit-in-comment-p () + "True if the point is within a Lisp line comment." + ;++ Make this work on block comments? + (save-excursion + ;; The third T argument to SEARCH-BACKWARD says to return NIL, + ;; not to signal an error, if no match is found. + (and (search-backward ";" (point-at-bol) t) + (not (eq (char-before) ?\\ )) + t))) + +;;; Taken roughly from thingatpt.el. + +(defun paredit-in-string-p () + "True if the point is within a double-quote-delimited string." + (let ((orig (point))) + (save-excursion + (beginning-of-defun) + (eq (nth 3 (parse-partial-sexp (point) orig)) + ?\" )))) + + + +(provide 'paredit) |