From f35d2dba112815af9cb9a08a7d20a8611a1096cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Wed, 1 Aug 2012 23:38:19 +0100 Subject: [PATCH] Closes #271: save backquoted elisp and re-insert at end of expansion --- yasnippet-tests.el | 65 ++++++++++++++++++++++++++++++++++++++++++++-- yasnippet.el | 42 +++++++++++++++++++++++------- 2 files changed, 95 insertions(+), 12 deletions(-) diff --git a/yasnippet-tests.el b/yasnippet-tests.el index 3aa1ecd..99a62c4 100644 --- a/yasnippet-tests.el +++ b/yasnippet-tests.el @@ -112,17 +112,78 @@ (should (string= (yas--buffer-contents) "blabla}ble")) (should (string= (yas-field-value 1) "bla}")))) +(ert-deftest escape-backslashes () + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "bla\\ble") + (should (string= (yas--buffer-contents) "bla\\ble")))) + (ert-deftest escape-some-elisp-with-strings () + "elisp with strings and unbalance parens inside it" (with-temp-buffer (yas-minor-mode 1) ;; The rules here is: to output a literal `"' you need to escape ;; it with one backslash. You don't need to escape them in ;; embedded elisp. - (yas-expand-snippet "soon \\\"`(concat (upcase \"(my arms)\")\"\\\" were all around her\")`") - (should (string= (yas--buffer-contents) "soon \"MY ARMS\" were all around her")))) + (yas-expand-snippet "soon \\\"`(concat (upcase \"(my arms\")\"\\\" were all around her\")`") + (should (string= (yas--buffer-contents) "soon \"(MY ARMS\" were all around her")))) +(ert-deftest escape-some-elisp-with-backslashes () + (with-temp-buffer + (yas-minor-mode 1) + ;; And the rule here is: to output a literal `\' inside a string + ;; inside embedded elisp you need a total of six `\' + (yas-expand-snippet "bla`(upcase \"hey\\\\\\yo\")`ble") + (should (string= (yas--buffer-contents) "blaHEY\\YOble")))) +(ert-deftest be-careful-when-escaping-in-yas-selected-text () + (with-temp-buffer + (yas-minor-mode 1) + (let ((yas/selected-text "He\\\\o world!")) + (yas-expand-snippet "Look ma! `(yas/selected-text)`") + (should (string= (yas--buffer-contents) "Look ma! He\\\\o world!"))) + (yas-exit-all-snippets) + (erase-buffer) + (let ((yas/selected-text "He\"o world!")) + (yas-expand-snippet "Look ma! `(yas/selected-text)`") + (should (string= (yas--buffer-contents) "Look ma! He\"o world!"))) + (yas-exit-all-snippets) + (erase-buffer) + (let ((yas/selected-text "He\"\)\\o world!")) + (yas-expand-snippet "Look ma! `(yas/selected-text)`") + (should (string= (yas--buffer-contents) "Look ma! He\"\)\\o world!"))) + (yas-exit-all-snippets) + (erase-buffer)))) +(ert-deftest be-careful-when-escaping-in-yas-selected-text-2 () + (with-temp-buffer + (let ((yas/selected-text "He)}o world!")) + (yas-expand-snippet "Look ma! ${1:`(yas/selected-text)`} OK?") + (should (string= (yas--buffer-contents) "Look ma! He)}o world! OK?"))))) + +(ert-deftest mirror-transformation () + (with-temp-buffer + (yas-minor-mode 1) + (let ((snippet "${1:`(concat \"foo\" \"bar\")`} ${1:$(concat (upcase yas/text) \"baz\")}")) + (yas-expand-snippet snippet) + (should (string= (yas--buffer-contents) "foobar FOOBARbaz")) + (yas-exit-all-snippets) + (erase-buffer) + (yas-expand-snippet snippet) + (ert-simulate-command `(yas-mock-insert "bla")) + (should (string= (yas--buffer-contents) "bla BLAbaz"))))) + +(ert-deftest primary-field-transformation () + (with-temp-buffer + (yas-minor-mode 1) + ;; The rules here is: to output a literal `"' you need to escape + ;; it with one backslash. You don't need to escape them in + ;; embedded elisp. + (let ((snippet "${1:$$(upcase yas/text)}${1:$(concat \"bar\" yas/text)}")) + (yas-expand-snippet snippet) + (should (string= (yas--buffer-contents) "bar")) + (ert-simulate-command `(yas-mock-insert "foo")) + (should (string= (yas--buffer-contents) "FOObarFOO"))))) ;;; Misc tests diff --git a/yasnippet.el b/yasnippet.el index 28bd7e8..0709108 100644 --- a/yasnippet.el +++ b/yasnippet.el @@ -3745,6 +3745,11 @@ next FOM. Works its way up recursively for parents of parents." "When expanding the snippet the \"parse-create\" functions add cons cells to this var") +(defvar yas--backquote-markers-and-strings nil + "List of (MARKER . STRING) marking where the the values + from backquoted lisp expressions should be inserted at the end of + expansion" ) + (defun yas--snippet-parse-create (snippet) "Parse a recently inserted snippet template, creating all necessary fields, mirrors and exit points. @@ -3760,12 +3765,7 @@ Meant to be called in a narrowed buffer, does various passes" ;; replace all backquoted expressions ;; (goto-char parse-start) - (yas--replace-backquotes) - ;; protect escapes again since previous steps might have generated - ;; more characters needing escaping - ;; - (goto-char parse-start) - (yas--protect-escapes) + (yas--save-backquotes) ;; parse fields with {} ;; (goto-char parse-start) @@ -3784,6 +3784,9 @@ Meant to be called in a narrowed buffer, does various passes" ;; Delete $-constructs ;; (yas--delete-regions yas--dollar-regions) + ;; restore backquoted expression values + ;; + (yas--restore-backquotes) ;; restore escapes ;; (goto-char parse-start) @@ -3915,15 +3918,34 @@ With optional string TEXT do it in string instead of the buffer." (or escaped yas--escaped-characters)) changed-text)) -(defun yas--replace-backquotes () - "Replace all the \"`(lisp-expression)`\"-style expression - with their evaluated value" +(defun yas--save-backquotes () + "Save all the \"`(lisp-expression)`\"-style expression +with their evaluated value into `yas--backquote-markers-and-strings'" (while (re-search-forward yas--backquote-lisp-expression-regexp nil t) (let ((current-string (match-string 1)) transformed) (delete-region (match-beginning 0) (match-end 0)) (setq transformed (yas--eval-lisp (yas--read-lisp (yas--restore-escapes current-string)))) (goto-char (match-beginning 0)) - (when transformed (insert transformed))))) + (when transformed + (let ((marker (make-marker))) + (insert "Y") ;; quite horrendous, I love it :) + (set-marker marker (point)) + (insert "Y") + (push (cons marker transformed) yas--backquote-markers-and-strings)))))) + +(defun yas--restore-backquotes () + "Replace all the markers in +`yas--backquote-markers-and-strings' with their values" + (while yas--backquote-markers-and-strings + (let* ((marker-and-string (pop yas--backquote-markers-and-strings)) + (marker (car marker-and-string)) + (string (cdr marker-and-string))) + (save-excursion + (goto-char marker) + (delete-char -1) + (insert string) + (delete-char 1) + (set-marker marker nil))))) (defun yas--scan-sexps (from count) (condition-case err