From 67ee35b8f63f6e050e7adb1e91eb4eac9053b04b Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Fri, 19 Apr 2024 00:18:03 -0700 Subject: [PATCH] Tree-sitter: only update range and reparse for changed ranges In the very beginning, there's bug#66732, to solve that bug, we added treesit--pre-redisplay and treesit--syntax-propertize-notifier. However, to fix bug#66732, we were updating ranges for the whole buffer which makes Emacs extremely slow when there are a lot of local parsers in a large buffer. Then to solve that we introduced a workaround where we only update ranges in a fixed range around point. This change fixes the original problem (bug#66732) without using that workaround. * lisp/treesit.el (treesit--font-lock-notifier): (treesit--syntax-propertize-notifier): Remove functions (treesit--pre-redisplay): Use the new function treesit-parser-changed-ranges to get the changed ranges of the primary parser, and only update ranges for those ranges. Plus do the work of the removed function. (treesit-major-mode-setup): Remove setup for the removed functions. (cherry picked from commit f62c1b4cd00e5b2f1cdc94796cf55d006c3113eb) --- lisp/treesit.el | 97 ++++++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 57 deletions(-) diff --git a/lisp/treesit.el b/lisp/treesit.el index 2b899a84183..03df169da44 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -1328,18 +1328,6 @@ non-nil, print debugging information." (max node-start start) (min node-end end) face (treesit-node-type node))))))))) -(defun treesit--font-lock-notifier (ranges parser) - "Ensures updated parts of the parse-tree are refontified. -RANGES is a list of (BEG . END) ranges, PARSER is the tree-sitter -parser notifying of the change." - (with-current-buffer (treesit-parser-buffer parser) - (dolist (range ranges) - (when treesit--font-lock-verbose - (message "Notifier received range: %s-%s" - (car range) (cdr range))) - (with-silent-modifications - (put-text-property (car range) (cdr range) 'fontified nil))))) - (defvar-local treesit--syntax-propertize-start nil "If non-nil, next `syntax-propertize' should start at this position. @@ -1348,20 +1336,6 @@ When tree-sitter parser reparses, it calls and that function sets this variable to the start of the affected region.") -(defun treesit--syntax-propertize-notifier (ranges parser) - "Sets `treesit--syntax-propertize-start' to the smallest start. -Specifically, the smallest start position among all the ranges in -RANGES for PARSER." - (with-current-buffer (treesit-parser-buffer parser) - (when-let* ((range-starts (mapcar #'car ranges)) - (min-range-start - (seq-reduce - #'min (cdr range-starts) (car range-starts)))) - (if (null treesit--syntax-propertize-start) - (setq treesit--syntax-propertize-start min-range-start) - (setq treesit--syntax-propertize-start - (min treesit--syntax-propertize-start min-range-start)))))) - (defvar-local treesit--pre-redisplay-tick nil "The last `buffer-chars-modified-tick' that we've processed. Because `pre-redisplay-functions' could be called multiple times @@ -1369,32 +1343,47 @@ during a single command loop, we use this variable to debounce calls to `treesit--pre-redisplay'.") (defun treesit--pre-redisplay (&rest _) - "Force reparse and consequently run all notifiers. - -One of the notifiers is `treesit--font-lock-notifier', which will -mark the region whose syntax has changed to \"need to refontify\". - -For example, when the user types the final slash of a C block -comment /* xxx */, not only do we need to fontify the slash, but -also the whole block comment, which previously wasn't fontified -as comment due to incomplete parse tree." + "Force a reparse on the primary parser and do some work. + +After the parser reparses, we get the changed ranges, and +1) update non-primary parsers' ranges in the changed ranges +2) mark these ranges as to-be-fontified, +3) tell syntax-ppss to start reparsing from the min point of the ranges + +We need to mark to-be-fontified ranges before redisplay starts working, +because sometimes the range edited by the user is not the only range +that needs to be refontified. For example, when the user types the +final slash of a C block comment /* xxx */, not only do we need to +fontify the slash, but also the whole block comment, which previously +wasn't fontified as comment due to incomplete parse tree." (unless (eq treesit--pre-redisplay-tick (buffer-chars-modified-tick)) - ;; `treesit-update-ranges' will force the host language's parser to - ;; reparse and set correct ranges for embedded parsers. Then - ;; `treesit-parser-root-node' will force those parsers to reparse. - (let ((len (+ (* (window-body-height) (window-body-width)) 800))) - ;; FIXME: As a temporary fix, this prevents Emacs from updating - ;; every single local parsers in the buffer every time there's an - ;; edit. Moving forward, we need some way to properly track the - ;; regions which need update on parser ranges, like what jit-lock - ;; and syntax-ppss does. - (treesit-update-ranges - (max (point-min) (- (point) len)) - (min (point-max) (+ (point) len)))) - ;; Force repase on _all_ the parsers might not be necessary, but - ;; this is probably the most robust way. - (dolist (parser (treesit-parser-list)) - (treesit-parser-root-node parser)) + (let ((primary-parser + ;; TODO: We need something less ugly than this for getting + ;; the primary parser/language. + (if treesit-range-settings + (let ((query (car (car treesit-range-settings)))) + (if (treesit-query-p query) + (treesit-parser-create + (treesit-query-language query)) + (car (treesit-parser-list)))) + (car (treesit-parser-list))))) + ;; Force a reparse on the primary parser. + (treesit-parser-root-node primary-parser) + (dolist (range (treesit-parser-changed-ranges primary-parser)) + ;; 1. Update ranges. + (treesit-update-ranges (car range) (cdr range)) + ;; 2. Mark the changed ranges to be fontified. + (when treesit--font-lock-verbose + (message "Notifier received range: %s-%s" + (car range) (cdr range))) + (with-silent-modifications + (put-text-property (car range) (cdr range) 'fontified nil)) + ;; 3. Set `treesit--syntax-propertize-start'. + (if (null treesit--syntax-propertize-start) + (setq treesit--syntax-propertize-start (car range)) + (setq treesit--syntax-propertize-start + (min treesit--syntax-propertize-start (car range)))))) + (setq treesit--pre-redisplay-tick (buffer-chars-modified-tick)))) (defun treesit--pre-syntax-ppss (start end) @@ -2956,14 +2945,8 @@ before calling this function." (font-lock-fontify-syntactically-function . treesit-font-lock-fontify-region))) (treesit-font-lock-recompute-features) - (dolist (parser (treesit-parser-list)) - (treesit-parser-add-notifier - parser #'treesit--font-lock-notifier)) (add-hook 'pre-redisplay-functions #'treesit--pre-redisplay 0 t)) ;; Syntax - (dolist (parser (treesit-parser-list)) - (treesit-parser-add-notifier - parser #'treesit--syntax-propertize-notifier)) (add-hook 'syntax-propertize-extend-region-functions #'treesit--pre-syntax-ppss 0 t) ;; Indent. -- 2.39.5