From a04d9b61614e56470799fa980a204bbb89f13961 Mon Sep 17 00:00:00 2001 From: Taylor R Campbell Date: Sun, 28 Sep 2008 13:39:03 +0000 Subject: Changes for version 14, introducing fancy comment handling. - paredit-close-list-and-newline now refuses to move a margin comment to another line; instead it will help to preserve the column of the comment. - The semicolon key is now bound to a command that will automatically move any code following the point onto the next line, so that you do not inadvertently comment out half expressions. You can still use M-; (comment-dwim) to comment out specific regions that are not meant to be code (e.g., old comments that were accidentally uncommented) or whole S-expressions, usually in conjunction with C-M-SPC (mark-sexp). darcs-hash:20080928133903-00fcc-0f07413273679feeca677ed9d8e5464876c7f772 --- paredit.el | 161 ++++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 128 insertions(+), 33 deletions(-) (limited to 'paredit.el') diff --git a/paredit.el b/paredit.el index 49bed1d..db47706 100644 --- a/paredit.el +++ b/paredit.el @@ -1,7 +1,7 @@ ;;; -*- mode: emacs-lisp -*- ;;;;;; paredit: Parenthesis editing minor mode -;;;;;; Version 13 +;;;;;; Version 14 ;;; This code is written by Taylor Campbell (except where explicitly ;;; noted) and placed in the Public Domain. All warranties are @@ -20,37 +20,33 @@ ;;; turn it off with M-x disable-paredit-mode. ;;; ;;; This is written for GNU Emacs. It is known not to work in XEmacs. -;;; The author wrote it with GNU Emacs 22.0.50, but it should work in -;;; earlier versions as well. An alternative set of keybindings is -;;; available in PAREDIT-TERMINAL-MODE that works in Emacs under Unix -;;; terminals with the -nw option (implied or otherwise). +;;; The author wrote it with GNU Emacs 22.0.50; it may work in +;;; slightly earlier versions, but not older than 21 or so. An +;;; alternative minor mode, PAREDIT-TERMINAL-MODE, is provided that +;;; works in Emacs under Unix terminals (i.e. `emacs -nw'). ;;; -;;; This mode changes the keybindings for (, ), and ", most notably; -;;; if you really, really want a literal one of those, use C-q. +;;; 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 insert comments in various +;;; ways, similar to COMMENT-DWIM, but with different DWIM meanings. +;;; (M-; as COMMENT-DWIM is still useful, and I still use it too, but +;;; for different purposes.) These all have their ordinary behaviour +;;; when inside comments, and, outside comments, if truly necessary, +;;; you can insert them literally with C-q. ;;; -;;; 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. (Block comments are not handled at -;;; all, only line comments with a ; (semicolon) prefix.) -;;; -;;; 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. +;;; It 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 +;;; more convenient. (You are free to change this, of course.) C-d, +;;; DEL, & C-k are instrumented to respect the S-expression structure. +;;; You can, however, pass a prefix argument to them to get their +;;; usual behaviour if necessary; e.g., C-u C-k will kill the whole +;;; line, regardless of what S-expression structure there is on it. ;;; ;;; Automatic reindentation is performed as locally as possible, to ;;; ensure that Emacs does not interfere with custom indentation used @@ -71,7 +67,7 @@ ;;; This assumes Unix-style LF line endings. -(defconst paredit-version 13) +(defconst paredit-version 14) @@ -90,6 +86,7 @@ (define-key keymap (kbd "M-\"") 'paredit-close-string-and-newline) (define-key keymap "\"" 'paredit-doublequote) (define-key keymap "\\" 'paredit-backslash) + (define-key keymap ";" 'paredit-semicolon) ;; This defies ordinary conventions, but I believe it is justified ;; and more convenient this way, to have RET be fancy and C-j @@ -250,19 +247,50 @@ unintentionally." (paredit-blink-paren-match nil)))) (defun paredit-close-list-and-newline () - "Moves past one closing delimiter, adds a newline, and reindents." + "Moves past one closing delimiter, adds a newline, and reindents. +If there was a margin comment after the closing delimiter, preserves +the margin comment on the same line." (interactive) (cond ((or (paredit-in-string-p) (paredit-in-comment-p)) (insert ")")) (t (if (paredit-in-char-p) (forward-char)) (paredit-move-past-close-and-reindent) - (newline) + (let ((comment.point (paredit-find-comment-on-line))) + (newline) + (if comment.point + (save-excursion + (forward-line -1) + (end-of-line) + (indent-to (cdr comment.point)) + (insert (car comment.point))))) (lisp-indent-line) (condition-case () (indent-sexp) (scan-error nil)) (paredit-blink-paren-match t)))) +(defun paredit-find-comment-on-line () + "Finds a margin comment on the current line. +If a comment exists, deletes the comment (including all leading +whitespace) and returns a cons whose car is the comment as a string +and whose cdr is the point of the comment's initial semicolon, +relative to the start of the line." + (save-excursion + (catch 'return + (while t + (if (search-forward ";" (point-at-eol) t) + (if (not (or (paredit-in-string-p) + (paredit-in-char-p))) + (let* ((start (progn (backward-char) ;before semicolon + (point))) + (comment (buffer-substring start + (point-at-eol)))) + (paredit-skip-whitespace nil (point-at-bol)) + (delete-region (point) (point-at-eol)) + (throw 'return + (cons comment (- start (point-at-bol)))))) + (throw 'return nil)))))) + (defun paredit-move-past-close-and-reindent () "Moves one character past the next closing parenthesis. Deletes extraneous whitespace before the closing parenthesis. Comments @@ -391,6 +419,73 @@ unintentionally." (insert char) ; (Is there a better way to nil)) ; express the rubout char? ; ?\^? works, but ugh...) +(defun paredit-semicolon (&optional arg) + "Insert a comment beginning, moving other items on the line. +If in a string, comment, or character literal, or with a prefix +argument, inserts just a literal semicolon." + (interactive "P") + (cond ((or (paredit-in-string-p) + (paredit-in-comment-p) + (paredit-in-char-p) + arg) + (insert ";")) + ;; At the beginning of a line, so try a top-level comment. + ((eq (point) (point-at-bol)) + (paredit-top-level-comment)) + ;; No more code on this line after the point. + ((save-excursion (paredit-skip-whitespace t (point-at-eol)) + (eq (point) (point-at-eol))) + (paredit-maybe-margin-comment)) + (t (paredit-code-comment)))) + +(defun paredit-top-level-comment () + (let ((indent (calculate-lisp-indent))) + (if (not (eq (- (point) (point-at-bol)) + (if (consp indent) + (car indent) + indent))) + ;; Just incorrectly indented, so we'll use a code + ;; comment anyway after indenting first, and then break + ;; the line. + (progn ;; If there's code ahead of here, adjust first. + (if (not (eq (point) (point-at-eol))) + (save-excursion (newline-and-indent))) + (lisp-indent-line) + (insert ";; ")) + ;; It is indeed a top-level comment, so insert three semicolons, + ;; after indenting code past the point on a new line if there is + ;; any. + (save-excursion + (if (progn (paredit-skip-whitespace t (point-at-eol)) + (not (eq (point) (point-at-eol)))) + (newline))) + (insert ";;; ")))) + +(defun paredit-maybe-margin-comment () + (if (save-excursion (paredit-skip-whitespace nil (point-at-bol)) + (eq (point) (point-at-bol))) + ;; No code at all on this line, so it's not a margin comment; + (progn (lisp-indent-line) ; indent it, + (insert (if (eq (point) (point-at-bol)) + ";;; " ; and insert the appropriate + ";; "))) ; comment prefix. + ;; There is code on this line, just not after the point, so it will + ;; be margin comment. + (indent-to comment-column) + (if (not (memq (char-syntax (char-before)) + '(?\ ?-))) + (insert " ")) + (insert "; "))) + +(defun paredit-code-comment () + ;; If we're ahead of some code on the line, first move to another. + (if (not (save-excursion (paredit-skip-whitespace nil (point-at-bol)) + (eq (point) (point-at-bol)))) + (newline)) + (save-excursion (newline-and-indent)) + (lisp-indent-line) + (insert ";; ")) + (defun paredit-newline () "Inserts a newline and indents it. This is like `newline-and-indent', but it not only indents the line -- cgit v1.2.1