mirror of
https://github.com/joaotavora/yasnippet.git
synced 2025-10-13 21:13:04 +00:00
Update handling of markers during indentation
* yasnippet.el (yas--snapshot-marker-location): New function, save a regexp and whitespace count determining a marker's location in a line. (yas--restore-marker-location): New function, restores a marker's location based on info from `yas--snapshot-marker-location'. (yas--indent-region): Use them to fix marker locations after indentation. * yasnippet-tests.el (indent-org-property, indent-cc-mode): (indent-snippet-mode): New tests.
This commit is contained in:
parent
28d5496144
commit
e878afb883
@ -243,6 +243,68 @@ $1 ------------------------")
|
|||||||
XXXXX ---------------- XXXXX ----
|
XXXXX ---------------- XXXXX ----
|
||||||
XXXXX ------------------------"))))
|
XXXXX ------------------------"))))
|
||||||
|
|
||||||
|
(ert-deftest indent-org-property ()
|
||||||
|
"Handling of `org-mode' property indentation, see `org-property-format'."
|
||||||
|
;; This is an interesting case because `org-indent-line' calls
|
||||||
|
;; `replace-match' for properties.
|
||||||
|
(with-temp-buffer
|
||||||
|
(org-mode)
|
||||||
|
(yas-minor-mode +1)
|
||||||
|
(yas-expand-snippet "* Test ${1:test}\n:PROPERTIES:\n:ID: $1-after\n:END:")
|
||||||
|
(yas-mock-insert "foo bar")
|
||||||
|
(ert-simulate-command '(yas-next-field))
|
||||||
|
(goto-char (point-min))
|
||||||
|
(let ((expected (with-temp-buffer
|
||||||
|
(insert (format (concat "* Test foo bar\n"
|
||||||
|
" " org-property-format "\n"
|
||||||
|
" " org-property-format "\n"
|
||||||
|
" " org-property-format)
|
||||||
|
":PROPERTIES:" ""
|
||||||
|
":ID:" "foo bar-after"
|
||||||
|
":END:" ""))
|
||||||
|
(delete-trailing-whitespace)
|
||||||
|
(buffer-string))))
|
||||||
|
;; Some org-mode versions leave trailing whitespace, some don't.
|
||||||
|
(delete-trailing-whitespace)
|
||||||
|
(should (equal expected (buffer-string))))))
|
||||||
|
|
||||||
|
(ert-deftest indent-cc-mode ()
|
||||||
|
"Handling of cc-mode's indentation."
|
||||||
|
;; This is an interesting case because cc-mode deletes all the
|
||||||
|
;; indentation before recreating it.
|
||||||
|
(with-temp-buffer
|
||||||
|
(c++-mode)
|
||||||
|
(yas-minor-mode +1)
|
||||||
|
(yas-expand-snippet "\
|
||||||
|
int foo()
|
||||||
|
{
|
||||||
|
if ($1) {
|
||||||
|
delete $1;
|
||||||
|
$1 = 0;
|
||||||
|
}
|
||||||
|
}")
|
||||||
|
(yas-mock-insert "var")
|
||||||
|
(should (string= "\
|
||||||
|
int foo()
|
||||||
|
{
|
||||||
|
if (var) {
|
||||||
|
delete var;
|
||||||
|
var = 0;
|
||||||
|
}
|
||||||
|
}" (buffer-string)))))
|
||||||
|
|
||||||
|
(ert-deftest indent-snippet-mode ()
|
||||||
|
"Handling of snippet-mode indentation."
|
||||||
|
;; This is an interesting case because newlines match [[:space:]] in
|
||||||
|
;; snippet-mode.
|
||||||
|
(with-temp-buffer
|
||||||
|
(snippet-mode)
|
||||||
|
(yas-minor-mode +1)
|
||||||
|
(yas-expand-snippet "# -*- mode: snippet -*-\n# name: $1\n# key: $1\n# --\n")
|
||||||
|
(yas-mock-insert "foo")
|
||||||
|
(should (string= "# -*- mode: snippet -*-\n# name: foo\n# key: foo\n# --\n"
|
||||||
|
(buffer-string)))))
|
||||||
|
|
||||||
(ert-deftest indent-mirrors-on-update ()
|
(ert-deftest indent-mirrors-on-update ()
|
||||||
"Check that mirrors are always kept indented."
|
"Check that mirrors are always kept indented."
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
|
84
yasnippet.el
84
yasnippet.el
@ -3917,40 +3917,78 @@ Meant to be called in a narrowed buffer, does various passes"
|
|||||||
(goto-char parse-start)
|
(goto-char parse-start)
|
||||||
(yas--indent snippet)))
|
(yas--indent snippet)))
|
||||||
|
|
||||||
|
;; HACK: Some implementations of `indent-line-function' (called via
|
||||||
|
;; `indent-according-to-mode') delete text before they insert (like
|
||||||
|
;; cc-mode), some make complicated regexp replacements (looking at
|
||||||
|
;; you, org-mode). To find place where the marker "should" go after
|
||||||
|
;; indentation, we create a regexp based on what the line looks like
|
||||||
|
;; before, putting a capture group where the marker is. The regexp
|
||||||
|
;; matches any whitespace with [[:space:]]* to allow for the
|
||||||
|
;; indentation changing whitespace. Additionally, we try to preserve
|
||||||
|
;; the amount of whitespace *following* the marker, because
|
||||||
|
;; indentation generally affects whitespace at the beginning, not the
|
||||||
|
;; end.
|
||||||
|
;;
|
||||||
|
;; This is all best-effort heuristic stuff, but it should cover 99% of
|
||||||
|
;; use-cases.
|
||||||
|
|
||||||
|
(defun yas--snapshot-marker-location (marker)
|
||||||
|
"Returns info for restoring MARKER's location after indent.
|
||||||
|
The returned value is a list of the form (REGEXP MARKER WS-COUNT)."
|
||||||
|
(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 (concat "[[:space:]]*"
|
||||||
|
(mapconcat (lambda (s)
|
||||||
|
(if (eq s marker) "\\(\\)"
|
||||||
|
(regexp-quote s)))
|
||||||
|
(nconc before (list marker) after)
|
||||||
|
"[[:space:]]*"))
|
||||||
|
marker
|
||||||
|
(progn (goto-char marker)
|
||||||
|
(skip-syntax-forward " " (line-end-position))
|
||||||
|
(- (point) marker))))))
|
||||||
|
|
||||||
|
(defun yas--restore-marker-location (re-marker)
|
||||||
|
"Restores marker based on info from `yas--snapshot-marker-location'."
|
||||||
|
(let ((regexp (nth 0 re-marker))
|
||||||
|
(marker (nth 1 re-marker))
|
||||||
|
(ws-count (nth 2 re-marker)))
|
||||||
|
(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))
|
||||||
|
(set-marker marker (point))))))
|
||||||
|
|
||||||
(defun yas--indent-region (from to snippet)
|
(defun yas--indent-region (from to snippet)
|
||||||
"Indent the lines between FROM and TO with `indent-according-to-mode'.
|
"Indent the lines between FROM and TO with `indent-according-to-mode'.
|
||||||
The SNIPPET's markers are preserved."
|
The SNIPPET's markers are preserved."
|
||||||
;;; Apropos indenting problems....
|
|
||||||
;;
|
|
||||||
;; `indent-according-to-mode' uses whatever `indent-line-function'
|
|
||||||
;; is available. Some implementations of these functions delete text
|
|
||||||
;; before they insert. If there happens to be a marker just after
|
|
||||||
;; the text being deleted, the insertion actually happens after the
|
|
||||||
;; marker, which misplaces it.
|
|
||||||
;;
|
|
||||||
;; This would also happen if we had used overlays with the
|
|
||||||
;; `front-advance' property set to nil.
|
|
||||||
;;
|
|
||||||
;; This is why I have these `trouble-markers', they are the ones at
|
|
||||||
;; the first non-whitespace char at the line. After indentation
|
|
||||||
;; takes place we should be at the correct to restore them. All
|
|
||||||
;; other non-trouble-markers should have been *pushed* and don't
|
|
||||||
;; need special attention.
|
|
||||||
(let* ((snippet-markers (yas--collect-snippet-markers snippet))
|
(let* ((snippet-markers (yas--collect-snippet-markers snippet))
|
||||||
(to (set-marker (make-marker) to)))
|
(to (set-marker (make-marker) to)))
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(goto-char from)
|
(goto-char from)
|
||||||
(save-restriction
|
(save-restriction
|
||||||
(widen)
|
(widen)
|
||||||
;; Indent each non-empty line.
|
|
||||||
(cl-loop if (/= (line-beginning-position) (line-end-position)) do
|
(cl-loop if (/= (line-beginning-position) (line-end-position)) do
|
||||||
(back-to-indentation)
|
;; Indent each non-empty line.
|
||||||
(let ((trouble-markers ; The markers at (point).
|
(let ((remarkers
|
||||||
(cl-remove (point) snippet-markers :test #'/=)))
|
(delq nil (mapcar #'yas--snapshot-marker-location
|
||||||
|
snippet-markers))))
|
||||||
(unwind-protect
|
(unwind-protect
|
||||||
(indent-according-to-mode)
|
(progn (back-to-indentation)
|
||||||
(dolist (marker trouble-markers)
|
(indent-according-to-mode))
|
||||||
(set-marker marker (point)))))
|
(mapc #'yas--restore-marker-location remarkers)))
|
||||||
while (and (zerop (forward-line 1))
|
while (and (zerop (forward-line 1))
|
||||||
(< (point) to)))))))
|
(< (point) to)))))))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user