Skip to content

Commit 57fdab0

Browse files
committed
Fix incorrect parsing after a `upcase-initials-region' call
1 parent aa96d5f commit 57fdab0

File tree

3 files changed

+45
-17
lines changed

3 files changed

+45
-17
lines changed

lisp/test-files/change-case-region.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Change case of "this text" repeatedly.
2+
unsafe {
3+
input_function.call_unprotected((bytepos, point.line_number(), point.byte_column()))
4+
.and_then(|v| v.into_rust())
5+
}

lisp/tree-sitter-tests.el

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
(defun ts-test-use-lang (lang-symbol)
4040
"Turn on `tree-sitter-mode' in the current buffer, using language LANG-SYMBOL."
4141
(setq tree-sitter-language (tree-sitter-require lang-symbol))
42+
(ignore-errors
43+
(setq tree-sitter-hl-default-patterns
44+
(tree-sitter-langs--hl-default-patterns lang-symbol)))
4245
(add-hook 'tree-sitter-after-first-parse-hook
4346
(lambda () (should (not (null tree-sitter-tree)))))
4447
(tree-sitter-mode))
@@ -140,6 +143,16 @@
140143
(kill-region (point-min) (point-max))
141144
(ts-test-tree-sexp '(source_file))))
142145

146+
(ert-deftest minor-mode::incremental:change-case-region ()
147+
(ts-test-lang-with-file 'rust "lisp/test-files/change-case-region.rs"
148+
(let* ((orig-sexp (read (ts-tree-to-sexp tree-sitter-tree)))
149+
(end (re-search-forward "this text"))
150+
(beg (match-beginning 0)))
151+
(upcase-initials-region beg end)
152+
(ts-test-tree-sexp orig-sexp)
153+
(downcase-region beg end)
154+
(ts-test-tree-sexp orig-sexp))))
155+
143156
(ert-deftest node::eq ()
144157
(ts-test-with 'rust parser
145158
(let* ((tree (ts-parse-string parser "fn foo() {}"))

lisp/tree-sitter.el

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -80,28 +80,38 @@ BEG and END are the begin and end of the text to be changed."
8080
(setq tree-sitter--start-point (ts--point-from-position beg)
8181
tree-sitter--old-end-point (ts--point-from-position end))))
8282

83-
;;; TODO XXX: The doc says that `after-change-functions' can be called multiple times, with
84-
;;; different regions enclosed in the region passed to `before-change-functions'. Therefore what we
85-
;;; are doing may be a bit too naive. Several questions to investigate:
83+
;;; TODO: How do we batch *after* hooks to re-parse only once? Maybe using
84+
;;; `run-with-idle-timer' with 0-second timeout?
8685
;;;
87-
;;; 1. Are the *after* regions recorded all at once, or one after another? Are they disjointed
88-
;;; (would imply the former)?.
89-
;;;
90-
;;; 2. Are the *after* regions recorded at the same time as the *before* region? If not, how can the
91-
;;; latter possibly enclose the former, e.g. in case of inserting a bunch of text?
92-
;;;
93-
;;; 3. How do we batch *after* hooks to re-parse only once? Maybe using `run-with-idle-timer' with
94-
;;; 0-second timeout?
95-
;;;
96-
;;; 4. What's the deal with several primitives calling `after-change-functions' *zero* or more
97-
;;; times? Does that mean we can't rely on this hook at all?
98-
(defun tree-sitter--after-change (_beg end _length)
86+
;;; XXX: Figure out how to detect whether it was a text-property-only change.
87+
;;; There's no point in reparsing in these situations.
88+
(defun tree-sitter--after-change (beg end length)
9989
"Update relevant editing states and reparse the buffer (incrementally).
10090
Installed on `after-change-functions'.
10191
10292
END is the end of the changed text."
103-
(setq tree-sitter--new-end-byte (position-bytes end)
104-
tree-sitter--new-end-point (ts-point-from-position end))
93+
(ts--save-context
94+
(setq tree-sitter--start-byte (position-bytes beg)
95+
tree-sitter--start-point (ts--point-from-position beg)
96+
tree-sitter--new-end-byte (position-bytes end)
97+
tree-sitter--new-end-point (ts--point-from-position end))
98+
;; The enclosing region passed to `before-change-functions' can be inexact,
99+
;; which can be larger than the actual changes. One example is
100+
;; `upcase-initials-region'. Therefore, we need to compute the exact change
101+
;; here. XXX: This is sometimes incorrect, because `ts--point-from-position'
102+
;; and `position-bytes' here look at other text in the same region, not the
103+
;; changed text. TODO FIX: Either figure out how we can get the exact
104+
;; old-end, or make `ts_tree_edit' accept position ranges.
105+
(let ((old-end (+ beg length)))
106+
;; XXX: Additionally, in case of a deletion at the end of the buffer,
107+
;; trying to compute the old-end position/byte is impossible, because the
108+
;; text is already gone. When that happens, use the old-end previously
109+
;; recorded in `before-change-functions'.
110+
(setq tree-sitter--old-end-point (or (ignore-errors
111+
(ts--point-from-position old-end))
112+
tree-sitter--old-end-point)
113+
tree-sitter--old-end-byte (or (position-bytes old-end)
114+
tree-sitter--old-end-byte))))
105115
(when tree-sitter-tree
106116
(ts-edit-tree tree-sitter-tree
107117
tree-sitter--start-byte

0 commit comments

Comments
 (0)