|
|
- ;;; paredit.el --- minor mode for editing parentheses -*- Mode: Emacs-Lisp -*-
-
- ;; Copyright (C) 2005--2017 Taylor R. Campbell
-
- ;; Author: Taylor R. Campbell <campbell+paredit@mumble.net>
- ;; Version: 25beta
- ;; Package-Version: 20171127.205
- ;; Created: 2005-07-31
- ;; Keywords: lisp
-
- ;; NOTE: THIS IS A BETA VERSION OF PAREDIT. USE AT YOUR OWN RISK.
- ;; THIS FILE IS SUBJECT TO CHANGE, AND NOT SUITABLE FOR DISTRIBUTION
- ;; BY PACKAGE MANAGERS SUCH AS APT, PKGSRC, MACPORTS, &C.
-
- ;; Paredit is free software: you can redistribute it and/or modify it
- ;; under the terms of the GNU General Public License as published by
- ;; the Free Software Foundation, either version 3 of the License, or
- ;; (at your option) any later version.
- ;;
- ;; Paredit is distributed in the hope that it will be useful, but
- ;; WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;; GNU General Public License for more details.
- ;;
- ;; You should have received a copy of the GNU General Public License
- ;; along with paredit. If not, see <http://www.gnu.org/licenses/>.
-
- ;;; The currently released version of paredit is available at
- ;;; <https://mumble.net/~campbell/emacs/paredit.el>.
- ;;;
- ;;; The latest beta version of paredit is available at
- ;;; <https://mumble.net/~campbell/emacs/paredit-beta.el>.
- ;;;
- ;;; The Git repository for paredit is available at
- ;;; <https://mumble.net/~campbell/git/paredit.git>
- ;;;
- ;;; Release notes are available at
- ;;; <https://mumble.net/~campbell/emacs/paredit.release>.
- ;;; Install paredit by placing `paredit.el' in `/path/to/elisp', a
- ;;; directory of your choice, and adding to your .emacs file:
- ;;;
- ;;; (add-to-list 'load-path "/path/to/elisp")
- ;;; (autoload 'enable-paredit-mode "paredit"
- ;;; "Turn on pseudo-structural editing of Lisp code."
- ;;; t)
- ;;;
- ;;; Start Paredit Mode on the fly with `M-x enable-paredit-mode RET',
- ;;; or always enable it in a major mode `M' (e.g., `lisp') with:
- ;;;
- ;;; (add-hook 'M-mode-hook 'enable-paredit-mode)
- ;;;
- ;;; Customize paredit using `eval-after-load':
- ;;;
- ;;; (eval-after-load 'paredit
- ;;; '(progn
- ;;; (define-key paredit-mode-map (kbd "ESC M-A-C-s-)")
- ;;; 'paredit-dwim)))
- ;;;
- ;;; Send questions, bug reports, comments, feature suggestions, &c.,
- ;;; via email to the author's surname at mumble.net.
- ;;;
- ;;; Paredit should run in GNU Emacs 21 or later and XEmacs 21.5.28 or
- ;;; later.
- ;;; The paredit minor mode, Paredit Mode, binds common character keys,
- ;;; such as `(', `)', `"', and `\', to commands that carefully insert
- ;;; S-expression structures in the buffer:
- ;;;
- ;;; ( inserts `()', leaving the point in the middle;
- ;;; ) moves the point over the next closing delimiter;
- ;;; " inserts `""' if outside a string, or inserts an escaped
- ;;; double-quote if in the middle of a string, or moves over the
- ;;; closing double-quote if at the end of a string; and
- ;;; \ prompts for the character to escape, to avoid inserting lone
- ;;; backslashes that may break structure.
- ;;;
- ;;; In comments, these keys insert themselves. If necessary, you can
- ;;; insert these characters literally outside comments by pressing
- ;;; `C-q' before these keys, in case a mistake has broken the
- ;;; structure.
- ;;;
- ;;; These key bindings are designed so that when typing new code in
- ;;; Paredit Mode, you can generally type exactly the same sequence of
- ;;; keys you would have typed without Paredit Mode.
- ;;;
- ;;; Paredit Mode also binds common editing keys, such as `DEL', `C-d',
- ;;; and `C-k', to commands that respect S-expression structures in the
- ;;; buffer:
- ;;;
- ;;; DEL deletes the previous character, unless it is a delimiter: DEL
- ;;; will move the point backward over a closing delimiter, and
- ;;; will delete a delimiter pair together if between an open and
- ;;; closing delimiter;
- ;;;
- ;;; C-d deletes the next character in much the same manner; and
- ;;;
- ;;; C-k kills all S-expressions that begin anywhere between the point
- ;;; and the end of the line or the closing delimiter of the
- ;;; enclosing list, whichever is first.
- ;;;
- ;;; If necessary, you can delete a character, kill a line, &c.,
- ;;; irrespective of S-expression structure, by pressing `C-u' before
- ;;; these keys, in case a mistake has broken the structure.
- ;;;
- ;;; Finally, Paredit Mode binds some keys to complex S-expression
- ;;; editing operations. For example, `C-<right>' makes the enclosing
- ;;; list slurp up an S-expression to its right (here `|' denotes the
- ;;; point):
- ;;;
- ;;; (foo (bar | baz) quux) C-<right> (foo (bar | baz quux))
- ;;;
- ;;; Some paredit commands automatically reindent code. When they do,
- ;;; they try to indent as locally as possible, to avoid interfering
- ;;; with any indentation you might have manually written. Only the
- ;;; advanced S-expression manipulation commands automatically reindent,
- ;;; and only the forms that they immediately operated upon (and their
- ;;; subforms).
- ;;;
- ;;; This code is written for clarity, not efficiency. It frequently
- ;;; walks over S-expressions redundantly. If you have problems with
- ;;; the time it takes to execute some of the commands, let me know.
-
- ;;; This assumes Unix-style LF line endings.
-
- (defconst paredit-version 25)
- (defconst paredit-beta-p t)
- (eval-and-compile
-
- (defun paredit-xemacs-p ()
- ;; No idea where I got this definition from. Edward O'Connor
- ;; (hober in #emacs) suggested the current definition.
- ;; (and (boundp 'running-xemacs)
- ;; running-xemacs)
- (featurep 'xemacs))
-
- (defun paredit-gnu-emacs-p ()
- ;++ This could probably be improved.
- (not (paredit-xemacs-p)))
-
- (defmacro xcond (&rest clauses)
- "Exhaustive COND.
- Signal an error if no clause matches."
- `(cond ,@clauses
- (t (error "XCOND lost."))))
-
- (defalias 'paredit-warn (if (fboundp 'warn) 'warn 'message))
-
- (defvar paredit-sexp-error-type
- (with-temp-buffer
- (insert "(")
- (condition-case condition
- (backward-sexp)
- (error (if (eq (car condition) 'error)
- (paredit-warn "%s%s%s%s%s"
- "Paredit is unable to discriminate"
- " S-expression parse errors from"
- " other errors. "
- " This may cause obscure problems. "
- " Please upgrade Emacs."))
- (car condition)))))
-
- (defmacro paredit-handle-sexp-errors (body &rest handler)
- `(condition-case ()
- ,body
- (,paredit-sexp-error-type ,@handler)))
-
- (put 'paredit-handle-sexp-errors 'lisp-indent-function 1)
-
- (defmacro paredit-ignore-sexp-errors (&rest body)
- `(paredit-handle-sexp-errors (progn ,@body)
- nil))
-
- (put 'paredit-ignore-sexp-errors 'lisp-indent-function 0)
-
- (defmacro paredit-preserving-column (&rest body)
- "Evaluate BODY and restore point to former column, relative to code.
- Assumes BODY will change only indentation.
- If point was on code, it moves with the code.
- If point was on indentation, it stays in indentation."
- (let ((column (make-symbol "column"))
- (indentation (make-symbol "indentation")))
- `(let ((,column (current-column))
- (,indentation (paredit-current-indentation)))
- (let ((value (progn ,@body)))
- (paredit-restore-column ,column ,indentation)
- value))))
-
- (put 'paredit-preserving-column 'lisp-indent-function 0)
-
- nil)
- ;;;; Minor Mode Definition
-
- (defvar paredit-lighter " Paredit"
- "Mode line lighter Paredit Mode.")
-
- (defvar paredit-mode-map (make-sparse-keymap)
- "Keymap for the paredit minor mode.")
-
- (defvar paredit-override-check-parens-function
- (lambda (condition) condition nil)
- "Function to tell whether unbalanced text should inhibit Paredit Mode.")
-
- ;;;###autoload
- (define-minor-mode paredit-mode
- "Minor mode for pseudo-structurally editing Lisp code.
- With a prefix argument, enable Paredit Mode even if there are
- unbalanced parentheses in the buffer.
- Paredit behaves badly if parentheses are unbalanced, so exercise
- caution when forcing Paredit Mode to be enabled, and consider
- fixing unbalanced parentheses instead.
- \\<paredit-mode-map>"
- :lighter paredit-lighter
- ;; Setting `paredit-mode' to false here aborts enabling Paredit Mode.
- (if (and paredit-mode
- (not current-prefix-arg))
- (condition-case condition
- (check-parens)
- (error
- (if (not (funcall paredit-override-check-parens-function condition))
- (progn (setq paredit-mode nil)
- (signal (car condition) (cdr condition))))))))
-
- (defun paredit-override-check-parens-interactively (condition)
- (y-or-n-p (format "Enable Paredit Mode despite condition %S? " condition)))
-
- ;;;###autoload
- (defun enable-paredit-mode ()
- "Turn on pseudo-structural editing of Lisp code."
- (interactive)
- (paredit-mode +1))
-
- (defun disable-paredit-mode ()
- "Turn off pseudo-structural editing of Lisp code."
- (interactive)
- (paredit-mode -1))
-
- (defvar paredit-backward-delete-key
- (xcond ((paredit-xemacs-p) "BS")
- ((paredit-gnu-emacs-p) "DEL")))
-
- (defvar paredit-forward-delete-keys
- (xcond ((paredit-xemacs-p) '("DEL"))
- ((paredit-gnu-emacs-p) '("<delete>" "<deletechar>"))))
- ;;;; Paredit Keys
-
- ;;; Separating the definition and initialization of this variable
- ;;; simplifies the development of paredit, since re-evaluating DEFVAR
- ;;; forms doesn't actually do anything.
-
- (defvar paredit-commands nil
- "List of paredit commands with their keys and examples.")
-
- ;;; Each specifier is of the form:
- ;;; (key[s] function (example-input example-output) ...)
- ;;; where key[s] is either a single string suitable for passing to KBD
- ;;; or a list of such strings. Entries in this list may also just be
- ;;; strings, in which case they are headings for the next entries.
-
- (progn (setq paredit-commands
- `(
- "Basic Insertion Commands"
- ("(" paredit-open-round
- ("(a b |c d)"
- "(a b (|) c d)")
- ("(foo \"bar |baz\" quux)"
- "(foo \"bar (|baz\" quux)"))
- (")" paredit-close-round
- ("(a b |c )" "(a b c)|")
- ("; Hello,| world!"
- "; Hello,)| world!"))
- ("M-)" paredit-close-round-and-newline
- ("(defun f (x| ))"
- "(defun f (x)\n |)")
- ("; (Foo.|"
- "; (Foo.)|"))
- ("[" paredit-open-square
- ("(a b |c d)"
- "(a b [|] c d)")
- ("(foo \"bar |baz\" quux)"
- "(foo \"bar [|baz\" quux)"))
- ("]" paredit-close-square
- ("(define-key keymap [frob| ] 'frobnicate)"
- "(define-key keymap [frob]| 'frobnicate)")
- ("; [Bar.|"
- "; [Bar.]|"))
- ("\"" paredit-doublequote
- ("(frob grovel |full lexical)"
- "(frob grovel \"|\" full lexical)"
- "(frob grovel \"\"| full lexical)")
- ("(foo \"bar |baz\" quux)"
- "(foo \"bar \\\"|baz\" quux)")
- ("(frob grovel) ; full |lexical"
- "(frob grovel) ; full \"|lexical"))
- ("M-\"" paredit-meta-doublequote
- ("(foo \"bar |baz\" quux)"
- "(foo \"bar baz\"| quux)")
- ("(foo |(bar #\\x \"baz \\\\ quux\") zot)"
- ,(concat "(foo \"|(bar #\\\\x \\\"baz \\\\"
- "\\\\ quux\\\")\" zot)")))
- ("\\" paredit-backslash
- ("(string #|)\n ; Character to escape: x"
- "(string #\\x|)")
- ("\"foo|bar\"\n ; Character to escape: \""
- "\"foo\\\"|bar\""))
- (";" paredit-semicolon
- ("|(frob grovel)"
- ";|(frob grovel)")
- ("(frob |grovel)"
- "(frob ;|grovel\n )")
- ("(frob |grovel (bloit\n zargh))"
- "(frob ;|grovel\n (bloit\n zargh))")
- ("(frob grovel) |"
- "(frob grovel) ;|"))
- ("M-;" paredit-comment-dwim
- ("(foo |bar) ; baz"
- "(foo bar) ; |baz")
- ("(frob grovel)|"
- "(frob grovel) ;|")
- ("(zot (foo bar)\n|\n (baz quux))"
- "(zot (foo bar)\n ;; |\n (baz quux))")
- ("(zot (foo bar) |(baz quux))"
- "(zot (foo bar)\n ;; |\n (baz quux))")
- ("|(defun hello-world ...)"
- ";;; |\n(defun hello-world ...)"))
- ("C-j" paredit-newline
- ("(let ((n (frobbotz))) |(display (+ n 1)\nport))"
- ,(concat "(let ((n (frobbotz)))"
- "\n |(display (+ n 1)"
- "\n port))")))
-
- "Deleting & Killing"
- (("C-d" ,@paredit-forward-delete-keys)
- paredit-forward-delete
- ("(quu|x \"zot\")" "(quu| \"zot\")")
- ("(quux |\"zot\")"
- "(quux \"|zot\")"
- "(quux \"|ot\")")
- ("(foo (|) bar)" "(foo | bar)")
- ("|(foo bar)" "(|foo bar)"))
- (,paredit-backward-delete-key
- paredit-backward-delete
- ("(\"zot\" q|uux)" "(\"zot\" |uux)")
- ("(\"zot\"| quux)"
- "(\"zot|\" quux)"
- "(\"zo|\" quux)")
- ("(foo (|) bar)" "(foo | bar)")
- ("(foo bar)|" "(foo bar|)"))
- ("C-k" paredit-kill
- ("(foo bar)| ; Useless comment!"
- "(foo bar)|")
- ("(|foo bar) ; Useful comment!"
- "(|) ; Useful comment!")
- ("|(foo bar) ; Useless line!"
- "|")
- ("(foo \"|bar baz\"\n quux)"
- "(foo \"|\"\n quux)"))
- ("M-d" paredit-forward-kill-word
- ("|(foo bar) ; baz"
- "(| bar) ; baz"
- "(|) ; baz"
- "() ;|")
- (";;;| Frobnicate\n(defun frobnicate ...)"
- ";;;|\n(defun frobnicate ...)"
- ";;;\n(| frobnicate ...)"))
- (,(concat "M-" paredit-backward-delete-key)
- paredit-backward-kill-word
- ("(foo bar) ; baz\n(quux)|"
- "(foo bar) ; baz\n(|)"
- "(foo bar) ; |\n()"
- "(foo |) ; \n()"
- "(|) ; \n()"))
-
- "Movement & Navigation"
- ("C-M-f" paredit-forward
- ("(foo |(bar baz) quux)"
- "(foo (bar baz)| quux)")
- ("(foo (bar)|)"
- "(foo (bar))|"))
- ("C-M-b" paredit-backward
- ("(foo (bar baz)| quux)"
- "(foo |(bar baz) quux)")
- ("(|(foo) bar)"
- "|((foo) bar)"))
- ("C-M-u" paredit-backward-up)
- ("C-M-d" paredit-forward-down)
- ("C-M-p" paredit-backward-down) ; Built-in, these are FORWARD-
- ("C-M-n" paredit-forward-up) ; & BACKWARD-LIST, which have
- ; no need given C-M-f & C-M-b.
- "Depth-Changing Commands"
- ("M-(" paredit-wrap-round
- ("(foo |bar baz)"
- "(foo (|bar) baz)"))
- ("M-s" paredit-splice-sexp
- ("(foo (bar| baz) quux)"
- "(foo bar| baz quux)"))
- (("M-<up>" "ESC <up>")
- paredit-splice-sexp-killing-backward
- ("(foo (let ((x 5)) |(sqrt n)) bar)"
- "(foo |(sqrt n) bar)"))
- (("M-<down>" "ESC <down>")
- paredit-splice-sexp-killing-forward
- ("(a (b c| d e) f)"
- "(a b c| f)"))
- ("M-r" paredit-raise-sexp
- ("(dynamic-wind in (lambda () |body) out)"
- "(dynamic-wind in |body out)"
- "|body"))
- ("M-?" paredit-convolute-sexp
- ("(let ((x 5) (y 3)) (frob |(zwonk)) (wibblethwop))"
- "(frob |(let ((x 5) (y 3)) (zwonk) (wibblethwop)))"))
-
- "Barfage & Slurpage"
- (("C-)" "C-<right>")
- paredit-forward-slurp-sexp
- ("(foo (bar |baz) quux zot)"
- "(foo (bar |baz quux) zot)")
- ("(a b ((c| d)) e f)"
- "(a b ((c| d) e) f)"))
- (("C-}" "C-<left>")
- paredit-forward-barf-sexp
- ("(foo (bar |baz quux) zot)"
- "(foo (bar |baz) quux zot)"))
- (("C-(" "C-M-<left>" "ESC C-<left>")
- paredit-backward-slurp-sexp
- ("(foo bar (baz| quux) zot)"
- "(foo (bar baz| quux) zot)")
- ("(a b ((c| d)) e f)"
- "(a (b (c| d)) e f)"))
- (("C-{" "C-M-<right>" "ESC C-<right>")
- paredit-backward-barf-sexp
- ("(foo (bar baz |quux) zot)"
- "(foo bar (baz |quux) zot)"))
-
- "Miscellaneous Commands"
- ("M-S" paredit-split-sexp
- ("(hello| world)"
- "(hello)| (world)")
- ("\"Hello, |world!\""
- "\"Hello, \"| \"world!\""))
- ("M-J" paredit-join-sexps
- ("(hello)| (world)"
- "(hello| world)")
- ("\"Hello, \"| \"world!\""
- "\"Hello, |world!\"")
- ("hello-\n| world"
- "hello-|world"))
- ("C-c C-M-l" paredit-recenter-on-sexp)
- ("M-q" paredit-reindent-defun)
- ))
- nil) ; end of PROGN
- ;;;;; Command Examples
-
- (eval-and-compile
- (defmacro paredit-do-commands (vars string-case &rest body)
- (let ((spec (nth 0 vars))
- (keys (nth 1 vars))
- (fn (nth 2 vars))
- (examples (nth 3 vars)))
- `(dolist (,spec paredit-commands)
- (if (stringp ,spec)
- ,string-case
- (let ((,keys (let ((k (car ,spec)))
- (cond ((stringp k) (list k))
- ((listp k) k)
- (t (error "Invalid paredit command %s."
- ,spec)))))
- (,fn (cadr ,spec))
- (,examples (cddr ,spec)))
- ,@body)))))
-
- (put 'paredit-do-commands 'lisp-indent-function 2))
-
- (defun paredit-define-keys ()
- (paredit-do-commands (spec keys fn examples)
- nil ; string case
- (dolist (key keys)
- (define-key paredit-mode-map (read-kbd-macro key) fn))))
-
- (defun paredit-function-documentation (fn)
- (let ((original-doc (get fn 'paredit-original-documentation))
- (doc (documentation fn 'function-documentation)))
- (or original-doc
- (progn (put fn 'paredit-original-documentation doc)
- doc))))
-
- (defun paredit-annotate-mode-with-examples ()
- (let ((contents
- (list (paredit-function-documentation 'paredit-mode))))
- (paredit-do-commands (spec keys fn examples)
- (push (concat "\n\n" spec "\n")
- contents)
- (let ((name (symbol-name fn)))
- (if (string-match (symbol-name 'paredit-) name)
- (push (concat "\n\n\\[" name "]\t" name
- (if examples
- (mapconcat (lambda (example)
- (concat
- "\n"
- (mapconcat 'identity
- example
- "\n --->\n")
- "\n"))
- examples
- "")
- "\n (no examples)\n"))
- contents))))
- (put 'paredit-mode 'function-documentation
- (apply 'concat (reverse contents))))
- ;; PUT returns the huge string we just constructed, which we don't
- ;; want it to return.
- nil)
-
- (defun paredit-annotate-functions-with-examples ()
- (paredit-do-commands (spec keys fn examples)
- nil ; string case
- (put fn 'function-documentation
- (concat (paredit-function-documentation fn)
- "\n\n\\<paredit-mode-map>\\[" (symbol-name fn) "]\n"
- (mapconcat (lambda (example)
- (concat "\n"
- (mapconcat 'identity
- example
- "\n ->\n")
- "\n"))
- examples
- "")))))
- ;;;;; HTML Examples
-
- (defun paredit-insert-html-examples ()
- "Insert HTML for a paredit quick reference table."
- (interactive)
- (let ((insert-lines
- (lambda (&rest lines) (dolist (line lines) (insert line) (newline))))
- (initp nil))
- (paredit-do-commands (spec keys fn examples)
- (progn (if initp
- (funcall insert-lines "</table>")
- (setq initp t))
- (funcall insert-lines (concat "<h3>" spec "</h3>"))
- (funcall insert-lines "<table>"))
- (let ((name (symbol-name fn))
- (keys
- (mapconcat (lambda (key)
- (concat "<tt>" (paredit-html-quote key) "</tt>"))
- keys
- ", ")))
- (funcall insert-lines "<tr>")
- (funcall insert-lines (concat " <th align=\"left\">" keys "</th>"))
- (funcall insert-lines (concat " <th align=\"left\">" name "</th>"))
- (funcall insert-lines "</tr>")
- (funcall insert-lines
- "<tr><td colspan=\"2\"><table cellpadding=\"5\"><tr>")
- (dolist (example examples)
- (let ((prefix "<td><table border=\"1\"><tr><td><table><tr><td><pre>")
- (examples
- (mapconcat 'paredit-html-quote
- example
- (concat "</pre></td></tr>"
- "<tr><th>↓</th></tr>"
- "<tr><td><pre>")))
- (suffix "</pre></td></tr></table></td></tr></table></td>"))
- (funcall insert-lines (concat prefix examples suffix))))
- (funcall insert-lines "</tr></table></td></tr>")))
- (funcall insert-lines "</table>")))
-
- (defun paredit-html-quote (string)
- (with-temp-buffer
- (dotimes (i (length string))
- (insert (let ((c (elt string i)))
- (cond ((eq c ?\<) "<")
- ((eq c ?\>) ">")
- ((eq c ?\&) "&")
- ((eq c ?\') "'")
- ((eq c ?\") """)
- (t c)))))
- (buffer-string)))
- ;;;; Delimiter Insertion
-
- (eval-and-compile
- (defun paredit-conc-name (&rest strings)
- (intern (apply 'concat strings)))
-
- (defmacro define-paredit-pair (open close name)
- `(progn
- (defun ,(paredit-conc-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 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)
- (save-excursion (backward-up-list) (indent-sexp)))))
- (defun ,(paredit-conc-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-conc-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))
- (defun ,(paredit-conc-name "paredit-wrap-" name)
- (&optional argument)
- ,(concat "Wrap the following S-expression.
- See `paredit-wrap-sexp' for more details.")
- (interactive "P")
- (paredit-wrap-sexp argument ,open ,close))
- (add-to-list 'paredit-wrap-commands
- ',(paredit-conc-name "paredit-wrap-" name)))))
-
- (defvar paredit-wrap-commands '(paredit-wrap-sexp)
- "List of paredit commands that wrap S-expressions.
- Used by `paredit-yank-pop'; for internal paredit use only.")
-
- (define-paredit-pair ?\( ?\) "round")
- (define-paredit-pair ?\[ ?\] "square")
- (define-paredit-pair ?\{ ?\} "curly")
- (define-paredit-pair ?\< ?\> "angled")
-
- ;;; Aliases for the old names.
-
- (defalias 'paredit-open-parenthesis 'paredit-open-round)
- (defalias 'paredit-close-parenthesis 'paredit-close-round)
- (defalias 'paredit-close-parenthesis-and-newline
- 'paredit-close-round-and-newline)
-
- (defalias 'paredit-open-bracket 'paredit-open-square)
- (defalias 'paredit-close-bracket 'paredit-close-square)
- (defalias 'paredit-close-bracket-and-newline
- 'paredit-close-square-and-newline)
- (defun paredit-move-past-close (close)
- (paredit-move-past-close-and close
- (lambda ()
- (paredit-blink-paren-match nil))))
-
- (defun paredit-move-past-close-and-newline (close)
- (paredit-move-past-close-and close
- (lambda ()
- (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)
- (paredit-ignore-sexp-errors (indent-sexp))
- (paredit-blink-paren-match t))))
-
- (defun paredit-move-past-close-and (close if-moved)
- (if (or (paredit-in-string-p)
- (paredit-in-comment-p))
- (insert close)
- (if (paredit-in-char-p) (forward-char))
- (paredit-move-past-close-and-reindent close)
- (funcall if-moved)))
-
- (defun paredit-find-comment-on-line ()
- "Find a margin comment on the current line.
- Return nil if there is no such comment or if there is anything but
- whitespace until such a comment.
- If such a comment exists, delete the comment (including all leading
- whitespace) and return 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
- (paredit-skip-whitespace t (point-at-eol))
- (and (eq ?\; (char-after))
- (not (eq ?\; (char-after (1+ (point)))))
- (not (or (paredit-in-string-p)
- (paredit-in-char-p)))
- (let* ((start ;Move to before the semicolon.
- (progn (backward-char) (point)))
- (comment
- (buffer-substring start (point-at-eol))))
- (paredit-skip-whitespace nil (point-at-bol))
- (delete-region (point) (point-at-eol))
- (cons comment (- start (point-at-bol)))))))
- (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))))))
- (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
- (paredit-scan-sexps-hack (point)
- (prefix-numeric-value n))))
- (regionp
- (funcall forward (+ end (if spacep 2 1)))))
- ;; The string case can happen if we are inserting string
- ;; delimiters. The comment case may happen by moving to the
- ;; end of a buffer that has a comment with no trailing newline.
- (if (and (not (paredit-in-string-p))
- (paredit-in-comment-p))
- (newline))
- (insert close)
- (if (paredit-space-for-delimiter-p t close)
- (insert " "))))))
-
- ;++ This needs a better name...
-
- (defun paredit-scan-sexps-hack (point n)
- (save-excursion
- (goto-char point)
- (let ((direction (if (< 0 n) +1 -1))
- (magnitude (abs n))
- (count 0))
- (catch 'exit
- (while (< count magnitude)
- (let ((p
- (paredit-handle-sexp-errors (scan-sexps (point) direction)
- nil)))
- (if (not p) (throw 'exit nil))
- (goto-char p))
- (setq count (+ count 1)))))
- (point)))
- (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
-
- (defvar paredit-space-for-delimiter-predicates nil
- "List of predicates for whether to put space by delimiter at point.
- Each predicate is a function that is is applied to two arguments, ENDP
- and DELIMITER, and that returns a boolean saying whether to put a
- space next to the delimiter -- before/after the delimiter if ENDP is
- false/true, respectively.
- If any predicate returns false, no space is inserted: every predicate
- has veto power.
- Each predicate may assume that the point is not at the beginning/end of
- the buffer, and that the point is preceded/followed by a word
- constituent, symbol constituent, string quote, or delimiter matching
- DELIMITER, if ENDP is false/true, respectively.
- Each predicate should examine only text before/after the point if ENDP is
- false/true, respectively.")
-
- (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)))
- (and (not endp)
- (eq ?\" (char-syntax delimiter))
- ?\) )))
- (catch 'exit
- (dolist (predicate paredit-space-for-delimiter-predicates)
- (if (not (funcall predicate endp delimiter))
- (throw 'exit nil)))
- t)))
- (defun paredit-move-past-close-and-reindent (close)
- (let ((open (paredit-missing-close)))
- (if open
- (if (eq close (matching-paren open))
- (save-excursion
- (message "Missing closing delimiter: %c" close)
- (insert close))
- (error "Mismatched missing closing delimiter: %c ... %c"
- open close))))
- (up-list)
- (if (catch 'return ; 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 'return t))
- ((save-excursion (forward-line -1)
- (end-of-line)
- (paredit-in-comment-p))
- ;; Moving the closing delimiter any further
- ;; would put it into a comment, so we just
- ;; indent the closing delimiter where it is and
- ;; abort the loop, telling its continuation that
- ;; no leading whitespace should be deleted.
- (lisp-indent-line)
- (throw 'return nil))
- (t (delete-indentation)))))))
- (paredit-delete-leading-whitespace)))
-
- (defun paredit-missing-close ()
- (save-excursion
- (paredit-handle-sexp-errors (backward-up-list)
- (error "Not inside a list."))
- (let ((open (char-after)))
- (paredit-handle-sexp-errors (progn (forward-sexp) nil)
- open))))
-
- (defun paredit-delete-leading-whitespace ()
- ;; This assumes that we're on the closing delimiter 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))))))
- (delete-char -1))))
-
- (defun paredit-blink-paren-match (another-line-p)
- (if (and blink-matching-paren
- (or (not show-paren-mode) another-line-p))
- (paredit-ignore-sexp-errors
- (save-excursion
- (backward-sexp)
- (forward-sexp)
- ;; SHOW-PAREN-MODE inhibits any blinking, so we disable it
- ;; locally here.
- (let ((show-paren-mode nil))
- (blink-matching-open))))))
- (defun paredit-doublequote (&optional n)
- "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.
- If in a character literal, do nothing. This prevents accidentally
- changing a what was in the character literal to become a meaningful
- delimiter unintentionally."
- (interactive "P")
- (cond ((paredit-in-string-p)
- (if (eq (point) (- (paredit-enclosing-string-end) 1))
- (forward-char) ; Just move past the closing quote.
- ;; Don't split a \x into an escaped backslash and a string end.
- (if (paredit-in-string-escape-p) (forward-char))
- (insert ?\\ ?\" )))
- ((paredit-in-comment-p)
- (insert ?\" ))
- ((not (paredit-in-char-p))
- (paredit-insert-pair n ?\" ?\" 'paredit-forward-for-quote))))
-
- (defun paredit-meta-doublequote (&optional n)
- "Move to the end of the string.
- If not in a string, act as `paredit-doublequote'; if not prefix argument
- 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))
- (paredit-doublequote (or n (and (not (paredit-region-active-p)) 1)))
- (goto-char (paredit-enclosing-string-end))))
-
- (defun paredit-meta-doublequote-and-newline (&optional n)
- "Move to the end of the string, insert a newline, and indent.
- If not in a string, act as `paredit-doublequote'; if not prefix argument
- 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))
- (paredit-doublequote (or n (and (not (paredit-region-active-p)) 1)))
- (progn (goto-char (paredit-enclosing-string-end))
- (newline)
- (lisp-indent-line)
- (paredit-ignore-sexp-errors (indent-sexp)))))
-
- (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 (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
- ;; character by appending another backslash.
- (insert ?\\ )
- ;; Now the point is after both escapes, and we want to
- ;; rescan from before the first one to after the second
- ;; one.
- (setq state
- (parse-partial-sexp (- (point) 2) (point)
- nil nil state))
- ;; Advance the end point, since we just inserted a new
- ;; character.
- (setq end (1+ end)))
- ;; String: escape by inserting a backslash before the quote.
- (backward-char)
- (insert ?\\ )
- ;; The point is now between the escape and the quote, and we
- ;; want to rescan from before the escape to after the quote.
- (setq state
- (parse-partial-sexp (1- (point)) (1+ (point))
- nil nil state))
- ;; 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)
- (cond ((paredit-in-string-p) (paredit-backslash-interactive))
- ((paredit-in-comment-p) (insert ?\\))
- ((paredit-in-char-p) (forward-char) (paredit-backslash-interactive))
- (t (paredit-backslash-interactive))))
-
- (defun paredit-backslash-interactive ()
- (insert ?\\ )
- ;; Read a character to insert after the backslash. If anything
- ;; goes wrong -- the user hits delete (entering the rubout
- ;; `character'), aborts with C-g, or enters non-character input
- ;; -- then delete the backslash to avoid a dangling escape.
- (let ((delete-p t))
- (unwind-protect
- (let ((char (read-char "Character to escape: " t)))
- (if (not (eq char ?\^?))
- (progn (message "Character to escape: %c" char)
- (insert char)
- (setq delete-p nil))))
- (if delete-p
- (progn (message "Deleting escape.")
- (delete-char -1))))))
-
- (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.
- If in a comment and if followed by invalid structure, call
- `indent-new-comment-line' to keep the invalid structure in a
- comment."
- (interactive)
- (cond ((paredit-in-string-p)
- (newline))
- ((paredit-in-comment-p)
- (if (paredit-region-ok-p (point) (point-at-eol))
- (progn (newline-and-indent)
- (paredit-ignore-sexp-errors (indent-sexp)))
- (indent-new-comment-line)))
- (t
- (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 delimiter after the point.
- (paredit-ignore-sexp-errors (indent-sexp)))))
-
- (defun paredit-reindent-defun (&optional argument)
- "Reindent the definition that the point is on.
- If the point is in a string or a comment, fill the paragraph instead,
- and with a prefix argument, justify as well."
- (interactive "P")
- (if (or (paredit-in-string-p)
- (paredit-in-comment-p))
- (lisp-fill-paragraph argument)
- (paredit-preserving-column
- (save-excursion
- (end-of-defun)
- (beginning-of-defun)
- (indent-sexp)))))
- ;;;; Comment Insertion
-
- (defun paredit-semicolon (&optional n)
- "Insert a semicolon.
- With a prefix argument N, insert N semicolons.
- If in a string, do just that and nothing else.
- If in a character literal, move to the beginning of the character
- literal before inserting the semicolon.
- If the enclosing list ends on the line after the point, break the line
- after the last S-expression following the point.
- If a list begins on the line after the point but ends on a different
- line, break the line after the last S-expression following the point
- before the list."
- (interactive "p")
- (if (or (paredit-in-string-p) (paredit-in-comment-p))
- (insert (make-string (or n 1) ?\; ))
- (if (paredit-in-char-p)
- (backward-char 2))
- (let ((line-break-point (paredit-semicolon-find-line-break-point)))
- (if line-break-point
- (paredit-semicolon-with-line-break line-break-point (or n 1))
- (insert (make-string (or n 1) ?\; ))))))
-
- (defun paredit-semicolon-find-line-break-point ()
- (and (not (eolp)) ;Implies (not (eobp)).
- (let ((eol (point-at-eol)))
- (save-excursion
- (catch 'exit
- (while t
- (let ((line-break-point (point)))
- (cond ((paredit-handle-sexp-errors (progn (forward-sexp) t)
- nil)
- ;; Successfully advanced by an S-expression.
- ;; If that S-expression started on this line
- ;; and ended on another one, break here.
- (cond ((not (eq eol (point-at-eol)))
- (throw 'exit
- (and (save-excursion
- (backward-sexp)
- (eq eol (point-at-eol)))
- line-break-point)))
- ((eobp)
- (throw 'exit nil))))
- ((save-excursion
- (paredit-skip-whitespace t (point-at-eol))
- (or (eolp) (eobp) (eq (char-after) ?\;)))
- ;; Can't move further, but there's no closing
- ;; delimiter we're about to clobber -- either
- ;; it's on the next line or we're at the end of
- ;; the buffer. Don't break the line.
- (throw 'exit nil))
- (t
- ;; Can't move because we hit a delimiter at the
- ;; end of this line. Break here.
- (throw 'exit line-break-point))))))))))
-
- (defun paredit-semicolon-with-line-break (line-break-point n)
- (let ((line-break-marker (make-marker)))
- (set-marker line-break-marker line-break-point)
- (set-marker-insertion-type line-break-marker t)
- (insert (make-string (or n 1) ?\; ))
- (save-excursion
- (goto-char line-break-marker)
- (set-marker line-break-marker nil)
- (newline)
- (lisp-indent-line)
- ;; This step is redundant if we are inside a list, but even if we
- ;; are at the top level, we want at least to indent whatever we
- ;; bumped off the line.
- (paredit-ignore-sexp-errors (indent-sexp))
- (paredit-indent-sexps))))
- ;;; This is all a horrible, horrible hack, primarily for GNU Emacs 21,
- ;;; in which there is no `comment-or-uncomment-region'.
-
- (autoload 'comment-forward "newcomment")
- (autoload 'comment-normalize-vars "newcomment")
- (autoload 'comment-region "newcomment")
- (autoload 'comment-search-forward "newcomment")
- (autoload 'uncomment-region "newcomment")
-
- (defun paredit-initialize-comment-dwim ()
- (require 'newcomment)
- (if (not (fboundp 'comment-or-uncomment-region))
- (defalias 'comment-or-uncomment-region
- (lambda (beginning end &optional argument)
- (interactive "*r\nP")
- (if (save-excursion (goto-char beginning)
- (comment-forward (point-max))
- (<= end (point)))
- (uncomment-region beginning end argument)
- (comment-region beginning end argument)))))
- (defalias 'paredit-initialize-comment-dwim 'comment-normalize-vars)
- (comment-normalize-vars))
-
- (defun paredit-comment-dwim (&optional argument)
- "Call the Lisp comment command you want (Do What I Mean).
- This is like `comment-dwim', but it is specialized for Lisp editing.
- If transient mark mode is enabled and the mark is active, comment or
- uncomment the selected region, depending on whether it was entirely
- commented not not already.
- If there is already a comment on the current line, with no prefix
- argument, indent to that comment; with a prefix argument, kill that
- comment.
- Otherwise, insert a comment appropriate for the context and ensure that
- any code following the comment is moved to the next line.
- At the top level, where indentation is calculated to be at column 0,
- insert a triple-semicolon comment; within code, where the indentation
- is calculated to be non-zero, and on the line there is either no code
- at all or code after the point, insert a double-semicolon comment;
- and if the point is after all code on the line, insert a single-
- semicolon margin comment at `comment-column'."
- (interactive "*P")
- (paredit-initialize-comment-dwim)
- (cond ((paredit-region-active-p)
- (comment-or-uncomment-region (region-beginning)
- (region-end)
- argument))
- ((paredit-comment-on-line-p)
- (if argument
- (comment-kill (if (integerp argument) argument nil))
- (comment-indent)))
- (t (paredit-insert-comment))))
- (defun paredit-comment-on-line-p ()
- "True if there is a comment on the line following point.
- This is expected to be called only in `paredit-comment-dwim'; do not
- call it elsewhere."
- (save-excursion
- (beginning-of-line)
- (let ((comment-p nil))
- ;; Search forward for a comment beginning. If there is one, set
- ;; COMMENT-P to true; if not, it will be nil.
- (while (progn
- (setq comment-p ;t -> no error
- (comment-search-forward (point-at-eol) t))
- (and comment-p
- (or (paredit-in-string-p)
- (paredit-in-char-p (1- (point))))))
- (forward-char))
- comment-p)))
-
- (defun paredit-insert-comment ()
- (let ((code-after-p
- (save-excursion (paredit-skip-whitespace t (point-at-eol))
- (not (eolp))))
- (code-before-p
- (save-excursion (paredit-skip-whitespace nil (point-at-bol))
- (not (bolp)))))
- (cond ((and (bolp)
- (let ((indent
- (let ((indent (calculate-lisp-indent)))
- (if (consp indent) (car indent) indent))))
- (and indent (zerop indent))))
- ;; Top-level comment
- (if code-after-p (save-excursion (newline)))
- (insert ";;; "))
- ((or code-after-p (not code-before-p))
- ;; Code comment
- (if code-before-p
- (newline-and-indent)
- (lisp-indent-line))
- (insert ";; ")
- (if code-after-p
- (save-excursion
- (newline)
- (lisp-indent-line)
- (paredit-indent-sexps))))
- (t
- ;; Margin comment
- (indent-to comment-column 1) ; 1 -> force one leading space
- (insert ?\; )))))
- ;;;; Character Deletion
-
- (defun paredit-forward-delete (&optional argument)
- "Delete a character forward or move forward over a delimiter.
- If on an opening S-expression delimiter, move forward into the
- S-expression.
- If on a closing S-expression delimiter, refuse to delete unless the
- S-expression is empty, in which case delete the whole S-expression.
- With a numeric prefix argument N, delete N characters forward.
- With a `C-u' prefix argument, simply delete a character forward,
- without regard for delimiter balancing."
- (interactive "P")
- (cond ((or (consp argument) (eobp))
- (delete-char +1))
- ((integerp argument)
- (if (< argument 0)
- (paredit-backward-delete argument)
- (while (> argument 0)
- (paredit-forward-delete)
- (setq argument (- argument 1)))))
- ((paredit-in-string-p)
- (paredit-forward-delete-in-string))
- ((paredit-in-comment-p)
- (paredit-forward-delete-in-comment))
- ((paredit-in-char-p) ; Escape -- delete both chars.
- (delete-char -1)
- (delete-char +1))
- ((eq (char-after) ?\\ ) ; ditto
- (delete-char +2))
- ((let ((syn (char-syntax (char-after))))
- (or (eq syn ?\( )
- (eq syn ?\" )))
- (if (save-excursion
- (paredit-handle-sexp-errors (progn (forward-sexp) t)
- nil))
- (forward-char)
- (message "Deleting spurious opening delimiter.")
- (delete-char +1)))
- ((and (not (paredit-in-char-p (1- (point))))
- (eq (char-syntax (char-after)) ?\) )
- (eq (char-before) (matching-paren (char-after))))
- (delete-char -1) ; Empty list -- delete both
- (delete-char +1)) ; delimiters.
- ((eq ?\; (char-after))
- (paredit-forward-delete-comment-start))
- ((eq (char-syntax (char-after)) ?\) )
- (if (paredit-handle-sexp-errors
- (save-excursion (forward-char) (backward-sexp) t)
- nil)
- (message "End of list!")
- (progn
- (message "Deleting spurious closing delimiter.")
- (delete-char +1))))
- ;; Just delete a single character, if it's not a closing
- ;; delimiter. (The character literal case is already handled
- ;; by now.)
- (t (delete-char +1))))
- (defun paredit-forward-delete-in-string ()
- (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.
- (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)) (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.
- (delete-char -1)
- (delete-char +1)))))
-
- (defun paredit-check-forward-delete-in-comment ()
- ;; Point is in a comment, possibly at eol. We are about to delete
- ;; some characters forward; if we are at eol, we are about to delete
- ;; the line break. Refuse to do so if if moving the next line into
- ;; the comment would break structure.
- (if (eolp)
- (let ((next-line-start (point-at-bol 2))
- (next-line-end (point-at-eol 2)))
- (paredit-check-region next-line-start next-line-end))))
-
- (defun paredit-forward-delete-in-comment ()
- (paredit-check-forward-delete-in-comment)
- (delete-char +1))
-
- (defun paredit-forward-delete-comment-start ()
- ;; Point precedes a comment start (not at eol). Refuse to delete a
- ;; comment start if the comment contains unbalanced junk.
- (paredit-check-region (+ (point) 1) (point-at-eol))
- (delete-char +1))
- (defun paredit-backward-delete (&optional argument)
- "Delete a character backward or move backward over a delimiter.
- If on a closing S-expression delimiter, move backward into the
- S-expression.
- If on an opening S-expression delimiter, refuse to delete unless the
- S-expression is empty, in which case delete the whole S-expression.
- With a numeric prefix argument N, delete N characters backward.
- With a `C-u' prefix argument, simply delete a character backward,
- without regard for delimiter balancing."
- (interactive "P")
- (cond ((or (consp argument) (bobp))
- ;++ Should this untabify?
- (delete-char -1))
- ((integerp argument)
- (if (< argument 0)
- (paredit-forward-delete (- 0 argument))
- (while (> argument 0)
- (paredit-backward-delete)
- (setq argument (- argument 1)))))
- ((paredit-in-string-p)
- (paredit-backward-delete-in-string))
- ((paredit-in-comment-p)
- (paredit-backward-delete-in-comment))
- ((paredit-in-char-p) ; Escape -- delete both chars.
- (delete-char -1)
- (delete-char +1))
- ((paredit-in-char-p (1- (point)))
- (delete-char -2)) ; ditto
- ((let ((syn (char-syntax (char-before))))
- (or (eq syn ?\) )
- (eq syn ?\" )))
- (if (save-excursion
- (paredit-handle-sexp-errors (progn (backward-sexp) t)
- nil))
- (backward-char)
- (message "Deleting spurious closing delimiter.")
- (delete-char -1)))
- ((and (eq (char-syntax (char-before)) ?\( )
- (eq (char-after) (matching-paren (char-before))))
- (delete-char -1) ; Empty list -- delete both
- (delete-char +1)) ; delimiters.
- ((bolp)
- (paredit-backward-delete-maybe-comment-end))
- ((eq (char-syntax (char-before)) ?\( )
- (if (paredit-handle-sexp-errors
- (save-excursion (backward-char) (forward-sexp) t)
- nil)
- (message "Beginning of list!")
- (progn
- (message "Deleting spurious closing delimiter.")
- (delete-char -1))))
- ;; Delete it, unless it's an opening delimiter. The case of
- ;; character literals is already handled by now.
- (t
- ;; Turn off the @#&*&!^&(%^ botch in GNU Emacs 24 that changed
- ;; `backward-delete-char' and `backward-delete-char-untabify'
- ;; semantically so that they delete the region in transient
- ;; mark mode.
- (let ((delete-active-region nil))
- (backward-delete-char-untabify +1)))))
- (defun paredit-backward-delete-in-string ()
- (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))
- (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.
- (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.
- (delete-char -1)
- (delete-char +1)))))
-
- (defun paredit-backward-delete-in-comment ()
- ;; Point is in a comment, possibly just after the comment start.
- ;; Refuse to delete a comment start if the comment contains
- ;; unbalanced junk.
- (if (save-excursion
- (backward-char)
- ;; Must call `paredit-in-string-p' before
- ;; `paredit-in-comment-p'.
- (not (or (paredit-in-string-p) (paredit-in-comment-p))))
- (paredit-check-region (point) (point-at-eol)))
- (backward-delete-char-untabify +1))
-
- (defun paredit-backward-delete-maybe-comment-end ()
- ;; Point is at bol, possibly just after a comment end (i.e., the
- ;; previous line may have had a line comment). Refuse to delete a
- ;; comment end if moving the current line into the previous line's
- ;; comment would break structure.
- (if (save-excursion
- (backward-char)
- (and (not (paredit-in-string-p)) (paredit-in-comment-p)))
- (paredit-check-region (point-at-eol) (point-at-bol)))
- (delete-char -1))
- ;;;; Killing
-
- (defun paredit-kill (&optional argument)
- "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
- closing string delimiter.
- On a line with no S-expressions on it starting after the point or
- within a comment, act exactly as `kill-line'.
- Otherwise, kill all S-expressions that start after the point.
- With a `C-u' prefix argument, just do the standard `kill-line'.
- With a numeric prefix argument N, do `kill-line' that many times."
- (interactive "P")
- (cond (argument
- (kill-line (if (integerp argument) argument 1)))
- ((paredit-in-string-p)
- (paredit-kill-line-in-string))
- ((paredit-in-comment-p)
- (paredit-kill-line-in-comment))
- ((save-excursion (paredit-skip-whitespace t (point-at-eol))
- (or (eolp) (eq (char-after) ?\; )))
- ;** Be careful about trailing backslashes.
- (if (paredit-in-char-p)
- (backward-char))
- (kill-line))
- (t (paredit-kill-sexps-on-line))))
-
- (defun paredit-kill-line-in-string ()
- (if (save-excursion (paredit-skip-whitespace t (point-at-eol))
- (eolp))
- (kill-line)
- (save-excursion
- ;; Be careful not to split an escape sequence.
- (if (paredit-in-string-escape-p)
- (backward-char))
- (kill-region (point)
- (min (point-at-eol)
- (cdr (paredit-string-start+end-points)))))))
-
- (defun paredit-kill-line-in-comment ()
- ;; The variable `kill-whole-line' is not relevant: the point is in a
- ;; comment, and hence not at the beginning of the line.
- (paredit-check-forward-delete-in-comment)
- (kill-line))
-
- (defun paredit-kill-sexps-on-line ()
- (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)))
- (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
- ;; the last one, kill the whole line, including
- ;; any comments; otherwise just kill to the end of
- ;; the last S-expression we found. Be sure,
- ;; though, not to kill any closing parentheses.
- (if (and (not end-of-list-p)
- (eq (point-at-eol) eol))
- eol
- (point)))))))
- ;;; Please do not try to understand this code unless you have a VERY
- ;;; good reason to do so. I gave up trying to figure it out well
- ;;; enough to explain it, long ago.
-
- (defun paredit-forward-sexps-to-kill (beginning eol)
- (let ((end-of-list-p nil)
- (firstp t))
- ;; 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 t
- ;; This and the `kill-whole-line' business below fix a bug that
- ;; inhibited any S-expression at the very end of the buffer
- ;; (with no trailing newline) from being deleted. It's a
- ;; bizarre fix that I ought to document at some point, but I am
- ;; too busy at the moment to do so.
- (if (and kill-whole-line (eobp)) (throw 'return nil))
- (save-excursion
- (paredit-handle-sexp-errors (forward-sexp)
- (up-list)
- (setq end-of-list-p (eq (point-at-eol) eol))
- (throw 'return nil))
- (if (or (and (not firstp)
- (not kill-whole-line)
- (eobp))
- (paredit-handle-sexp-errors
- (progn (backward-sexp) nil)
- t)
- (not (eq (point-at-eol) eol)))
- (throw 'return nil)))
- (forward-sexp)
- (if (and firstp
- (not kill-whole-line)
- (eobp))
- (throw 'return nil))
- (setq firstp nil)))
- end-of-list-p))
-
- (defun paredit-kill-sexps-on-whole-line (beginning)
- (kill-region beginning
- (or (save-excursion ; Delete trailing indentation...
- (paredit-skip-whitespace t)
- (and (not (eq (char-after) ?\; ))
- (point)))
- ;; ...or just use the point past the newline, if
- ;; we encounter a comment.
- (point-at-eol)))
- (cond ((save-excursion (paredit-skip-whitespace nil (point-at-bol))
- (bolp))
- ;; Nothing but indentation before the point, so indent it.
- (lisp-indent-line))
- ((eobp) nil) ; Protect the CHAR-SYNTAX below against NIL.
- ;; Insert a space to avoid invalid joining if necessary.
- ((let ((syn-before (char-syntax (char-before)))
- (syn-after (char-syntax (char-after))))
- (or (and (eq syn-before ?\) ) ; Separate opposing
- (eq syn-after ?\( )) ; parentheses,
- (and (eq syn-before ?\" ) ; string delimiter
- (eq syn-after ?\" )) ; pairs,
- (and (memq syn-before '(?_ ?w)) ; or word or symbol
- (memq syn-after '(?_ ?w))))) ; constituents.
- (insert " "))))
- ;;;;; Killing Words
-
- ;;; This is tricky and asymmetrical because backward parsing is
- ;;; extraordinarily difficult or impossible, so we have to implement
- ;;; killing in both directions by parsing forward.
-
- (defun paredit-forward-kill-word ()
- "Kill a word forward, skipping over intervening delimiters."
- (interactive)
- (let ((beginning (point)))
- (skip-syntax-forward " -")
- (let* ((parse-state (paredit-current-parse-state))
- (state (paredit-kill-word-state parse-state 'char-after)))
- (while (not (or (eobp)
- (eq ?w (char-syntax (char-after)))))
- (setq parse-state
- (progn (forward-char 1) (paredit-current-parse-state))
- ;; (parse-partial-sexp (point) (1+ (point))
- ;; nil nil parse-state)
- )
- (let* ((old-state state)
- (new-state
- (paredit-kill-word-state parse-state 'char-after)))
- (cond ((not (eq old-state new-state))
- (setq parse-state
- (paredit-kill-word-hack old-state
- new-state
- parse-state))
- (setq state
- (paredit-kill-word-state parse-state
- 'char-after))
- (setq beginning (point)))))))
- (goto-char beginning)
- (kill-word 1)))
-
- (defun paredit-backward-kill-word ()
- "Kill a word backward, skipping over any intervening delimiters."
- (interactive)
- (if (not (or (bobp)
- (eq (char-syntax (char-before)) ?w)))
- (let ((end (point)))
- (backward-word 1)
- (forward-word 1)
- (goto-char (min end (point)))
- (let* ((parse-state (paredit-current-parse-state))
- (state
- (paredit-kill-word-state parse-state 'char-before)))
- (while (and (< (point) end)
- (progn
- (setq parse-state
- (parse-partial-sexp (point) (1+ (point))
- nil nil parse-state))
- (or (eq state
- (paredit-kill-word-state parse-state
- 'char-before))
- (progn (backward-char 1) nil)))))
- (if (and (eq state 'comment)
- (eq ?\# (char-after (point)))
- (eq ?\| (char-before (point))))
- (backward-char 1)))))
- (backward-kill-word 1))
- ;;;;;; Word-Killing Auxiliaries
-
- (defun paredit-kill-word-state (parse-state adjacent-char-fn)
- (cond ((paredit-in-comment-p parse-state) 'comment)
- ((paredit-in-string-p parse-state) 'string)
- ((memq (char-syntax (funcall adjacent-char-fn))
- '(?\( ?\) ))
- 'delimiter)
- (t 'other)))
-
- ;;; This optionally advances the point past any comment delimiters that
- ;;; should probably not be touched, based on the last state change and
- ;;; the characters around the point. It returns a new parse state,
- ;;; starting from the PARSE-STATE parameter.
-
- (defun paredit-kill-word-hack (old-state new-state parse-state)
- (cond ((and (not (eq old-state 'comment))
- (not (eq new-state 'comment))
- (not (paredit-in-string-escape-p))
- (eq ?\# (char-before))
- (eq ?\| (char-after)))
- (forward-char 1)
- (paredit-current-parse-state)
- ;; (parse-partial-sexp (point) (1+ (point))
- ;; nil nil parse-state)
- )
- ((and (not (eq old-state 'comment))
- (eq new-state 'comment)
- (eq ?\; (char-before)))
- (skip-chars-forward ";")
- (paredit-current-parse-state)
- ;; (parse-partial-sexp (point) (save-excursion
- ;; (skip-chars-forward ";"))
- ;; nil nil parse-state)
- )
- (t parse-state)))
-
- (defun paredit-copy-as-kill ()
- "Save in the kill ring the region that `paredit-kill' would kill."
- (interactive)
- (cond ((paredit-in-string-p)
- (paredit-copy-as-kill-in-string))
- ((paredit-in-comment-p)
- (copy-region-as-kill (point) (point-at-eol)))
- ((save-excursion (paredit-skip-whitespace t (point-at-eol))
- (or (eolp) (eq (char-after) ?\; )))
- ;** Be careful about trailing backslashes.
- (save-excursion
- (if (paredit-in-char-p)
- (backward-char))
- (copy-region-as-kill (point) (point-at-eol))))
- (t (paredit-copy-sexps-as-kill))))
-
- (defun paredit-copy-as-kill-in-string ()
- (save-excursion
- (if (paredit-in-string-escape-p)
- (backward-char))
- (copy-region-as-kill (point)
- (min (point-at-eol)
- (cdr (paredit-string-start+end-points))))))
-
- (defun paredit-copy-sexps-as-kill ()
- (save-excursion
- (if (paredit-in-char-p)
- (backward-char 2))
- (let ((beginning (point))
- (eol (point-at-eol)))
- (let ((end-of-list-p (paredit-forward-sexps-to-kill beginning eol)))
- (if end-of-list-p (progn (up-list) (backward-char)))
- (copy-region-as-kill beginning
- (cond (kill-whole-line
- (or (save-excursion
- (paredit-skip-whitespace t)
- (and (not (eq (char-after) ?\; ))
- (point)))
- (point-at-eol)))
- ((and (not end-of-list-p)
- (eq (point-at-eol) eol))
- eol)
- (t
- (point))))))))
- ;;;; Deleting Regions
-
- (defun paredit-delete-region (start end)
- "Delete the text between point and mark, like `delete-region'.
- If that text is unbalanced, signal an error instead.
- With a prefix argument, skip the balance check."
- (interactive "r")
- (if (and start end (not current-prefix-arg))
- (paredit-check-region-for-delete start end))
- (setq this-command 'delete-region)
- (delete-region start end))
-
- (defun paredit-kill-region (start end)
- "Kill the text between point and mark, like `kill-region'.
- If that text is unbalanced, signal an error instead.
- With a prefix argument, skip the balance check."
- (interactive "r")
- (if (and start end (not current-prefix-arg))
- (paredit-check-region-for-delete start end))
- (setq this-command 'kill-region)
- (kill-region start end))
-
- (defun paredit-check-region-for-delete (start end)
- "Signal an error deleting text between START and END is unsafe."
- (save-excursion
- (goto-char start)
- (let* ((start-state (paredit-current-parse-state))
- (end-state (parse-partial-sexp start end nil nil start-state)))
- (paredit-check-region-for-delete:depth start start-state end end-state)
- (paredit-check-region-for-delete:string start start-state end end-state)
- (paredit-check-region-for-delete:comment start start-state end end-state)
- (paredit-check-region-for-delete:char-quote start start-state
- end end-state))))
-
- (defun paredit-check-region-for-delete:depth (start start-state end end-state)
- (let ((start-depth (nth 0 start-state))
- (end-depth (nth 0 end-state)))
- (if (not (= start-depth end-depth))
- (error "Mismatched parenthesis depth: %S at start, %S at end."
- start-depth
- end-depth))))
-
- (defun paredit-check-region-for-delete:string (start start-state end end-state)
- (let ((start-string-p (nth 3 start-state))
- (end-string-p (nth 3 end-state)))
- (if (not (eq start-string-p end-string-p))
- (error "Mismatched string state: start %sin string, end %sin string."
- (if start-string-p "" "not ")
- (if end-string-p "" "not ")))))
- (defun paredit-check-region-for-delete:comment
- (start start-state end end-state)
- (let ((start-comment-state (nth 4 start-state))
- (end-comment-state (nth 4 end-state)))
- (if (not (or (eq start-comment-state end-comment-state)
- ;; If we are moving text into or out of a line
- ;; comment, make sure that the text is balanced. (The
- ;; comment state may be a number, not t or nil at all,
- ;; for nestable comments, which are not handled by
- ;; this heuristic (or any of paredit, really).)
- (and (or (and (eq start-comment-state nil)
- (eq end-comment-state t))
- (and (eq start-comment-state t)
- (eq end-comment-state nil)))
- (save-excursion
- (goto-char end)
- (paredit-region-ok-p (point) (point-at-eol))))))
- (error "Mismatched comment state: %s"
- (cond ((and (integerp start-comment-state)
- (integerp end-comment-state))
- (format "depth %S at start, depth %S at end."
- start-comment-state
- end-comment-state))
- ((integerp start-comment-state)
- "start in nested comment, end otherwise.")
- ((integerp end-comment-state)
- "end in nested comment, start otherwise.")
- (start-comment-state
- "start in comment, end not in comment.")
- (end-comment-state
- "end in comment, start not in comment.")
- (t
- (format "start %S, end %S."
- start-comment-state
- end-comment-state)))))))
-
- (defun paredit-check-region-for-delete:char-quote
- (start start-state end end-state)
- (let ((start-char-quote (nth 5 start-state))
- (end-char-quote (nth 5 end-state)))
- (if (not (eq start-char-quote end-char-quote))
- (let ((phrase "character quotation"))
- (error "Mismatched %s: start %sin %s, end %sin %s."
- phrase
- (if start-char-quote "" "not ")
- phrase
- (if end-char-quote "" "not ")
- phrase)))))
- ;;;; Point Motion
-
- (eval-and-compile
- (defmacro defun-motion (name bvl doc &rest body)
- `(defun ,name ,bvl
- ,doc
- ,(xcond ((paredit-xemacs-p)
- '(interactive "_"))
- ((paredit-gnu-emacs-p)
- ;++ Not sure this is sufficient for the `^'.
- (if (fboundp 'handle-shift-selection)
- '(interactive "^p")
- '(interactive "p"))))
- ,@body)))
-
- (defun-motion paredit-forward (&optional arg)
- "Move forward an S-expression, or up an S-expression forward.
- If there are no more S-expressions in this one before the closing
- delimiter, move past that closing delimiter; otherwise, move forward
- past the S-expression following the point."
- (let ((n (or arg 1)))
- (cond ((< 0 n) (dotimes (i n) (paredit-move-forward)))
- ((< n 0) (dotimes (i (- n)) (paredit-move-backward))))))
-
- (defun-motion paredit-backward (&optional arg)
- "Move backward an S-expression, or up an S-expression backward.
- If there are no more S-expressions in this one before the opening
- delimiter, move past that opening delimiter backward; otherwise, move
- move backward past the S-expression preceding the point."
- (let ((n (or arg 1)))
- (cond ((< 0 n) (dotimes (i n) (paredit-move-backward)))
- ((< n 0) (dotimes (i (- n)) (paredit-move-forward))))))
- (defun paredit-move-forward ()
- (cond ((paredit-in-string-p)
- (let ((end (paredit-enclosing-string-end)))
- ;; `forward-sexp' and `up-list' may move into the next string
- ;; in the buffer. Don't do that; move out of the current one.
- (if (paredit-handle-sexp-errors
- (progn (paredit-handle-sexp-errors (forward-sexp)
- (up-list))
- (<= end (point)))
- t)
- (goto-char end))))
- ((paredit-in-char-p)
- (forward-char))
- (t
- (paredit-handle-sexp-errors (forward-sexp)
- (up-list)))))
-
- (defun paredit-move-backward ()
- (cond ((paredit-in-string-p)
- (let ((start (paredit-enclosing-string-start)))
- (if (paredit-handle-sexp-errors
- (progn (paredit-handle-sexp-errors (backward-sexp)
- (backward-up-list))
- (<= (point) start))
- t)
- (goto-char start))))
- ((paredit-in-char-p)
- ;++ Corner case: a buffer of `\|x'. What to do?
- (backward-char 2))
- (t
- (paredit-handle-sexp-errors (backward-sexp)
- (backward-up-list)))))
- ;;;; Window Positioning
-
- (defalias 'paredit-recentre-on-sexp 'paredit-recenter-on-sexp)
-
- (defun paredit-recenter-on-sexp (&optional n)
- "Recenter the screen on the S-expression following the point.
- With a prefix argument N, encompass all N S-expressions forward."
- (interactive "P")
- (let* ((p (point))
- (end-point (progn (forward-sexp n) (point)))
- (start-point (progn (goto-char end-point) (backward-sexp n) (point))))
- ;; Point is at beginning of first S-expression.
- (let ((p-visible nil) (start-visible nil))
- (save-excursion
- (forward-line (/ (count-lines start-point end-point) 2))
- (recenter)
- (setq p-visible (pos-visible-in-window-p p))
- (setq start-visible (pos-visible-in-window-p start-point)))
- (cond ((not start-visible)
- ;; Implies (not p-visible). Put the start at the top of
- ;; the screen.
- (recenter 0))
- (p-visible
- ;; Go back to p if we can.
- (goto-char p))))))
-
- (defun paredit-recenter-on-defun ()
- "Recenter the screen on the definition at point."
- (interactive)
- (save-excursion
- (beginning-of-defun)
- (paredit-recenter-on-sexp)))
-
- (defun paredit-focus-on-defun ()
- "Moves display to the top of the definition at point."
- (interactive)
- (beginning-of-defun)
- (recenter 0))
- ;;;; Generalized Upward/Downward Motion
-
- (defun paredit-up/down (n vertical-direction)
- (let ((horizontal-direction (if (< 0 n) +1 -1)))
- (while (/= n 0)
- (goto-char
- (paredit-next-up/down-point horizontal-direction vertical-direction))
- (setq n (- n horizontal-direction)))))
-
- (defun paredit-next-up/down-point (horizontal-direction vertical-direction)
- (let ((state (paredit-current-parse-state))
- (scan-lists
- (lambda ()
- (scan-lists (point) horizontal-direction vertical-direction))))
- (cond ((paredit-in-string-p state)
- (let ((start+end (paredit-string-start+end-points state)))
- (if (< 0 vertical-direction)
- (if (< 0 horizontal-direction)
- (+ 1 (cdr start+end))
- (car start+end))
- ;; We could let the user try to descend into lists
- ;; within the string, but that would be asymmetric
- ;; with the up case, which rises out of the whole
- ;; string and not just out of a list within the
- ;; string, so this case will just be an error.
- (error "Can't descend further into string."))))
- ((< 0 vertical-direction)
- ;; When moving up, just try to rise up out of the list.
- (or (funcall scan-lists)
- (buffer-end horizontal-direction)))
- ((< vertical-direction 0)
- ;; When moving down, look for a string closer than a list,
- ;; and use that if we find it.
- (let* ((list-start
- (paredit-handle-sexp-errors (funcall scan-lists) nil))
- (string-start
- (paredit-find-next-string-start horizontal-direction
- list-start)))
- (if (and string-start list-start)
- (if (< 0 horizontal-direction)
- (min string-start list-start)
- (max string-start list-start))
- (or string-start
- ;; Scan again: this is a kludgey way to report the
- ;; error if there really was one.
- (funcall scan-lists)
- (buffer-end horizontal-direction)))))
- (t
- (error "Vertical direction must be nonzero in `%s'."
- 'paredit-up/down)))))
- (defun paredit-find-next-string-start (horizontal-direction limit)
- (let ((buffer-limit-p (if (< 0 horizontal-direction) 'eobp 'bobp))
- (next-char (if (< 0 horizontal-direction) 'char-after 'char-before))
- (pastp (if (< 0 horizontal-direction) '> '<)))
- (paredit-handle-sexp-errors
- (save-excursion
- (catch 'exit
- (while t
- (if (or (funcall buffer-limit-p)
- (and limit (funcall pastp (point) limit)))
- (throw 'exit nil))
- (forward-sexp horizontal-direction)
- (save-excursion
- (backward-sexp horizontal-direction)
- (if (eq ?\" (char-syntax (funcall next-char)))
- (throw 'exit (+ (point) horizontal-direction)))))))
- nil)))
-
- (defun-motion paredit-forward-down (&optional argument)
- "Move forward down into a list.
- With a positive argument, move forward down that many levels.
- With a negative argument, move backward down that many levels."
- (paredit-up/down (or argument +1) -1))
-
- (defun-motion paredit-backward-up (&optional argument)
- "Move backward up out of the enclosing list.
- With a positive argument, move backward up that many levels.
- With a negative argument, move forward up that many levels.
- If in a string initially, that counts as one level."
- (paredit-up/down (- 0 (or argument +1)) +1))
-
- (defun-motion paredit-forward-up (&optional argument)
- "Move forward up out of the enclosing list.
- With a positive argument, move forward up that many levels.
- With a negative argument, move backward up that many levels.
- If in a string initially, that counts as one level."
- (paredit-up/down (or argument +1) +1))
-
- (defun-motion paredit-backward-down (&optional argument)
- "Move backward down into a list.
- With a positive argument, move backward down that many levels.
- With a negative argument, move forward down that many levels."
- (paredit-up/down (- 0 (or argument +1)) -1))
- ;;;; Depth-Changing Commands: Wrapping, Splicing, & Raising
-
- (defun paredit-wrap-sexp (&optional argument open close)
- "Wrap the following S-expression.
- If a `C-u' prefix argument is given, wrap all S-expressions following
- the point until the end of the buffer or of the enclosing list.
- If a numeric 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 parenthesis pair, rather than inserting a lone opening delimiter
- and then signalling an error, in the interest of preserving
- structure.
- By default OPEN and CLOSE are round delimiters."
- (interactive "P")
- (paredit-lose-if-not-in-sexp 'paredit-wrap-sexp)
- (let ((open (or open ?\( ))
- (close (or close ?\) )))
- (paredit-handle-sexp-errors
- ((lambda (n) (paredit-insert-pair n open close 'goto-char))
- (cond ((integerp argument) argument)
- ((consp argument) (paredit-count-sexps-forward))
- ((paredit-region-active-p) nil)
- (t 1)))
- (insert close)
- (backward-char)))
- (save-excursion (backward-up-list) (indent-sexp)))
-
- (defun paredit-yank-pop (&optional argument)
- "Replace just-yanked text with the next item in the kill ring.
- If this command follows a `yank', just run `yank-pop'.
- If this command follows a `paredit-wrap-sexp', or any other paredit
- wrapping command (see `paredit-wrap-commands'), run `yank' and
- reindent the enclosing S-expression.
- If this command is repeated, run `yank-pop' and reindent the enclosing
- S-expression.
-
- The argument is passed on to `yank' or `yank-pop'; see their
- documentation for details."
- (interactive "*p")
- (cond ((eq last-command 'yank)
- (yank-pop argument))
- ((memq last-command paredit-wrap-commands)
- (yank argument)
- ;; `yank' futzes with `this-command'.
- (setq this-command 'paredit-yank-pop)
- (save-excursion (backward-up-list) (indent-sexp)))
- ((eq last-command 'paredit-yank-pop)
- ;; Pretend we just did a `yank', so that we can use
- ;; `yank-pop' without duplicating its definition.
- (setq last-command 'yank)
- (yank-pop argument)
- ;; Return to our original state.
- (setq last-command 'paredit-yank-pop)
- (setq this-command 'paredit-yank-pop)
- (save-excursion (backward-up-list) (indent-sexp)))
- (t (error "Last command was not a yank or a wrap: %s" last-command))))
- (defun paredit-splice-sexp (&optional argument)
- "Splice the list that the point is on by removing its delimiters.
- With a prefix argument as in `C-u', kill all S-expressions backward in
- the current list before splicing all S-expressions forward into the
- enclosing list.
- With two prefix arguments as in `C-u C-u', kill all S-expressions
- forward in the current list before splicing all S-expressions
- backward into the enclosing list.
- With a numerical prefix argument N, kill N S-expressions backward in
- the current list before splicing the remaining S-expressions into the
- enclosing list. If N is negative, kill forward.
- Inside a string, unescape all backslashes, or signal an error if doing
- so would invalidate the buffer's structure."
- (interactive "P")
- (if (paredit-in-string-p)
- (paredit-splice-string argument)
- (if (paredit-in-comment-p)
- (error "Can't splice comment."))
- (paredit-handle-sexp-errors (paredit-enclosing-list-start)
- (error "Can't splice top level."))
- (paredit-kill-surrounding-sexps-for-splice argument)
- (let ((delete-start (paredit-enclosing-list-start))
- (delete-end
- (let ((limit
- (save-excursion
- (paredit-ignore-sexp-errors (forward-sexp) (backward-sexp))
- (point))))
- (save-excursion
- (backward-up-list)
- (forward-char +1)
- (paredit-skip-whitespace t limit)
- (point)))))
- (let ((end-marker (make-marker)))
- (save-excursion
- (up-list)
- (delete-char -1)
- (set-marker end-marker (point)))
- (delete-region delete-start delete-end)
- (paredit-splice-reindent delete-start (marker-position end-marker))))))
- (defun paredit-splice-reindent (start end)
- (paredit-preserving-column
- ;; If we changed the first subform of the enclosing list, we must
- ;; reindent the whole enclosing list.
- (if (paredit-handle-sexp-errors
- (save-excursion
- (backward-up-list)
- (down-list)
- (paredit-ignore-sexp-errors (forward-sexp))
- (< start (point)))
- nil)
- (save-excursion (backward-up-list) (indent-sexp))
- (paredit-indent-region start end))))
-
- (defun paredit-kill-surrounding-sexps-for-splice (argument)
- (cond ((or (paredit-in-string-p)
- (paredit-in-comment-p))
- (error "Invalid context for splicing S-expressions."))
- ((or (not argument) (eq argument 0)) nil)
- ((or (numberp argument) (eq argument '-))
- ;; Kill S-expressions before/after the point by saving the
- ;; point, moving across them, and killing the region.
- (let* ((argument (if (eq argument '-) -1 argument))
- (saved (paredit-point-at-sexp-boundary (- argument))))
- (goto-char saved)
- (paredit-ignore-sexp-errors (backward-sexp argument))
- (paredit-hack-kill-region saved (point))))
- ((consp argument)
- (let ((v (car argument)))
- (if (= v 4) ;One `C-u'.
- ;; Move backward until we hit the open paren; then
- ;; kill that selected region.
- (let ((end (point)))
- (paredit-ignore-sexp-errors
- (while (not (bobp))
- (backward-sexp)))
- (paredit-hack-kill-region (point) end))
- ;; Move forward until we hit the close paren; then
- ;; kill that selected region.
- (let ((beginning (point)))
- (paredit-ignore-sexp-errors
- (while (not (eobp))
- (forward-sexp)))
- (paredit-hack-kill-region beginning (point))))))
- (t (error "Bizarre prefix argument `%s'." argument))))
- (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.
- With a prefix argument N, kill only the preceding N S-expressions."
- (interactive "P")
- (paredit-splice-sexp (if n
- (prefix-numeric-value n)
- '(4))))
-
- (defun paredit-splice-sexp-killing-forward (&optional n)
- "Splice the list the point is on by removing its delimiters, and
- also kill all S-expressions after the point in the current list.
- With a prefix argument N, kill only the following N S-expressions."
- (interactive "P")
- (paredit-splice-sexp (if n
- (- (prefix-numeric-value n))
- '(16))))
-
- (defun paredit-raise-sexp (&optional argument)
- "Raise the following S-expression in a tree, deleting its siblings.
- With a prefix argument N, raise the following N S-expressions. If N
- is negative, raise the preceding N S-expressions.
- If the point is on an S-expression, such as a string or a symbol, not
- between them, that S-expression is considered to follow the point."
- (interactive "P")
- (save-excursion
- (cond ((paredit-in-string-p)
- (goto-char (car (paredit-string-start+end-points))))
- ((paredit-in-char-p)
- (backward-sexp))
- ((paredit-in-comment-p)
- (error "No S-expression to raise in comment.")))
- ;; Select the S-expressions we want to raise in a buffer substring.
- (let* ((n (prefix-numeric-value argument))
- (bound (scan-sexps (point) n))
- (sexps
- (if (< n 0)
- (buffer-substring bound (paredit-point-at-sexp-end))
- (buffer-substring (paredit-point-at-sexp-start) bound))))
- ;; Move up to the list we're raising those S-expressions out of and
- ;; delete it.
- (backward-up-list)
- (delete-region (point) (scan-sexps (point) 1))
- (let* ((indent-start (point))
- (indent-end (save-excursion (insert sexps) (point))))
- ;; If the expression spans multiple lines, its indentation is
- ;; probably broken, so reindent it -- but don't reindent
- ;; anything that we didn't touch outside the expression.
- ;;
- ;; XXX What if the *column* of the starting point was preserved
- ;; too? Should we avoid reindenting in that case?
- (if (not (eq (save-excursion (goto-char indent-start) (point-at-eol))
- (save-excursion (goto-char indent-end) (point-at-eol))))
- (indent-region indent-start indent-end nil))))))
- ;;; The effects of convolution on the surrounding whitespace are pretty
- ;;; random. If you have better suggestions, please let me know.
-
- (defun paredit-convolute-sexp (&optional n)
- "Convolute S-expressions.
- Save the S-expressions preceding point and delete them.
- Splice the S-expressions following point.
- Wrap the enclosing list in a new list prefixed by the saved text.
- With a prefix argument N, move up N lists before wrapping."
- (interactive "p")
- (paredit-lose-if-not-in-sexp 'paredit-convolute-sexp)
- ;; Make sure we can move up before destroying anything.
- (save-excursion (backward-up-list n) (backward-up-list))
- (let (open close) ;++ Is this a good idea?
- (let ((prefix
- (let ((end (point)))
- (paredit-ignore-sexp-errors
- (while (not (bobp)) (backward-sexp)))
- (prog1 (buffer-substring (point) end)
- (backward-up-list)
- (save-excursion (forward-sexp)
- (setq close (char-before))
- (delete-char -1))
- (setq open (char-after))
- (delete-region (point) end)
- ;; I'm not sure this makes sense...
- (if (not (eolp)) (just-one-space))))))
- (backward-up-list n)
- (paredit-insert-pair 1 open close 'goto-char)
- (insert prefix)
- ;; I'm not sure this makes sense either...
- (if (not (eolp)) (just-one-space))
- (save-excursion
- (backward-up-list)
- (paredit-ignore-sexp-errors (indent-sexp))))))
- (defun paredit-splice-string (argument)
- (let ((original-point (point))
- (start+end (paredit-string-start+end-points)))
- (let ((start (car start+end))
- (end (cdr start+end)))
- ;; START and END both lie before the respective quote
- ;; characters, which we want to delete; thus we increment START
- ;; by one to extract the string, and we increment END by one to
- ;; delete the string.
- (let* ((escaped-string
- (cond ((not (consp argument))
- (buffer-substring (1+ start) end))
- ((= 4 (car argument))
- (buffer-substring original-point end))
- (t
- (buffer-substring (1+ start) original-point))))
- (unescaped-string
- (paredit-unescape-string escaped-string)))
- (if (not unescaped-string)
- (error "Unspliceable string.")
- (save-excursion
- (goto-char start)
- (delete-region start (1+ end))
- (insert unescaped-string))
- (if (not (and (consp argument)
- (= 4 (car argument))))
- (goto-char (- original-point 1))))))))
-
- (defun paredit-unescape-string (string)
- (with-temp-buffer
- (insert string)
- (goto-char (point-min))
- (while (and (not (eobp))
- ;; nil -> no bound; t -> no errors.
- (search-forward "\\" nil t))
- (delete-char -1)
- (forward-char))
- (paredit-handle-sexp-errors
- (progn (scan-sexps (point-min) (point-max))
- (buffer-string))
- nil)))
- ;;;; Slurpage & Barfage
-
- (defun paredit-forward-slurp-sexp (&optional argument)
- "Add the S-expression following the current list into that list
- by moving the closing delimiter.
- Automatically reindent the newly slurped S-expression with respect to
- its new enclosing form.
- If in a string, move the opening double-quote forward by one
- S-expression and escape any intervening characters as necessary,
- without altering any indentation or formatting."
- (interactive "P")
- (save-excursion
- (cond ((paredit-in-comment-p)
- (error "Invalid context for slurping S-expressions."))
- ((numberp argument)
- (if (< argument 0)
- (paredit-forward-barf-sexp (- 0 argument))
- (while (< 0 argument)
- (paredit-forward-slurp-sexp)
- (setq argument (- argument 1)))))
- ((paredit-in-string-p)
- ;; If there is anything to slurp into the string, take that.
- ;; Otherwise, try to slurp into the enclosing list.
- (if (save-excursion
- (goto-char (paredit-enclosing-string-end))
- (paredit-handle-sexp-errors (progn (forward-sexp) nil)
- t))
- (progn
- (goto-char (paredit-enclosing-string-end))
- (paredit-forward-slurp-into-list argument))
- (paredit-forward-slurp-into-string argument)))
- (t
- (paredit-forward-slurp-into-list argument)))))
-
- (defun paredit-forward-slurp-into-list (&optional argument)
- (let ((nestedp nil))
- (save-excursion
- (up-list) ; Up to the end of the list to
- (let ((close (char-before))) ; save and delete the closing
- (delete-char -1) ; delimiter.
- (let ((start (point)))
- (catch 'return ; Go to the end of the desired
- (while t ; S-expression, going up a
- (paredit-handle-sexp-errors ; list if it's not in this,
- (progn (forward-sexp)
- (if argument
- (paredit-ignore-sexp-errors
- (while (not (eobp))
- (forward-sexp))))
- (throw 'return nil))
- (setq nestedp t)
- (up-list)
- (setq close ; adjusting for mixed
- (prog1 (char-before) ; delimiters as necessary,
- (delete-char -1)
- (insert close))))))
- (insert close) ; to insert that delimiter.
- (indent-region start (point) nil))))
- (if (and (not nestedp)
- (eq (save-excursion (paredit-skip-whitespace nil) (point))
- (save-excursion (backward-up-list) (forward-char) (point)))
- (eq (save-excursion (forward-sexp) (backward-sexp) (point))
- (save-excursion (paredit-skip-whitespace t) (point))))
- (delete-region (save-excursion (paredit-skip-whitespace nil) (point))
- (save-excursion (paredit-skip-whitespace t) (point))))))
- (defun paredit-forward-slurp-into-string (&optional argument)
- (let ((start (paredit-enclosing-string-start))
- (end (paredit-enclosing-string-end)))
- (goto-char end)
- ;; Signal any errors that we might get first, before mucking with
- ;; the buffer's contents.
- (save-excursion (forward-sexp))
- (let ((close (char-before)))
- ;; Skip intervening whitespace if we're slurping into an empty
- ;; string. XXX What about nonempty strings?
- (if (and (= (+ start 2) end)
- (eq (save-excursion (paredit-skip-whitespace t) (point))
- (save-excursion (forward-sexp) (backward-sexp) (point))))
- (delete-region (- (point) 1)
- (save-excursion (paredit-skip-whitespace t) (point)))
- (delete-char -1))
- (paredit-forward-for-quote
- (save-excursion
- (forward-sexp)
- (if argument
- (while (paredit-handle-sexp-errors (progn (forward-sexp) t) nil)))
- (point)))
- (insert close))))
-
- (defun paredit-forward-barf-sexp (&optional argument)
- "Remove the last S-expression in the current list from that list
- by moving the closing delimiter.
- Automatically reindent the newly barfed S-expression with respect to
- its new enclosing form."
- (interactive "P")
- (paredit-lose-if-not-in-sexp 'paredit-forward-barf-sexp)
- (if (and (numberp argument) (< argument 0))
- (paredit-forward-slurp-sexp (- 0 argument))
- (let ((start (point)) (end nil))
- (save-excursion
- (up-list) ; Up to the end of the list to
- (let ((close (char-before))) ; save and delete the closing
- (delete-char -1) ; delimiter.
- (setq end (point))
- (paredit-ignore-sexp-errors ; Go back to where we want to
- (if (or (not argument) ; insert the delimiter.
- (numberp argument))
- (backward-sexp argument)
- (while (paredit-handle-sexp-errors
- (save-excursion (backward-sexp) (<= start (point)))
- nil)
- (backward-sexp))))
- (paredit-skip-whitespace nil) ; Skip leading whitespace.
- (cond ((bobp)
- ;++ We'll have deleted the close, but there's no open.
- ;++ Is that OK?
- (error "Barfing all subexpressions with no open-paren?"))
- ((paredit-in-comment-p) ; Don't put the close-paren in
- (newline))) ; a comment.
- (insert close))
- ;; Reindent all of the newly barfed S-expressions. Start at the
- ;; start of the first barfed S-expression, not at the close we
- ;; just inserted.
- (forward-sexp)
- (backward-sexp)
- (if (or (not argument) (numberp argument))
- (paredit-forward-and-indent argument)
- (indent-region (point) end))))))
- (defun paredit-backward-slurp-sexp (&optional argument)
- "Add the S-expression preceding the current list into that list
- by moving the closing delimiter.
- Automatically reindent the whole form into which new S-expression was
- slurped.
- If in a string, move the opening double-quote backward by one
- S-expression and escape any intervening characters as necessary,
- without altering any indentation or formatting."
- (interactive "P")
- (save-excursion
- (cond ((paredit-in-comment-p)
- (error "Invalid context for slurping S-expressions."))
- ((numberp argument)
- (if (< argument 0)
- (paredit-backward-barf-sexp (- 0 argument))
- (while (< 0 argument)
- (paredit-backward-slurp-sexp)
- (setq argument (- argument 1)))))
- ((paredit-in-string-p)
- ;; If there is anything to slurp into the string, take that.
- ;; Otherwise, try to slurp into the enclosing list.
- (if (save-excursion
- (goto-char (paredit-enclosing-string-start))
- (paredit-handle-sexp-errors (progn (backward-sexp) nil)
- t))
- (progn
- (goto-char (paredit-enclosing-string-start))
- (paredit-backward-slurp-into-list argument))
- (paredit-backward-slurp-into-string argument)))
- (t
- (paredit-backward-slurp-into-list argument)))))
-
- (defun paredit-backward-slurp-into-list (&optional argument)
- (let ((nestedp nil))
- (save-excursion
- (backward-up-list)
- (let ((open (char-after)))
- (delete-char +1)
- (catch 'return
- (while t
- (paredit-handle-sexp-errors
- (progn (backward-sexp)
- (if argument
- (paredit-ignore-sexp-errors
- (while (not (bobp))
- (backward-sexp))))
- (throw 'return nil))
- (setq nestedp t)
- (backward-up-list)
- (setq open
- (prog1 (char-after)
- (save-excursion (insert open) (delete-char +1)))))))
- (insert open))
- ;; Reindent the line at the beginning of wherever we inserted the
- ;; opening delimiter, and then indent the whole S-expression.
- (backward-up-list)
- (lisp-indent-line)
- (indent-sexp))
- ;; If we slurped into an empty list, don't leave dangling space:
- ;; (foo |).
- (if (and (not nestedp)
- (eq (save-excursion (paredit-skip-whitespace nil) (point))
- (save-excursion (backward-sexp) (forward-sexp) (point)))
- (eq (save-excursion (up-list) (backward-char) (point))
- (save-excursion (paredit-skip-whitespace t) (point))))
- (delete-region (save-excursion (paredit-skip-whitespace nil) (point))
- (save-excursion (paredit-skip-whitespace t) (point))))))
- (defun paredit-backward-slurp-into-string (&optional argument)
- (let ((start (paredit-enclosing-string-start))
- (end (paredit-enclosing-string-end)))
- (goto-char start)
- ;; Signal any errors that we might get first, before mucking with
- ;; the buffer's contents.
- (save-excursion (backward-sexp))
- (let ((open (char-after))
- (target (point)))
- ;; Skip intervening whitespace if we're slurping into an empty
- ;; string. XXX What about nonempty strings?
- (if (and (= (+ start 2) end)
- (eq (save-excursion (paredit-skip-whitespace nil) (point))
- (save-excursion (backward-sexp) (forward-sexp) (point))))
- (delete-region (save-excursion (paredit-skip-whitespace nil) (point))
- (+ (point) 1))
- (delete-char +1))
- (backward-sexp)
- (if argument
- (paredit-ignore-sexp-errors
- (while (not (bobp))
- (backward-sexp))))
- (insert open)
- (paredit-forward-for-quote target))))
-
- (defun paredit-backward-barf-sexp (&optional argument)
- "Remove the first S-expression in the current list from that list
- by moving the closing delimiter.
- Automatically reindent the barfed S-expression and the form from which
- it was barfed."
- (interactive "P")
- (paredit-lose-if-not-in-sexp 'paredit-backward-barf-sexp)
- (if (and (numberp argument) (< argument 0))
- (paredit-backward-slurp-sexp (- 0 argument))
- (let ((end (make-marker)))
- (set-marker end (point))
- (save-excursion
- (backward-up-list)
- (let ((open (char-after)))
- (delete-char +1)
- (paredit-ignore-sexp-errors
- (paredit-forward-and-indent
- (if (or (not argument) (numberp argument))
- argument
- (let ((n 0))
- (save-excursion
- (while (paredit-handle-sexp-errors
- (save-excursion
- (forward-sexp)
- (<= (point) end))
- nil)
- (forward-sexp)
- (setq n (+ n 1))))
- n))))
- (while (progn (paredit-skip-whitespace t) (eq (char-after) ?\; ))
- (forward-line 1))
- (if (eobp)
- ;++ We'll have deleted the close, but there's no open.
- ;++ Is that OK?
- (error "Barfing all subexpressions with no close-paren?"))
- ;** Don't use `insert' here. Consider, e.g., barfing from
- ;** (foo|)
- ;** and how `save-excursion' works.
- (insert-before-markers open))
- (backward-up-list)
- (lisp-indent-line)
- (indent-sexp)))))
- ;;;; Splitting & Joining
-
- (defun paredit-split-sexp ()
- "Split the list or string the point is on into two."
- (interactive)
- (cond ((paredit-in-string-p)
- (insert "\"")
- (save-excursion (insert " \"")))
- ((or (paredit-in-comment-p)
- (paredit-in-char-p))
- (error "Invalid context for splitting S-expression."))
- (t
- (let ((open (save-excursion (backward-up-list) (char-after)))
- (close (save-excursion (up-list) (char-before))))
- (delete-horizontal-space)
- (insert close)
- (save-excursion
- (insert ?\ )
- (insert open)
- (backward-char)
- (indent-sexp))))))
-
- (defun paredit-join-sexps ()
- "Join the S-expressions adjacent on either side of the point.
- Both must be lists, strings, or atoms; error if there is a mismatch."
- (interactive)
- (cond ((paredit-in-comment-p) (error "Can't join S-expressions in comment."))
- ((paredit-in-string-p) (error "Nothing to join in a string."))
- ((paredit-in-char-p) (error "Can't join characters.")))
- (let ((left-point (paredit-point-at-sexp-end))
- (right-point (paredit-point-at-sexp-start)))
- (let ((left-char (char-before left-point))
- (right-char (char-after right-point)))
- (let ((left-syntax (char-syntax left-char))
- (right-syntax (char-syntax right-char)))
- (cond ((< right-point left-point)
- (error "Can't join a datum with itself."))
- ((and (eq left-syntax ?\) )
- (eq right-syntax ?\( )
- (eq left-char (matching-paren right-char))
- (eq right-char (matching-paren left-char)))
- (paredit-join-lists-internal left-point right-point)
- (paredit-preserving-column
- (save-excursion
- (backward-up-list)
- (indent-sexp))))
- ((and (eq left-syntax ?\" )
- (eq right-syntax ?\" ))
- ;; Delete any intermediate formatting.
- (delete-region (1- left-point) (1+ right-point)))
- ((and (memq left-syntax '(?w ?_)) ; Word or symbol
- (memq right-syntax '(?w ?_)))
- (delete-region left-point right-point))
- (t (error "Mismatched S-expressions to join.")))))))
- (defun paredit-join-lists-internal (left-point right-point)
- (save-excursion
- ;; Leave intermediate formatting alone.
- (goto-char right-point)
- (delete-char +1)
- (goto-char left-point)
- (delete-char -1)
- ;; Kludge: Add an extra space in several conditions.
- (if (or
- ;; (foo)| ;x\n(bar) => (foo | ;x\nbar), not (foo| ;x\nbar).
- (and (not (eolp))
- (save-excursion
- (paredit-skip-whitespace t (point-at-eol))
- (eq (char-after) ?\;)))
- ;; (foo)|(bar) => (foo| bar), not (foo|bar).
- (and (= left-point right-point)
- (not (or (eq ?\ (char-syntax (char-before)))
- (eq ?\ (char-syntax (char-after)))))))
- (insert ?\ ))))
-
- ;++ How ought paredit-join to handle comments intervening symbols or strings?
- ;++ Idea:
- ;++
- ;++ "foo" | ;bar
- ;++ "baz" ;quux
- ;++
- ;++ =>
- ;++
- ;++ "foo|baz" ;bar
- ;++ ;quux
- ;++
- ;++ The point should stay where it is relative to the comments, and the
- ;++ the comments' columns should all be preserved, perhaps. Hmmmm...
- ;++ What about this?
- ;++
- ;++ "foo" ;bar
- ;++ | ;baz
- ;++ "quux" ;zot
-
- ;++ Should rename:
- ;++ paredit-point-at-sexp-start -> paredit-start-of-sexp-after-point
- ;++ paredit-point-at-sexp-end -> paredit-end-of-sexp-before-point
- ;;;; Variations on the Lurid Theme
-
- ;;; I haven't the imagination to concoct clever names for these.
-
- (defun paredit-add-to-previous-list ()
- "Add the S-expression following point to the list preceding point."
- (interactive)
- (paredit-lose-if-not-in-sexp 'paredit-add-to-previous-list)
- (save-excursion
- (down-list -1) ;++ backward-down-list...
- (paredit-forward-slurp-sexp)))
-
- (defun paredit-add-to-next-list ()
- "Add the S-expression preceding point to the list following point.
- If no S-expression precedes point, move up the tree until one does."
- (interactive)
- (paredit-lose-if-not-in-sexp 'paredit-add-to-next-list)
- (save-excursion
- (down-list)
- (paredit-backward-slurp-sexp)))
-
- (defun paredit-join-with-previous-list ()
- "Join the list the point is on with the previous list in the buffer."
- (interactive)
- (paredit-lose-if-not-in-sexp 'paredit-join-with-previous-list)
- (save-excursion
- (while (paredit-handle-sexp-errors (save-excursion (backward-sexp) nil)
- (backward-up-list)
- t))
- (paredit-join-sexps)))
-
- (defun paredit-join-with-next-list ()
- "Join the list the point is on with the next list in the buffer."
- (interactive)
- (paredit-lose-if-not-in-sexp 'paredit-join-with-next-list)
- (save-excursion
- (while (paredit-handle-sexp-errors (save-excursion (forward-sexp) nil)
- (up-list)
- t))
- (paredit-join-sexps)))
- ;;;; Utilities
-
- (defun paredit-in-string-escape-p ()
- "True if the point is on a character escape of a string.
- This is true only if the character is preceded by an odd number of
- backslashes.
- This assumes that `paredit-in-string-p' has already returned true."
- (let ((oddp nil))
- (save-excursion
- (while (eq (char-before) ?\\ )
- (setq oddp (not oddp))
- (backward-char)))
- oddp))
-
- (defun paredit-in-char-p (&optional position)
- "True if point is on a character escape outside a string."
- (save-excursion
- (goto-char (or position (point)))
- (paredit-in-string-escape-p)))
-
- (defun paredit-skip-whitespace (trailing-p &optional limit)
- "Skip past any whitespace, or until the point LIMIT is reached.
- If TRAILING-P is nil, skip leading whitespace; otherwise, skip trailing
- whitespace."
- (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.
-
- (defalias 'paredit-region-active-p
- (xcond ((paredit-xemacs-p) 'region-active-p)
- ((paredit-gnu-emacs-p)
- (lambda ()
- (and mark-active transient-mark-mode)))))
-
- (defun paredit-hack-kill-region (start end)
- "Kill the region between START and END.
- Do not append to any current kill, and
- do not let the next kill append to this one."
- (interactive "r") ;Eh, why not?
- ;; KILL-REGION sets THIS-COMMAND to tell the next kill that the last
- ;; command was a kill. It also checks LAST-COMMAND to see whether it
- ;; should append. If we bind these locally, any modifications to
- ;; THIS-COMMAND will be masked, and it will not see LAST-COMMAND to
- ;; indicate that it should append.
- (let ((this-command nil)
- (last-command nil))
- (kill-region start end)))
- ;;;;; Reindentation utilities
-
- ;++ Should `paredit-indent-sexps' and `paredit-forward-and-indent' use
- ;++ `paredit-indent-region' rather than `indent-region'?
-
- (defun paredit-indent-sexps ()
- "If in a list, indent all following S-expressions in the list."
- (let* ((start (point))
- (end (paredit-handle-sexp-errors (progn (up-list) (point)) nil)))
- (if end
- (indent-region start end nil))))
-
- (defun paredit-forward-and-indent (&optional n)
- "Move forward by N S-expressions, indenting them with `indent-region'."
- (let ((start (point)))
- (forward-sexp n)
- (indent-region start (point) nil)))
-
- (defun paredit-indent-region (start end)
- "Indent the region from START to END.
- Don't reindent the line starting at START, however."
- (if (not (<= start end))
- (error "Incorrectly related points: %S, %S" start end))
- (save-excursion
- (goto-char start)
- (let ((bol (point-at-bol)))
- ;; Skip all S-expressions that end on the starting line, but
- ;; don't go past `end'.
- (if (and (save-excursion (goto-char end) (not (eq bol (point-at-bol))))
- (paredit-handle-sexp-errors
- (catch 'exit
- (while t
- (save-excursion
- (forward-sexp)
- (if (not (eq bol (point-at-bol)))
- (throw 'exit t))
- (if (not (< (point) end))
- (throw 'exit nil)))
- (forward-sexp)))
- nil))
- (progn
- ;; Point is still on the same line, but precedes an
- ;; S-expression that ends on a different line.
- (if (not (eq bol (point-at-bol)))
- (error "Internal error -- we moved forward a line!"))
- (goto-char (+ 1 (point-at-eol)))
- (if (not (<= (point) end))
- (error "Internal error -- we frobnitzed the garfnut!"))
- (indent-region (point) end nil))))))
- ;;;;; S-expression Parsing Utilities
-
- ;++ These routines redundantly traverse S-expressions a great deal.
- ;++ If performance issues arise, this whole section will probably have
- ;++ to be refactored to preserve the state longer, like paredit.scm
- ;++ does, rather than to traverse the definition N times for every key
- ;++ stroke as it presently does.
-
- (defun paredit-current-parse-state ()
- "Return parse state of point from beginning of defun."
- (let ((point (point)))
- (beginning-of-defun)
- ;; Calling PARSE-PARTIAL-SEXP will advance the point to its second
- ;; argument (unless parsing stops due to an error, but we assume it
- ;; won't in paredit-mode).
- (parse-partial-sexp (point) point)))
-
- (defun paredit-in-string-p (&optional state)
- "True if the parse state is within a double-quote-delimited string.
- If no parse state is supplied, compute one from the beginning of the
- defun to the point."
- ;; 3. non-nil if inside a string (the terminator character, really)
- (and (nth 3 (or state (paredit-current-parse-state)))
- t))
-
- (defun paredit-string-start+end-points (&optional state)
- "Return a cons of the points of open and close quotes of the string.
- The string is determined from the parse state STATE, or the parse state
- from the beginning of the defun to the point.
- This assumes that `paredit-in-string-p' has already returned true, i.e.
- that the point is already within a string."
- (save-excursion
- ;; 8. character address of start of comment or string; nil if not
- ;; in one
- (let ((start (nth 8 (or state (paredit-current-parse-state)))))
- (goto-char start)
- (forward-sexp 1)
- (cons start (1- (point))))))
-
- (defun paredit-enclosing-string-start ()
- (car (paredit-string-start+end-points)))
-
- (defun paredit-enclosing-string-end ()
- (+ 1 (cdr (paredit-string-start+end-points))))
-
- (defun paredit-enclosing-list-start ()
- (save-excursion
- (backward-up-list)
- (point)))
-
- (defun paredit-enclosing-list-end ()
- (save-excursion
- (up-list)
- (point)))
- (defun paredit-in-comment-p (&optional state)
- "True if parse state STATE is within a comment.
- If no parse state is supplied, compute one from the beginning of the
- defun to the point."
- ;; 4. nil if outside a comment, t if inside a non-nestable comment,
- ;; else an integer (the current comment nesting)
- (and (nth 4 (or state (paredit-current-parse-state)))
- t))
-
- (defun paredit-prefix-numeric-value (argument)
- ;++ Kludgerific.
- (cond ((integerp argument) argument)
- ((eq argument '-) -1)
- ((consp argument)
- (cond ((equal argument '(4)) (paredit-count-sexps-forward)) ;C-u
- ((equal argument '(16)) (paredit-count-sexps-backward)) ;C-u C-u
- (t (error "Invalid prefix argument: %S" argument))))
- ((paredit-region-active-p)
- (save-excursion
- (save-restriction
- (narrow-to-region (region-beginning) (region-end))
- (cond ((= (point) (point-min)) (paredit-count-sexps-forward))
- ((= (point) (point-max)) (paredit-count-sexps-backward))
- (t
- (error "Point %S is not start or end of region: %S..%S"
- (point) (region-beginning) (region-end)))))))
- (t 1)))
-
- (defun paredit-count-sexps-forward ()
- (save-excursion
- (let ((n 0) (p nil)) ;hurk
- (paredit-ignore-sexp-errors
- (while (setq p (scan-sexps (point) +1))
- (goto-char p)
- (setq n (+ n 1))))
- n)))
-
- (defun paredit-count-sexps-backward ()
- (save-excursion
- (let ((n 0) (p nil)) ;hurk
- (paredit-ignore-sexp-errors
- (while (setq p (scan-sexps (point) -1))
- (goto-char p)
- (setq n (+ n 1))))
- n)))
- (defun paredit-point-at-sexp-boundary (n)
- (cond ((< n 0) (paredit-point-at-sexp-start))
- ((= n 0) (point))
- ((> n 0) (paredit-point-at-sexp-end))))
-
- (defun paredit-point-at-sexp-start ()
- (save-excursion
- (forward-sexp)
- (backward-sexp)
- (point)))
-
- (defun paredit-point-at-sexp-end ()
- (save-excursion
- (backward-sexp)
- (forward-sexp)
- (point)))
-
- (defun paredit-lose-if-not-in-sexp (command)
- (if (or (paredit-in-string-p)
- (paredit-in-comment-p)
- (paredit-in-char-p))
- (error "Invalid context for command `%s'." command)))
-
- (defun paredit-check-region (start end)
- "Signal an error if text between `start' and `end' is unbalanced."
- ;; `narrow-to-region' will move the point, so avoid calling it if we
- ;; don't need to. We don't want to use `save-excursion' because we
- ;; want the point to move if `check-parens' reports an error.
- (if (not (paredit-region-ok-p start end))
- (save-restriction
- (narrow-to-region start end)
- (check-parens))))
-
- (defun paredit-region-ok-p (start end)
- "Return true iff the region between `start' and `end' is balanced.
- This is independent of context -- it doesn't check what state the
- text at `start' is in."
- (save-excursion
- (paredit-handle-sexp-errors
- (progn
- (save-restriction
- (narrow-to-region start end)
- (scan-sexps (point-min) (point-max)))
- t)
- nil)))
-
- (defun paredit-current-indentation ()
- (save-excursion
- (back-to-indentation)
- (current-column)))
-
- (defun paredit-restore-column (column indentation)
- ;; Preserve the point's position either in the indentation or in the
- ;; code: if on code, move with the code; if in indentation, leave it
- ;; in the indentation, either where it was (if still on indentation)
- ;; or at the end of the indentation (if the code moved far enough
- ;; left).
- (let ((indentation* (paredit-current-indentation)))
- (goto-char
- (+ (point-at-bol)
- (cond ((not (< column indentation))
- (+ column (- indentation* indentation)))
- ((<= indentation* column) indentation*)
- (t column))))))
- ;;;; Initialization
-
- (paredit-define-keys)
- (paredit-annotate-mode-with-examples)
- (paredit-annotate-functions-with-examples)
-
- (provide 'paredit)
-
- ;;; Local Variables:
- ;;; outline-regexp: "\n;;;;+"
- ;;; End:
-
- ;;; paredit.el ends here
|