diff --git a/tempel.el b/tempel.el index 25b9b89..c083ee3 100644 --- a/tempel.el +++ b/tempel.el @@ -133,6 +133,11 @@ If a file is modified, added or removed, reload the templates." (defvar tempel--inhibit-hooks nil "Inhibit tempel modification change hooks from running.") +(defvar tempel--pending-field-edit nil + "Internal variable to supress repeated calls of +`tempel--field-modified' when an edit overlaps multiple field +boundaries.") + (defvar-local tempel--active nil "List of active templates. Each template state is a pair, where the car is a list of overlays and @@ -220,11 +225,25 @@ REGION are the current region bounds." (tempel--inhibit-hooks t)) (tempel--disable (overlay-get ov 'tempel--range))))) +(defun tempel--ensure-ov-invariant (st ov) + "Ensure the overlay order is maintained, by clamping the boundaries +of every overlay which was added to the state ST before OV to the +start of OV, and likewise for overlays added to ST after OV." + (let ((start (overlay-start ov)) + (end (overlay-end ov)) + before-current) + (dolist (ov_ (cdar st)) + (if (eq ov ov_) (setq before-current t) + (if before-current + (move-overlay ov_ (min start (overlay-start ov_)) (min start (overlay-end ov_))) + (move-overlay ov_ (max end (overlay-start ov_)) (max end (overlay-end ov_)))))))) + (defun tempel--field-modified (ov after beg end &optional _len) "Update field overlay OV. AFTER is non-nil after the modification. BEG and END are the boundaries of the modification." - (unless tempel--inhibit-hooks + (unless (or tempel--inhibit-hooks (xor after tempel--pending-field-edit)) + (setq tempel--pending-field-edit (not after)) (let ((inhibit-modification-hooks nil) (tempel--inhibit-hooks t)) (cond @@ -237,7 +256,8 @@ BEG and END are the boundaries of the modification." (after (let ((st (overlay-get ov 'tempel--field))) (unless undo-in-progress - (move-overlay ov (overlay-start ov) (max end (overlay-end ov)))) + (move-overlay ov (overlay-start ov) (max end (overlay-end ov))) + (tempel--ensure-ov-invariant st ov)) (when-let ((name (overlay-get ov 'tempel--name))) (setf (alist-get name (cdr st)) (buffer-substring-no-properties @@ -256,12 +276,12 @@ BEG and END are the boundaries of the modification." (let (x) (setq x (or (and (setq x (overlay-get ov 'tempel--form)) (eval x (cdr st))) (and (setq x (overlay-get ov 'tempel--name)) (alist-get x (cdr st))))) - (when x (tempel--synchronize-replace (overlay-start ov) (overlay-end ov) ov x))))) + (when x (tempel--synchronize-replace (overlay-start ov) (overlay-end ov) ov x st))))) ;; Move range overlay - (move-overlay range (overlay-start range) + (move-overlay range (min (overlay-start range) (overlay-start ov)) (max (overlay-end range) (overlay-end ov)))))) -(defun tempel--synchronize-replace (beg end ov str) +(defun tempel--synchronize-replace (beg end ov str st) "Replace region between BEG and END with STR. If OV is alive, move it." (let ((old (buffer-substring-no-properties beg end))) @@ -269,7 +289,7 @@ If OV is alive, move it." (unless (equal str old) (unless (eq buffer-undo-list t) (push (list 'apply #'tempel--synchronize-replace - beg (+ beg (length str)) ov old) + beg (+ beg (length str)) ov old st) buffer-undo-list)) (let ((buffer-undo-list t)) (save-excursion @@ -278,6 +298,7 @@ If OV is alive, move it." (insert str) (when ov (move-overlay ov beg (point)) + (tempel--ensure-ov-invariant st ov) (tempel--update-mark ov))))))) (defun tempel--update-mark (ov) @@ -297,7 +318,7 @@ INIT is the optional initial input. Return the added field." (let ((ov (make-overlay (point) (point))) (hooks (list #'tempel--field-modified))) - (push ov (car st)) + (push ov (cdar st)) (when name (overlay-put ov 'tempel--name name) (setq init (or init (alist-get name (cdr st)))) @@ -316,6 +337,7 @@ Return the added field." (overlay-put ov 'tempel--default (if (string-suffix-p ": " init) 'end 'start))) (tempel--synchronize-fields st ov) + (goto-char (overlay-end ov)) ov)) (defun tempel--form (st form) @@ -329,7 +351,7 @@ Return the added field." (let ((ov (make-overlay beg (point) nil t))) (overlay-put ov 'face 'tempel-form) (overlay-put ov 'tempel--form form) - (push ov (car st)) + (push ov (cdar st)) ov))) (defmacro tempel--protect (&rest body) @@ -403,13 +425,12 @@ If a field was added, return it." (when (and (<= (overlay-start ov) (point)) (>= (overlay-end ov) (point))) (setf (overlay-end ov) (point))))) ;; Activate template - (let ((st (cons nil nil)) - (ov (point)) - (tempel--inhibit-hooks t)) + (let* ((ov (make-overlay (point) (point) nil t)) + (st (cons (list ov) nil)) + (tempel--inhibit-hooks t)) (while (and template (not (keywordp (car template)))) (tempel--element st region (pop template))) - (setq ov (make-overlay ov (point) nil t)) - (push ov (car st)) + (move-overlay ov (overlay-start ov) (point)) (overlay-put ov 'modification-hooks (list #'tempel--range-modified)) (overlay-put ov 'tempel--range st) (overlay-put ov 'tempel--post (plist-get plist :post))