diff --git a/dumbparens-tests.el b/dumbparens-tests.el index 5b73657..fd33099 100644 --- a/dumbparens-tests.el +++ b/dumbparens-tests.el @@ -512,6 +512,173 @@ :keys "C-k" :after "(foo bar\\|)") +(dumbparens-test wrap-symbol + "You can wrap a symbol with M-(" + :before "foo b|ar baz" + :keys "M-(" + :after "foo (|bar) baz") + +(dumbparens-test wrap-list + "You can wrap a list with M-(" + :before "foo| (bar baz) quux" + :keys "M-(" + :after "foo(| (bar baz)) quux") + +(dumbparens-test wrap-string + "You can wrap a string with M-(" + :before "foo| \"bar baz\" quux" + :keys "M-(" + :after "foo(| \"bar baz\") quux") + +(dumbparens-test wrap-string-from-inside + "You can wrap a string from inside of it with M-(" + :before "foo \"bar| baz\" quux" + :keys "M-(" + :after "foo (|\"bar baz\") quux") + +(dumbparens-test wrap-symbol-with-string + "You can wrap a symbol with a string" + :before "foo | bar baz" + :keys "M-\"" + :after "foo \" bar\" baz") + +(dumbparens-test wrap-string-with-string-from-inside + "You can't wrap a string in another string" + :before "foo \"bar| baz\" quux" + :keys "M-\"" + :after "foo \"|bar baz\" quux") + +(dumbparens-test wrap-two-symbols + "You can use a prefix argument to wrap multiple forms with M-(" + :before "foo| bar baz quux" + :keys "C-u 2 M-(" + :after "foo( bar baz) quux") + +(dumbparens-test wrap-too-many-symbols + "Large prefix arg wraps as many symbols as possible" + :before "foo (bar |baz quux) lol" + :keys "C-u 4 M-(" + :after "foo (bar (|baz quux)) lol") + +(dumbparens-test wrap-backwards + "You can use M-( with a negative prefix arg" + :before "foo bar |baz" + :keys "C-u -1 M-(" + :after "foo (bar |)baz") + +(dumbparens-test wrap-backwards-from-inside + "You can use M-( with negative prefix arg on the current symbol" + :before "foo b|ar baz" + :keys "C-u -1 M-(" + :after "foo (bar) baz") + +(dumbparens-test wrap-on-empty-buffer + "You can use M-( even with nothing to wrap" + :before "|" + :keys "M-(" + :after "(|)") + +(dumbparens-test wrap-nothing + "You can use a zero prefix arg on M-( to wrap zero forms" + :before "foo b|ar baz" + :keys "C-u 0 M-(" + :after "foo (|)bar baz") + +(dumbparens-test wrap-through-comment + "M-( will skip comments to find forms to wrap" + :before "foo| ; bar\nbaz" + :keys "M-(" + :after "foo(| ;bar\nbaz)") + +(dumbparens-test wrap-from-within-comment + "M-( will jump out of comments before wrapping" + :before "foo ; b|ar\nbaz" + :keys "M-(" + :after "foo ; bar\n(baz)") + +(dumbparens-test wrap-with-square + "M-[ wraps with square brackets" + :before "|foo" + :keys "M-[" + :after "[|foo]") + +(dumbparens-test wrap-with-curly + "C-{ wraps with curly braces" + :mode c + :before "|foo" + :keys "C-{" + :after "{|foo}") + +(dumbparens-test wrap-with-single-quote + "M-' wraps with single quotes" + :mode python + :before "|foo" + :keys "M-'" + :after "'|foo'") + +(dumbparens-test wrap-string-and-symbol-with-string + "Combine a symbol backwards into a string with M-\"" + :before "|\"foo\" bar" + :keys "C-u 2 M-\"" + :after "\"|foo bar\"") + +(dumbparens-test wrap-symbol-and-string-with-string + "Combine a symbol forwards into a string with M-\"" + :before "|foo \"bar\"" + :keys "C-u 2 M-\"" + :after "\"|foo bar\"") + +(dumbparens-test wrap-string-and-string-with-string + "Combine two strings with M-\"" + :before "|\"foo\" \"bar\"" + :keys "C-u 2 M-\"" + :after "\"foo bar\"") + +(dumbparens-test wrap-extending-math-bidirectionally + "Use C-( to wrap multiple forms with math mode in TeX" + :mode latex + :before "|foo $bar$ baz" + :keys "C-u 3 C-( $" + :after "$|foo bar baz$") + +(dumbparens-test wrap-nested-string + "Wrap a list containing a string with M-\"" + :before "|(foo \"bar\" baz)" + :keys "M-\"" + :after "\"|(foo bar baz)\"") + +(dumbparens-test wrap-string-with-different-string + "Use M-\" to change quote style" + :mode python + :before "\"foo| bar\"" + :keys "M-'" + :after "'|foo bar'") + +(dumbparens-test wrap-string-handles-embedded-quotes + "When changing quote style, embedded quotes are escaped" + :mode python + :before "\"Mo'|at\"" + :keys "M-'" + :after "|'Mo\\'|at'") + +(dumbparens-test wrap-string-handles-embedded-escaped-quotes + "When changing quote style, embedded quotes are not re-escaped" + :mode python + :before "'Mo\\|'|at'" + :keys "M-\"" + :after "\"|Mo\\\"|at\"") + +(dumbparens-test wrap-round-to-square + "You can use M-[ with prefix arg to replace existing parens" + :before "foo (bar| baz) quux" + :keys "C-u M-[" + :after "foo [bar| baz] quux") + +(dumbparens-test ) + +;; multiple C-u's +;; M-) + (provide 'dumbparens-test) ;; Local Variables: diff --git a/dumbparens.el b/dumbparens.el index bd5f9b1..7b37baa 100644 --- a/dumbparens.el +++ b/dumbparens.el @@ -55,10 +55,14 @@ ("C-M-n" . dumbparens-up-forward) ("C-M-u" . dumbparens-up-backward) ("C-k" . dumbparens-kill-line) - ("C-(" . dumbparens-wrap) - ("M-(" . dumbparens-wrap-round) - ("M-[" . dumbparens-wrap-square) - ("C-{" . dumbparens-wrap-curly) + ("C-(" . dumbparens-wrap-forward) + ("C-)" . dumbparens-wrap-backward) + ("M-(" . dumbparens-wrap-round-forward) + ("M-)" . dumbparens-wrap-round-backward) + ("M-[" . dumbparens-wrap-square-forward) + ("M-]" . dumbparens-wrap-square-backward) + ("C-{" . dumbparens-wrap-curly-forward) + ("C-}" . dumbparens-wrap-curly-backward) ("M-\"" . dumbparens-wrap-double-quote) ("M-'" . dumbparens-wrap-single-quote) ("M-s" . dumbparens-splice) @@ -476,53 +480,175 @@ newline unless point is at the end of the line already." ;; Depth-changing commands -(defun dumbparens-wrap (char &optional n) +(defun dumbparens-wrap-forward (char &optional n) "Wrap following form in paren CHAR and its matched pair. With argument N, wrap that many forms. With negative N, wrap -preceding form(s)." - (interactive "c\np") - (setq n (or n 1)) - (ignore char)) +preceding form(s). With \\[universal-argument], replace enclosing +parens instead of inserting new ones. With multiple +\\[universal-argument]'s, go up that many levels before +replacing." + (interactive "c\nP") + (cl-block nil + (cl-letf* ((replace (listp n)) + (n (if replace + (/ (logb (car n)) 2) + (prefix-numeric-value n))) + (type (pcase (char-syntax char) + (`?\( 'paren) + (`?\" 'string) + (`?$ 'delim) + (`?| 'generic-string) + (_ (user-error + "Character %c is not a paren in %S" + char major-mode)))) + (closer (or (matching-paren char) char)) + (state (syntax-ppss))) + (cond + ;; If in string, move to its beginning or end. + ((nth 3 state) + (if (>= n 0) + (goto-char (nth 8 state)) + (dumbparens--up-string-forward))) + ;; If in comment, move to its beginning or end. + ((nth 4 state) + (if (>= n 0) + (condition-case _ + (dumbparens--end-of-comment) + (end-of-buffer (cl-return))) + (goto-char (nth 8 state)))) + ;; Otherwise, move to beginning or end of current symbol (if + ;; any). + (t + (if (>= n 0) + (dumbparens--beginning-of-symbol) + (dumbparens--end-of-symbol)))) + ;; wrapping with PARENS: no need to mess with anything + ;; + ;; wrapping with STRING: kill all the strings and generic + ;; strings first, escape string quotes of the type being added, + ;; stop at comment + ;; + ;; wrapping with DELIMS: kill all the paired delimiters of the + ;; type being added first + ;; + ;; wrapping with GENERIC STRING: kill all the strings and + ;; generic strings first, escape generic string quotes, stop at + ;; comment + ))) + +(defun dumbparens-wrap-backward (char &optional n) + "Wrap preceding form in paren CHAR and its matched pair. +With argument N, wrap that many forms. With negative N, wrap +following form(s). With \\[universal-argument], replace enclosing +parens instead of inserting new ones. With multiple +\\[universal-argument]'s, go up that many levels before +replacing." + (interactive "P") + (dumbparens-wrap-forward + char + (if (listp n) + n + (- (prefix-numeric-value n))))) -(defun dumbparens-wrap-round (&optional n) +(defun dumbparens-wrap-round-forward (&optional n) "Wrap following form in pair of round parens. With argument N, wrap that many forms. With negative N, wrap -preceding form(s)." +preceding form(s). With \\[universal-argument], replace enclosing +parens instead of inserting new ones. With multiple +\\[universal-argument]'s, go up that many levels before +replacing." (interactive "p") - (setq n (or n 1)) - (dumbparens-wrap ?\( n)) + (dumbparens-wrap-forward ?\( n)) -(defun dumbparens-wrap-square (&optional n) +(defun dumbparens-wrap-round-backward (&optional n) + "Wrap preceding form in pair of round parens. +With argument N, wrap that many forms. With negative N, wrap +following form(s). With \\[universal-argument], replace enclosing +parens instead of inserting new ones. With multiple +\\[universal-argument]'s, go up that many levels before +replacing." + (interactive "p") + (dumbparens-wrap-backward ?\( n)) + +(defun dumbparens-wrap-square-forward (&optional n) "Wrap following form in pair of square brackets. With argument N, wrap that many forms. With negative N, wrap -preceding form(s)." +preceding form(s). With \\[universal-argument], replace enclosing +parens instead of inserting new ones. With multiple +\\[universal-argument]'s, go up that many levels before +replacing." (interactive "p") - (setq n (or n 1)) - (dumbparens-wrap ?\[ n)) + (dumbparens-wrap-forward ?\[ n)) + +(defun dumbparens-wrap-square-backward (&optional n) + "Wrap preceding form in pair of square brackets. +With argument N, wrap that many forms. With negative N, wrap +following form(s). With \\[universal-argument], replace enclosing +parens instead of inserting new ones. With multiple +\\[universal-argument]'s, go up that many levels before +replacing." + (interactive "p") + (dumbparens-wrap-backward ?\[ n)) -(defun dumbparens-wrap-curly (&optional n) +(defun dumbparens-wrap-curly-forward (&optional n) "Wrap following form in pair of curly braces. With argument N, wrap that many forms. With negative N, wrap -preceding form(s)." +preceding form(s). With \\[universal-argument], replace enclosing +parens instead of inserting new ones. With multiple +\\[universal-argument]'s, go up that many levels before +replacing." (interactive "p") - (setq n (or n 1)) - (dumbparens-wrap ?{ n)) + (dumbparens-wrap-forward ?{ n)) -(defun dumbparens-wrap-double-quote (&optional n) +(defun dumbparens-wrap-curly-backward (&optional n) + "Wrap preceding form in pair of curly braces. +With argument N, wrap that many forms. With negative N, wrap +following form(s). With \\[universal-argument], replace enclosing +parens instead of inserting new ones. With multiple +\\[universal-argument]'s, go up that many levels before +replacing." + (interactive "p") + (dumbparens-wrap-backward ?{ n)) + +(defun dumbparens-wrap-double-quote-forward (&optional n) "Wrap following form in pair of double quotes. With argument N, wrap that many forms. With negative N, wrap -preceding form(s)." +preceding form(s). With \\[universal-argument], replace enclosing +parens instead of inserting new ones. With multiple +\\[universal-argument]'s, go up that many levels before +replacing." (interactive "p") - (setq n (or n 1)) - (dumbparens-wrap ?\" n)) + (dumbparens-wrap-forward ?\" n)) -(defun dumbparens-wrap-single-quote (&optional n) +(defun dumbparens-wrap-double-quote-backward (&optional n) + "Wrap preceding form in pair of double quotes. +With argument N, wrap that many forms. With negative N, wrap +following form(s). With \\[universal-argument], replace enclosing +parens instead of inserting new ones. With multiple +\\[universal-argument]'s, go up that many levels before +replacing." + (interactive "p") + (dumbparens-wrap-backward ?\" n)) + +(defun dumbparens-wrap-single-quote-forward (&optional n) "Wrap following form in pair of single quotes. With argument N, wrap that many forms. With negative N, wrap -preceding form(s)." +preceding form(s). With \\[universal-argument], replace enclosing +parens instead of inserting new ones. With multiple +\\[universal-argument]'s, go up that many levels before +replacing." (interactive "p") - (setq n (or n 1)) - (dumbparens-wrap ?\' n)) + (dumbparens-wrap-forward ?\' n)) + +(defun dumbparens-wrap-single-quote-backward (&optional n) + "Wrap preceding form in pair of single quotes. +With argument N, wrap that many forms. With negative N, wrap +following form(s). With \\[universal-argument], replace enclosing +parens instead of inserting new ones. With multiple +\\[universal-argument]'s, go up that many levels before +replacing." + (interactive "p") + (dumbparens-wrap-backward ?\' n)) (defun dumbparens-splice (&optional n) "Remove parens of enclosing form. With argument, repeat N times. @@ -637,7 +763,6 @@ See `dumbparens-mode-bindings'.") ;; Local Variables: ;; indent-tabs-mode: nil -;; outline-regexp: ";;;;* " ;; sentence-end-double-space: nil ;; End: