Fix field adjustment on deletion

For deletion, we need to check the bounds before the deletion happens,
otherwise the overlay may already be moved to wrong place.
* yasnippet.el (yas--before-change-modified-snippets): New variable.
(yas--merge-and-drop-dups): New function.
(yas--gather-active-snippets): New function.
(yas--on-field-overlay-modification): Use it.

* yasnippet.el
This commit is contained in:
Noam Postavsky 2019-05-01 18:04:38 -04:00
parent ffce236268
commit c432e78ffd
2 changed files with 86 additions and 29 deletions

View File

@ -1122,6 +1122,27 @@ hello ${1:$(when (stringp yas-text) (funcall func yas-text))} foo${1:$$(concat \
(ert-simulate-command '(yas-next-field-or-maybe-expand)) (ert-simulate-command '(yas-next-field-or-maybe-expand))
(should (string= (buffer-string) "<-<-abcdef\n"))))) (should (string= (buffer-string) "<-<-abcdef\n")))))
(ert-deftest nested-snippet-expansion-5-nested-delete ()
"See Github #996."
(let ((yas-triggers-in-field t))
(yas-with-snippet-dirs
'((".emacs.d/snippets"
("text-mode"
("sel" . "${1:ch}")
("ch" . "<-${1:ch}"))))
(yas-reload-all)
(text-mode)
(yas-minor-mode +1)
(insert "sel")
(ert-simulate-command '(yas-expand))
(ert-simulate-command '(forward-word 1))
(ert-simulate-command '(yas-expand))
(ert-simulate-command '(forward-word 1))
;; The (cl-assert (memq pfield (yas--snippet-fields psnippet)))
;; in `yas--on-field-overlay-modification' failed here.
(ert-simulate-command '(delete-backward-char 1))
(should (string= (buffer-string) "<-c\n")))))
;;; Loading ;;; Loading
;;; ;;;

View File

@ -3788,13 +3788,45 @@ BEG, END and LENGTH like overlay modification hooks."
(defvar yas--todo-snippet-indent nil nil) (defvar yas--todo-snippet-indent nil nil)
(make-variable-buffer-local 'yas--todo-snippet-indent) (make-variable-buffer-local 'yas--todo-snippet-indent)
(defvar yas--before-change-modified-snippets nil)
(defun yas--merge-and-drop-dups (list1 list2 cmp key)
;; `delete-consecutive-dups' + `cl-merge'.
(funcall (if (fboundp 'delete-consecutive-dups)
#'delete-consecutive-dups ; 24.4
#'delete-dups)
(cl-merge 'list list1 list2 cmp :key key)))
(defun yas--gather-active-snippets (overlay beg end then-delete)
;; Add active snippets in BEG..END into an OVERLAY keyed entry of
;; `yas--before-change-modified-snippets'. Return accumulated list.
;; If THEN-DELETE is non-nil, delete the entry.
(let ((new (yas-active-snippets beg end))
(old (assq overlay yas--before-change-modified-snippets)))
(prog1 (cond ((and new old)
(setf (cdr old)
(yas--merge-and-drop-dups
(cdr old) new
;; Sort like `yas-active-snippets'.
#'>= #'yas--snippet-id)))
(new (unless then-delete
;; Don't add new entry if we're about to
;; remove it anyway.
(push (cons overlay new)
yas--before-change-modified-snippets))
new)
(old (cdr old))
(t nil))
(when then-delete
(cl-callf2 delq old yas--before-change-modified-snippets)))))
(defun yas--on-field-overlay-modification (overlay after? beg end &optional length) (defun yas--on-field-overlay-modification (overlay after? beg end &optional length)
"Clears the field and updates mirrors, conditionally. "Clears the field and updates mirrors, conditionally.
Only clears the field if it hasn't been modified and point is at Only clears the field if it hasn't been modified and point is at
field start. This hook does nothing if an undo is in progress." field start. This hook does nothing if an undo is in progress."
(unless (or (not after?) (unless (or yas--inhibit-overlay-hooks
yas--inhibit-overlay-hooks
(not (overlayp yas--active-field-overlay)) ; Avoid Emacs bug #21824. (not (overlayp yas--active-field-overlay)) ; Avoid Emacs bug #21824.
;; If a single change hits multiple overlays of the same ;; If a single change hits multiple overlays of the same
;; snippet, then we delete the snippet the first time, ;; snippet, then we delete the snippet the first time,
@ -3807,6 +3839,7 @@ field start. This hook does nothing if an undo is in progress."
(field (overlay-get overlay 'yas--field)) (field (overlay-get overlay 'yas--field))
(snippet (overlay-get yas--active-field-overlay 'yas--snippet))) (snippet (overlay-get yas--active-field-overlay 'yas--snippet)))
(if (yas--snippet-live-p snippet) (if (yas--snippet-live-p snippet)
(if after?
(save-match-data (save-match-data
(yas--letenv (yas--snippet-expand-env snippet) (yas--letenv (yas--snippet-expand-env snippet)
(when (yas--skip-and-clear-field-p field beg end length) (when (yas--skip-and-clear-field-p field beg end length)
@ -3816,7 +3849,8 @@ field start. This hook does nothing if an undo is in progress."
;; Adjust any pending active fields in case of stacked ;; Adjust any pending active fields in case of stacked
;; expansion. ;; expansion.
(let ((pfield field) (let ((pfield field)
(psnippets (yas-active-snippets beg end))) (psnippets (yas--gather-active-snippets
overlay beg end t)))
(while (and pfield psnippets) (while (and pfield psnippets)
(let ((psnippet (pop psnippets))) (let ((psnippet (pop psnippets)))
(cl-assert (memq pfield (yas--snippet-fields psnippet))) (cl-assert (memq pfield (yas--snippet-fields psnippet)))
@ -3834,6 +3868,8 @@ field start. This hook does nothing if an undo is in progress."
(unless (or (not (eq yas-indent-line 'auto)) (unless (or (not (eq yas-indent-line 'auto))
(memq snippet yas--todo-snippet-indent)) (memq snippet yas--todo-snippet-indent))
(push snippet yas--todo-snippet-indent)))) (push snippet yas--todo-snippet-indent))))
;; Remember active snippets to use for after the change.
(yas--gather-active-snippets overlay beg end nil))
(lwarn '(yasnippet zombie) :warning "Killing zombie snippet!") (lwarn '(yasnippet zombie) :warning "Killing zombie snippet!")
(delete-overlay overlay))))) (delete-overlay overlay)))))