Fix interaction with c auto-fill

Save and restore all snippet marker and overlays in the current
paragraph around auto-fill calls.  Extend the marker/overlay snapshot
and restore mechanisms to work in multiline regions.

* yasnippet.el (yas-minor-mode): Install our auto-fill function around
the default one.
(yas--original-auto-fill-function): New variable.
(yas--auto-fill): New function, saves snippet markers & overlays,
calls original auto-fill-function, then restores markers and overlays.
(yas--snapshot-marker-location): Take new optional parameters BEG END,
allow for newlines in whitespace sequences (even if newline doesn't
have whitespace syntax).
(yas--snapshot-overlay-location): New function, takes BEG END,
produced non-line based location snapshots.
(yas--snapshot-overlay-line-location): Renamed from old
`yas--snapshot-overlay-location' function.
(yas--restore-overlay-line-location): Renamed from old
`yas--restore-overlay-location' function, narrow to line before calling
`yas--goto-saved-location'.
(yas--goto-saved-location): Use whole narrowed buffer instead of
assuming current line.
(yas--restore-overlay-location): New function, assume whole narrowed
buffer may be used.
(yas--prepare-snippets-for-move, yas--finish-moving-snippets):
(yas--indent-region): Adjust callers.
This commit is contained in:
Noam Postavsky 2017-03-22 23:40:58 -04:00
parent 30913fdfb7
commit 22eeb1ef0e

View File

@ -558,6 +558,10 @@ conditions.
(defvar yas--snippet-id-seed 0
"Contains the next id for a snippet.")
(defvar yas--original-auto-fill-function nil
"The original value of `auto-fill-function'.")
(make-variable-buffer-local 'yas--original-auto-fill-function)
(defun yas--snippet-next-id ()
(let ((id yas--snippet-id-seed))
(cl-incf yas--snippet-id-seed)
@ -796,12 +800,18 @@ Key bindings:
(set-default name nil)
(set (make-local-variable name) t)))
;; Perform JIT loads
;;
(yas--load-pending-jits))
(yas--load-pending-jits)
;; Install auto-fill handler.
(when (and auto-fill-function
(not (eq auto-fill-function #'yas--auto-fill)))
(setq yas--original-auto-fill-function auto-fill-function)
(setq auto-fill-function #'yas--auto-fill)))
(t
;; Uninstall the direct keymaps and the post-command hook
;;
;; Uninstall the direct keymaps, post-command hook, and
;; auto-fill handler.
(remove-hook 'post-command-hook #'yas--post-command-handler t)
(when (local-variable-p 'yas--original-auto-fill-function)
(setq auto-fill-function yas--original-auto-fill-function))
(setq emulation-mode-map-alists
(remove 'yas--direct-keymaps emulation-mode-map-alists)))))
@ -3307,7 +3317,7 @@ This renders the snippet as ordinary text."
(yas--snapshot-marker-location m))
(set-marker m nil)))
snippet)
(let ((ctrl-ov (yas--snapshot-overlay-location
(let ((ctrl-ov (yas--snapshot-overlay-line-location
(yas--snippet-control-overlay snippet))))
(push (list ctrl-ov dst-base-line snippet) to-move)
(delete-overlay (car ctrl-ov))))
@ -3341,7 +3351,10 @@ This renders the snippet as ordinary text."
(lambda (l-m-r-w)
(goto-char base-pos)
(forward-line (nth 0 l-m-r-w))
(yas--restore-marker-location (cdr l-m-r-w))
(save-restriction
(narrow-to-region (line-beginning-position)
(line-end-position))
(yas--restore-marker-location (cdr l-m-r-w)))
(nth 1 l-m-r-w))
snippet)
(goto-char base-pos)
@ -3560,6 +3573,33 @@ field start. This hook does nothing if an undo is in progress."
(yas--update-mirrors snippet)))
(lwarn '(yasnippet zombie) :warning "Killing zombie snippet!")
(delete-overlay overlay)))))
(defun yas--auto-fill ()
(let* ((orig-point (point))
(end (progn (forward-paragraph) (point)))
(beg (progn (backward-paragraph) (point)))
(snippets (yas-active-snippets beg end))
(remarkers nil)
(reoverlays nil))
(dolist (snippet snippets)
(dolist (m (yas--collect-snippet-markers snippet))
(push (yas--snapshot-marker-location m beg end) remarkers))
(push (yas--snapshot-overlay-location
(yas--snippet-control-overlay snippet) beg end)
reoverlays))
(goto-char orig-point)
(let ((yas--inhibit-overlay-hooks t))
(funcall yas--original-auto-fill-function))
(save-excursion
(setq end (progn (forward-paragraph) (point)))
(setq beg (progn (backward-paragraph) (point))))
(save-excursion
(save-restriction
(narrow-to-region beg end)
(mapc #'yas--restore-marker-location remarkers)
(mapc #'yas--restore-overlay-location reoverlays))
(mapc #'yas--update-mirrors snippets))))
;;; Apropos protection overlays:
;;
@ -4036,34 +4076,48 @@ Meant to be called in a narrowed buffer, does various passes"
;; indentation generally affects whitespace at the beginning, not the
;; end.
;;
;; Two other cases where we apply a similar strategy:
;;
;; 1. Handling `auto-fill-mode', in this case we need to use the
;; current paragraph instead of line.
;;
;; 2. Moving snippets from an `org-src' temp buffer into the main org
;; buffer, in this case we need to count the line offsets (because org
;; may add indentation on each line making character positions
;; unreliable).
;;
;; This is all best-effort heuristic stuff, but it should cover 99% of
;; use-cases.
(defun yas--snapshot-marker-location (marker)
(defun yas--snapshot-marker-location (marker &optional beg end)
"Returns info for restoring MARKER's location after indent.
The returned value is a list of the form (MARKER REGEXP WS-COUNT).
If MARKER is not on current line, then return nil."
(when (and (<= (line-beginning-position) marker)
(<= marker (line-end-position)))
(let ((before
(split-string (buffer-substring-no-properties
(line-beginning-position) marker) "[[:space:]]+" t))
(after
(split-string (buffer-substring-no-properties
marker (line-end-position)) "[[:space:]]+" t)))
(list marker
(concat "[[:space:]]*"
(mapconcat (lambda (s)
(if (eq s marker) "\\(\\)"
(regexp-quote s)))
(nconc before (list marker) after)
"[[:space:]]*"))
(progn (goto-char marker)
(skip-syntax-forward " " (line-end-position))
(- (point) marker))))))
The returned value is a list of the form (MARKER REGEXP WS-COUNT)."
(unless beg (setq beg (line-beginning-position)))
(unless end (setq end (line-end-position)))
(let ((before (split-string (buffer-substring-no-properties beg marker)
"[[:space:]\n]+" t))
(after (split-string (buffer-substring-no-properties marker end)
"[[:space:]\n]+" t)))
(list marker
(concat "[[:space:]\n]*"
(mapconcat (lambda (s)
(if (eq s marker) "\\(\\)"
(regexp-quote s)))
(nconc before (list marker) after)
"[[:space:]\n]*"))
(progn (goto-char marker)
(skip-syntax-forward " " end)
(- (point) marker)))))
(defun yas--snapshot-overlay-location (overlay)
"Like `yas--snapshot-marker-location', but for overlays.
(defun yas--snapshot-overlay-location (overlay beg end)
"Like `yas--snapshot-marker-location' for overlays.
The returned format is (OVERLAY (RE WS) (RE WS))."
(list overlay
(cdr (yas--snapshot-marker-location (overlay-start overlay) beg end))
(cdr (yas--snapshot-marker-location (overlay-end overlay) beg end))))
(defun yas--snapshot-overlay-line-location (overlay)
"Return info for restoring OVERLAY's line based location.
The returned format is (OVERLAY (LINE RE WS) (LINE RE WS))."
(let ((loc-beg (progn (goto-char (overlay-start overlay))
(yas--snapshot-marker-location (point))))
@ -4076,35 +4130,47 @@ The returned format is (OVERLAY (LINE RE WS) (LINE RE WS))."
(list overlay loc-beg loc-end)))
(defun yas--goto-saved-location (regexp ws-count)
"Move point to location saved by `yas--snapshot-marker-location'."
(beginning-of-line)
(save-restriction
;; Narrowing is the only way to limit `looking-at'.
(narrow-to-region (point) (line-end-position))
(if (not (looking-at regexp))
(lwarn '(yasnippet re-marker) :warning
"Couldn't find: %S" regexp)
(goto-char (match-beginning 1))
(skip-syntax-forward " ")
(skip-syntax-backward " " (- (point) ws-count)))))
"Move point to location saved by `yas--snapshot-marker-location'.
Buffer must be narrowed to BEG..END used to create the snapshot info."
(goto-char (point-min))
(if (not (looking-at regexp))
(lwarn '(yasnippet re-marker) :warning
"Couldn't find: %S" regexp)
(goto-char (match-beginning 1))
(skip-syntax-forward " ")
(skip-syntax-backward " " (- (point) ws-count))))
(defun yas--restore-marker-location (re-marker)
"Restores marker based on info from `yas--snapshot-marker-location'.
Assumes point is currently on the 'same' line as before."
Buffer must be narrowed to BEG..END used to create the snapshot info."
(apply #'yas--goto-saved-location (cdr re-marker))
(set-marker (car re-marker) (point)))
(defun yas--restore-overlay-location (ov-locations)
"Restores overlay based on info from `yas--snapshot-overlay-location'."
(move-overlay (car ov-locations)
(save-excursion
(forward-line (car (nth 1 ov-locations)))
(apply #'yas--goto-saved-location (cdr (nth 1 ov-locations)))
(point))
(save-excursion
(forward-line (car (nth 2 ov-locations)))
(apply #'yas--goto-saved-location (cdr (nth 2 ov-locations)))
(point))))
"Restores marker based on info from `yas--snapshot-marker-location'.
Buffer must be narrowed to BEG..END used to create the snapshot info."
(cl-destructuring-bind (overlay loc-beg loc-end) ov-locations
(move-overlay overlay
(progn (apply #'yas--goto-saved-location loc-beg)
(point))
(progn (apply #'yas--goto-saved-location loc-end)
(point)))))
(defun yas--restore-overlay-line-location (ov-locations)
"Restores overlay based on info from `yas--snapshot-overlay-line-location'."
(save-restriction
(move-overlay (car ov-locations)
(save-excursion
(forward-line (car (nth 1 ov-locations)))
(narrow-to-region (line-beginning-position) (line-end-position))
(apply #'yas--goto-saved-location (cdr (nth 1 ov-locations)))
(point))
(save-excursion
(forward-line (car (nth 2 ov-locations)))
(narrow-to-region (line-beginning-position) (line-end-position))
(apply #'yas--goto-saved-location (cdr (nth 2 ov-locations)))
(point)))))
(defun yas--indent-region (from to snippet)
"Indent the lines between FROM and TO with `indent-according-to-mode'.
@ -4115,15 +4181,21 @@ The SNIPPET's markers are preserved."
(goto-char from)
(save-restriction
(widen)
(cl-loop if (/= (line-beginning-position) (line-end-position)) do
(cl-loop for bol = (line-beginning-position)
for eol = (line-end-position)
if (/= bol eol) do
;; Indent each non-empty line.
(let ((remarkers
(delq nil (mapcar #'yas--snapshot-marker-location
snippet-markers))))
(let ((remarkers nil))
(dolist (m snippet-markers)
(when (and (<= bol m) (<= m eol))
(push (yas--snapshot-marker-location m bol eol)
remarkers)))
(unwind-protect
(progn (back-to-indentation)
(indent-according-to-mode))
(mapc #'yas--restore-marker-location remarkers)))
(save-restriction
(narrow-to-region bol (line-end-position))
(mapc #'yas--restore-marker-location remarkers))))
while (and (zerop (forward-line 1))
(< (point) to)))))))