Fix python-ts-mode font-lock breakage by grammar change (bug#79457)

* lisp/progmodes/python.el (python--treesit-fontify-string): Use
new algorithm.
This commit is contained in:
Yuan Fu 2025-09-20 23:36:23 -07:00
parent 192a0e1773
commit 7b8c17d527
No known key found for this signature in database
GPG key ID: 56E19BC57664A442

View file

@ -1092,36 +1092,43 @@ NODE is the string node. Do not fontify the initial f for
f-strings. OVERRIDE is the override flag described in f-strings. OVERRIDE is the override flag described in
`treesit-font-lock-rules'. START and END mark the region to be `treesit-font-lock-rules'. START and END mark the region to be
fontified." fontified."
(let* ((maybe-expression (treesit-node-parent node)) ;; Criteria for docstring: go up the parse tree until top-level or a
(grandparent (treesit-node-parent ;; node under function/class, at each level, the node is the first
(treesit-node-parent ;; child (excluding comments). This condition also rules out negative
maybe-expression))) ;; cases like
(maybe-defun grandparent) ;;
(face (if (and (or (member (treesit-node-type maybe-defun) ;; def function():
'("function_definition" ;; return "some string"
"class_definition")) ;;
;; If the grandparent is null, meaning the ;; And it recognizes for BOF docstrings, and allows comments before
;; string is top-level, and the string has ;; the docstring.
;; no node or only comment preceding it, ;;
;; it's a BOF docstring. ;; Older grammar has function_definition -> block -> expression_statement -> string
(and (null grandparent) ;; Newer grammar has function_definition -> block -> string
(cl-loop ;; This algorithm works for both.
for prev = (treesit-node-prev-sibling (let* ((cursor node)
maybe-expression) (face (catch 'break
then (treesit-node-prev-sibling prev) (while t
while prev (let ((parent (treesit-node-parent cursor))
if (not (equal (treesit-node-type prev) (cursor-idx (treesit-node-index cursor)))
"comment")) (when (null parent)
return nil (throw 'break 'font-lock-doc-face))
finally return t)))
;; This check filters out this case:
;; def function():
;; return "some string"
(equal (treesit-node-type maybe-expression)
"expression_statement"))
'font-lock-doc-face
'font-lock-string-face))
(when (and (member (treesit-node-type parent)
'("function_definition"
"class_definition"))
(equal (treesit-node-field-name-for-child
parent cursor-idx)
"body"))
(throw 'break 'font-lock-doc-face))
(let ((idx 0))
(while (< idx cursor-idx)
(unless (equal (treesit-node-type
(treesit-node-child parent idx))
"comment")
(throw 'break 'font-lock-string-face))))
(setq cursor parent)))))
(ignore-interpolation (ignore-interpolation
(not (seq-some (not (seq-some
(lambda (feats) (memq 'string-interpolation feats)) (lambda (feats) (memq 'string-interpolation feats))