mirror of
https://github.com/joaotavora/yasnippet.git
synced 2025-10-13 13:13:03 +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 ------------------------"))))
|
||||
|
||||
(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 ()
|
||||
"Check that mirrors are always kept indented."
|
||||
(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)
|
||||
(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)
|
||||
"Indent the lines between FROM and TO with `indent-according-to-mode'.
|
||||
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))
|
||||
(to (set-marker (make-marker) to)))
|
||||
(save-excursion
|
||||
(goto-char from)
|
||||
(save-restriction
|
||||
(widen)
|
||||
;; Indent each non-empty line.
|
||||
(cl-loop if (/= (line-beginning-position) (line-end-position)) do
|
||||
(back-to-indentation)
|
||||
(let ((trouble-markers ; The markers at (point).
|
||||
(cl-remove (point) snippet-markers :test #'/=)))
|
||||
;; Indent each non-empty line.
|
||||
(let ((remarkers
|
||||
(delq nil (mapcar #'yas--snapshot-marker-location
|
||||
snippet-markers))))
|
||||
(unwind-protect
|
||||
(indent-according-to-mode)
|
||||
(dolist (marker trouble-markers)
|
||||
(set-marker marker (point)))))
|
||||
(progn (back-to-indentation)
|
||||
(indent-according-to-mode))
|
||||
(mapc #'yas--restore-marker-location remarkers)))
|
||||
while (and (zerop (forward-line 1))
|
||||
(< (point) to)))))))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user