aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYuan Fu2022-11-02 16:31:25 -0700
committerYuan Fu2022-11-02 16:47:53 -0700
commitf331be1f074d68e7e5cdbac324419e07c186492a (patch)
treeeb2d16d70a9aa23ab3e5b2a083db4b59fefa780e
parent040991a4697b50ebcb54e498e7de54b8d0885101 (diff)
downloademacs-f331be1f074d68e7e5cdbac324419e07c186492a.tar.gz
emacs-f331be1f074d68e7e5cdbac324419e07c186492a.zip
Add handling of contextual entities in tree-sitter font-lock
* lisp/progmodes/python.el: Remove function. (python--treesit-settings): Capture contextual node. * lisp/treesit.el (treesit--set-nonsticky): (treesit-font-lock-contextual-post-process): New functions. (treesit-font-lock-fontify-region): Change local variable START and END to NODE-START and NODE-END, handle special capture name "contextual". * doc/lispref/modes.texi (Parser-based Font Lock): Update manual.
-rw-r--r--doc/lispref/modes.texi20
-rw-r--r--lisp/progmodes/python.el10
-rw-r--r--lisp/treesit.el88
3 files changed, 107 insertions, 11 deletions
diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi
index 3c91893bbfd..c6f848ffb23 100644
--- a/doc/lispref/modes.texi
+++ b/doc/lispref/modes.texi
@@ -3992,6 +3992,26 @@ priority. If a capture name is neither a face nor a function, it is
3992ignored. 3992ignored.
3993@end defun 3993@end defun
3994 3994
3995Contextual entities, like multi-line strings, or @code{/* */} style
3996comments, need special care, because change in these entities might
3997cause change in a large portion of the buffer. For example, inserting
3998the closing comment delimiter @code{*/} will change all the text
3999between it and the opening delimiter to comment face. Such entities
4000should be captured in a special name @code{contextual}, so Emacs can
4001correctly update their fontification. Here is an example for
4002comments:
4003
4004@example
4005@group
4006(treesit-font-lock-rules
4007 :language 'javascript
4008 :feature 'comment
4009 :override t
4010 '((comment) @@font-lock-comment-face)
4011 (comment) @@contextual))
4012@end group
4013@end example
4014
3995@defvar treesit-font-lock-feature-list 4015@defvar treesit-font-lock-feature-list
3996This is a list of lists of feature symbols. Each element of the list 4016This is a list of lists of feature symbols. Each element of the list
3997is a list that represents a decoration level. 4017is a list that represents a decoration level.
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index 46559db2cd1..603cdb14e15 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -1035,12 +1035,6 @@ f for f-strings. OVERRIDE is the override flag described in
1035 (cl-incf string-beg)) 1035 (cl-incf string-beg))
1036 (treesit-fontify-with-override string-beg string-end face override))) 1036 (treesit-fontify-with-override string-beg string-end face override)))
1037 1037
1038(defun python--treesit-fontify-string-end (node &rest _)
1039 "Mark the whole string as to-be-fontified.
1040NODE is the ending quote of a string."
1041 (let ((string (treesit-node-parent node)))
1042 (setq jit-lock-context-unfontify-pos (treesit-node-start string))))
1043
1044(defvar python--treesit-settings 1038(defvar python--treesit-settings
1045 (treesit-font-lock-rules 1039 (treesit-font-lock-rules
1046 :feature 'comment 1040 :feature 'comment
@@ -1051,8 +1045,8 @@ NODE is the ending quote of a string."
1051 :language 'python 1045 :language 'python
1052 :override t 1046 :override t
1053 ;; TODO Document on why we do this. 1047 ;; TODO Document on why we do this.
1054 '((string "\"" @python--treesit-fontify-string-end :anchor) 1048 '((string :anchor "\"" @python--treesit-fontify-string)
1055 (string :anchor "\"" @python--treesit-fontify-string :anchor)) 1049 (string) @contextual)
1056 1050
1057 :feature 'string-interpolation 1051 :feature 'string-interpolation
1058 :language 'python 1052 :language 'python
diff --git a/lisp/treesit.el b/lisp/treesit.el
index 248c23bf888..6a7ba87e836 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -636,6 +636,82 @@ See `treesit-font-lock-rules' for their semantic."
636 "Unrecognized value of :override option" 636 "Unrecognized value of :override option"
637 override))))) 637 override)))))
638 638
639(defun treesit--set-nonsticky (start end sym &optional remove)
640 "Set `rear-nonsticky' property between START and END.
641Set the proeprty to a list containing SYM. If there is already a
642list, add SYM to that list. If REMOVE is non-nil, remove SYM
643instead."
644 (let* ((prop (get-text-property start 'rear-nonsticky))
645 (new-prop
646 (pcase prop
647 ((pred listp) ; PROP is a list or nil.
648 (if remove
649 (remove sym prop)
650 ;; We should make sure PORP doesn't contain SYM, but
651 ;; whatever.
652 (cons sym prop)))
653 ;; PROP is t.
654 (_ (if remove
655 nil
656 (list sym))))))
657 (if (null new-prop)
658 (remove-text-properties start end '(rear-nonsticky nil))
659 (put-text-property start end 'rear-nonsticky new-prop))))
660
661;; This post-processing tries to deal with the following scenario:
662;; User inserts "/*", then go down the buffer and inserts "*/".
663;; Before the user inserts "*/", tree-sitter cannot construct a
664;; comment node and the parse tree is incomplete, and we can't fontify
665;; the comment. But once the user inserts the "*/", the parse-tree is
666;; complete and we want to refontify the whole comment, and possibly
667;; text after comment (the "/*" could damage the parse tree enough
668;; that makes tree-sitter unable to produce reasonable information for
669;; text after it).
670;;
671;; So we set jit-lock-context-unfontify-pos to comment start, and
672;; jit-lock-context will refontify text after that position in a
673;; timer. Refontifying those text will end up calling this function
674;; again, and we don't want to fall into infinite recursion. So we
675;; mark the end of the comment with a text property, so we can
676;; distinguish between initial and follow up invocation of this
677;; function.
678(defun treesit-font-lock-contextual-post-process
679 (node start end &optional verbose)
680 "Post-processing for contextual syntax nodes.
681NODE is a comment or string node, START and END are the region
682being fontified.
683
684If VERBOSE is non-nil, print debugging information."
685 (let* ((node-start (treesit-node-start node))
686 (node-end (treesit-node-end node))
687 (node-end-1 (max (point-min) (1- node-end)))
688 (prop-sym 'treesit-context-refontify-in-progress))
689 (when verbose
690 (message "Contextual: region: %s-%s, node: %s-%s"
691 start end node-start node-end))
692 (when (<= node-end end)
693 (if (get-text-property node-end-1 prop-sym)
694 ;; We are called from a refontification by jit-lock-context,
695 ;; caused by a previous call to this function.
696 (progn (when verbose
697 (message "Contextual: in progress"))
698 (remove-text-properties
699 node-end-1 node-end `(,prop-sym nil))
700 (treesit--set-nonsticky node-end-1 node-end prop-sym t))
701 ;; We are called from a normal fontification.
702 (when verbose
703 (message "Contextual: initial"))
704 (setq jit-lock-context-unfontify-pos node-start)
705 (put-text-property node-end-1 node-end prop-sym t)
706 (treesit--set-nonsticky node-end-1 node-end prop-sym)))))
707
708;; Some details worth explaining:
709;;
710;; 1. When we apply face to a node, we clip the face into the
711;; currently fontifying region, this way we don't overwrite faces
712;; applied by regexp-based font-lock. The clipped part will be
713;; fontified fine when Emacs fontifies the region containing it.
714;;
639(defun treesit-font-lock-fontify-region 715(defun treesit-font-lock-fontify-region
640 (start end &optional loudly) 716 (start end &optional loudly)
641 "Fontify the region between START and END. 717 "Fontify the region between START and END.
@@ -666,11 +742,17 @@ If LOUDLY is non-nil, display some debugging information."
666 (dolist (capture captures) 742 (dolist (capture captures)
667 (let* ((face (car capture)) 743 (let* ((face (car capture))
668 (node (cdr capture)) 744 (node (cdr capture))
669 (start (treesit-node-start node)) 745 (node-start (treesit-node-start node))
670 (end (treesit-node-end node))) 746 (node-end (treesit-node-end node)))
671 (cond 747 (cond
748 ((eq face 'contextual)
749 (treesit-font-lock-contextual-post-process
750 node start end
751 (or loudly treesit--font-lock-verbose)))
672 ((facep face) 752 ((facep face)
673 (treesit-fontify-with-override start end face override)) 753 (treesit-fontify-with-override
754 (max node-start start) (min node-end end)
755 face override))
674 ((functionp face) 756 ((functionp face)
675 (funcall face node override))) 757 (funcall face node override)))
676 ;; Don't raise an error if FACE is neither a face nor 758 ;; Don't raise an error if FACE is neither a face nor