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,33 +3839,37 @@ 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)
(save-match-data (if after?
(yas--letenv (yas--snippet-expand-env snippet) (save-match-data
(when (yas--skip-and-clear-field-p field beg end length) (yas--letenv (yas--snippet-expand-env snippet)
;; We delete text starting from the END of insertion. (when (yas--skip-and-clear-field-p field beg end length)
(yas--skip-and-clear field end)) ;; We delete text starting from the END of insertion.
(setf (yas--field-modified-p field) t) (yas--skip-and-clear field end))
;; Adjust any pending active fields in case of stacked (setf (yas--field-modified-p field) t)
;; expansion. ;; Adjust any pending active fields in case of stacked
(let ((pfield field) ;; expansion.
(psnippets (yas-active-snippets beg end))) (let ((pfield field)
(while (and pfield psnippets) (psnippets (yas--gather-active-snippets
(let ((psnippet (pop psnippets))) overlay beg end t)))
(cl-assert (memq pfield (yas--snippet-fields psnippet))) (while (and pfield psnippets)
(yas--advance-end-maybe pfield (overlay-end overlay)) (let ((psnippet (pop psnippets)))
(setq pfield (yas--snippet-previous-active-field psnippet))))) (cl-assert (memq pfield (yas--snippet-fields psnippet)))
;; Update fields now, but delay auto indentation until (yas--advance-end-maybe pfield (overlay-end overlay))
;; post-command. We don't want to run indentation on (setq pfield (yas--snippet-previous-active-field psnippet)))))
;; the intermediate state where field text might be ;; Update fields now, but delay auto indentation until
;; removed (and hence the field could be deleted along ;; post-command. We don't want to run indentation on
;; with leading indentation). ;; the intermediate state where field text might be
(let ((yas-indent-line nil)) ;; removed (and hence the field could be deleted along
(save-excursion ;; with leading indentation).
(yas--field-update-display field)) (let ((yas-indent-line nil))
(yas--update-mirrors snippet)) (save-excursion
(unless (or (not (eq yas-indent-line 'auto)) (yas--field-update-display field))
(memq snippet yas--todo-snippet-indent)) (yas--update-mirrors snippet))
(push snippet yas--todo-snippet-indent)))) (unless (or (not (eq yas-indent-line 'auto))
(memq 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)))))