diff --git a/lisp/textmodes/markdown-ts-mode.el b/lisp/textmodes/markdown-ts-mode.el index bf10c378fca..c488766166b 100644 --- a/lisp/textmodes/markdown-ts-mode.el +++ b/lisp/textmodes/markdown-ts-mode.el @@ -2174,36 +2174,16 @@ a line's leading whitespace resolves to the item on that line, not to a preceding item whose node spans the whitespace. Inside block quotes, also try from the content position past the `>' markers." - (when-let* ((pos (save-excursion (back-to-indentation) (point))) - (node (treesit-node-at pos 'markdown)) - (bol (line-beginning-position)) - (eol (line-end-position))) - (or (let ((item (treesit-parent-until - node (lambda (n) - (equal (treesit-node-type n) "list_item"))))) - ;; Verify the current line is within the item's range. - ;; `treesit-node-at' can return a node inside a list_item - ;; even when point is on a preceding line. - (when (and item - (<= (treesit-node-start item) eol) - (>= (treesit-node-end item) bol)) - item)) + (let ((pos (save-excursion (back-to-indentation) (point))) ;; When back-to-indentation lands on block quote markers, ;; skip past them and try from the content position. - (let ((content-pos (save-excursion - (beginning-of-line) - (skip-chars-forward "> \t") - (point)))) - (when (> content-pos pos) - (when-let* ((cnode (treesit-node-at content-pos 'markdown))) - (let ((item (treesit-parent-until - cnode - (lambda (n) - (equal (treesit-node-type n) "list_item"))))) - (when (and item - (<= (treesit-node-start item) eol) - (>= (treesit-node-end item) bol)) - item)))))))) + (content-pos (save-excursion + (beginning-of-line) + (skip-chars-forward "> \t") + (point)))) + (or (markdown-ts--node-containing pos "\\`list_item\\'") + (and (> content-pos pos) + (markdown-ts--node-containing content-pos "\\`list_item\\'"))))) (defun markdown-ts--list-marker-width (item) "Return the width of ITEM's list marker including trailing space." @@ -2609,14 +2589,24 @@ of the item's text. JUSTIFY is as in `fill-paragraph'." ;; as per the contract of `fill-paragraph-function'. t)) +(defun markdown-ts--node-containing (pos type) + "Return the innermost node matching TYPE that contains POS, or nil. +TYPE is as in `treesit-parent-until'. When no leaf node covers POS, +`treesit-node-at' falls back to one that merely ends at POS, which does +not contain POS and must not be reported here." + (and-let* ((node (treesit-node-at pos 'markdown)) + (parent (treesit-parent-until node type)) + ((<= (treesit-node-start parent) pos)) + ((< pos (treesit-node-end parent)))) + parent)) + (defun markdown-ts--adaptive-fill () "Return the fill prefix for the current line in Markdown. When inside a list item, return spaces matching the column where the item's text starts." - (and-let* ((node (treesit-node-at + (and-let* ((item (markdown-ts--node-containing (save-excursion (back-to-indentation) (point)) - 'markdown)) - (item (treesit-parent-until node "\\`list_item\\'"))) + "\\`list_item\\'"))) (make-string (markdown-ts--list-item-text-column item) ?\s))) (defun markdown-ts--fill-forward-paragraph (arg) @@ -2640,21 +2630,21 @@ unfilled." (block (car (treesit-query-capture (treesit-buffer-root-node 'markdown) markdown-ts--fill-unfillable-block-query - pos (1+ pos)))) + pos (min (1+ pos) (point-max))))) (indented-pos (save-excursion (goto-char pos) (back-to-indentation) (point))) - (node (treesit-node-at indented-pos 'markdown)) - (item (treesit-parent-until node "\\`list_item\\'"))) + (item (markdown-ts--node-containing + indented-pos "\\`list_item\\'"))) ;; When moving forward from whitespace between list items, ;; skip to the next non-blank position and check again. (when (and (not item) (not block) (> direction 0)) (let ((next-pos (save-excursion (skip-chars-forward " \t\n") (point)))) - (setq node (treesit-node-at next-pos 'markdown)) - (setq item (treesit-parent-until node "\\`list_item\\'")))) + (setq item (markdown-ts--node-containing + next-pos "\\`list_item\\'")))) (cond ;; Inside an unfillable block: skip over it entirely. (block @@ -2662,9 +2652,24 @@ unfilled." (setq moved (1+ moved))) ;; Inside a list item: treat as paragraph boundary. (item - (if (> direction 0) - (goto-char (treesit-node-end item)) - (goto-char (treesit-node-start item))) + (let ((target (if (< direction 0) + (treesit-node-start item) + ;; The item node extends over the blank line + ;; separating the list from the block that + ;; follows it; stop after the item's last + ;; non-blank line, or filling deletes that + ;; blank line (bug#81712). + (save-excursion + (goto-char (treesit-node-end item)) + (skip-chars-backward + " \t\n" (treesit-node-start item)) + (line-beginning-position 2))))) + ;; Move to the item boundary only when that makes progress; + ;; reporting a move we did not make loops `fill-region' + ;; forever (bug#81712). + (if (if (> direction 0) (> target (point)) (< target (point))) + (goto-char target) + (forward-paragraph direction))) (setq moved (1+ moved))) ;; Default: use standard paragraph motion. (t