-
-
Notifications
You must be signed in to change notification settings - Fork 242
Expand file tree
/
Copy pathsyntax-scanner.lisp
More file actions
268 lines (233 loc) · 11.5 KB
/
syntax-scanner.lisp
File metadata and controls
268 lines (233 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
(defpackage :lem-tests/syntax-scanner
(:use :cl :rove)
(:import-from :lem))
(in-package :lem-tests/syntax-scanner)
;;;; Tests for viewport-based syntax scanning (src/syntax-scanner.lisp)
;;;;
;;;; The scanned region tracking mechanism allows efficient syntax highlighting
;;;; by only scanning regions that haven't been scanned yet within the same
;;;; buffer modification tick.
;;; Helper to access internal functions
(defun buffer-scanned-region (buffer)
(lem-core::buffer-scanned-region buffer))
(defun (setf buffer-scanned-region) (value buffer)
(setf (lem-core::buffer-scanned-region buffer) value))
(defun update-scanned-region (buffer start-line end-line)
(lem-core::update-scanned-region buffer start-line end-line))
(defun viewport-needs-scan-p (buffer start-line end-line)
(lem-core::viewport-needs-scan-p buffer start-line end-line))
;;;; Unit Tests for buffer-scanned-region
(deftest buffer-scanned-region-initial-value
(testing "Initially, buffer has no scanned region"
(let ((buffer (lem:make-buffer "*test-scanned-region*" :temporary t)))
(unwind-protect
(ok (null (buffer-scanned-region buffer)))
(lem:delete-buffer buffer)))))
(deftest buffer-scanned-region-set-and-get
(testing "Can set and get scanned region"
(let ((buffer (lem:make-buffer "*test-scanned-region*" :temporary t)))
(unwind-protect
(progn
(setf (buffer-scanned-region buffer) '(1 10 20))
(let ((region (buffer-scanned-region buffer)))
(ok (equal region '(1 10 20)))))
(lem:delete-buffer buffer)))))
;;;; Unit Tests for update-scanned-region
(deftest update-scanned-region-new-region
(testing "Creates new region when none exists"
(let ((buffer (lem:make-buffer "*test-update-region*" :temporary t)))
(unwind-protect
(let ((tick (lem:buffer-modified-tick buffer)))
(update-scanned-region buffer 10 30)
(let ((region (buffer-scanned-region buffer)))
(ok (= (first region) tick))
(ok (= (second region) 10))
(ok (= (third region) 30))))
(lem:delete-buffer buffer)))))
(deftest update-scanned-region-extends-range
(testing "Extends existing region when tick matches"
(let ((buffer (lem:make-buffer "*test-extend-region*" :temporary t)))
(unwind-protect
(let ((tick (lem:buffer-modified-tick buffer)))
;; Initial scan: lines 10-30
(update-scanned-region buffer 10 30)
;; Extend upward: lines 5-20
(update-scanned-region buffer 5 20)
(let ((region (buffer-scanned-region buffer)))
(ok (= (first region) tick))
(ok (= (second region) 5) "Start should extend to 5")
(ok (= (third region) 30) "End should remain 30"))
;; Extend downward: lines 25-50
(update-scanned-region buffer 25 50)
(let ((region (buffer-scanned-region buffer)))
(ok (= (second region) 5) "Start should remain 5")
(ok (= (third region) 50) "End should extend to 50")))
(lem:delete-buffer buffer)))))
(deftest update-scanned-region-resets-on-tick-change
(testing "Resets region when tick changes (buffer modified)"
(let ((buffer (lem:make-buffer "*test-reset-region*" :temporary t)))
(unwind-protect
(progn
;; Initial scan: lines 10-30
(update-scanned-region buffer 10 30)
(let ((old-tick (first (buffer-scanned-region buffer))))
;; Modify buffer (this increments tick)
(lem:insert-string (lem:buffer-point buffer) "hello")
(let ((new-tick (lem:buffer-modified-tick buffer)))
(ok (/= old-tick new-tick) "Tick should change after edit")
;; Scan new region: lines 1-5
(update-scanned-region buffer 1 5)
(let ((region (buffer-scanned-region buffer)))
(ok (= (first region) new-tick) "Should use new tick")
(ok (= (second region) 1) "Start should be 1")
(ok (= (third region) 5) "End should be 5")))))
(lem:delete-buffer buffer)))))
;;;; Unit Tests for viewport-needs-scan-p
(deftest viewport-needs-scan-when-no-region
(testing "Needs scan when no region exists"
(let ((buffer (lem:make-buffer "*test-needs-scan*" :temporary t)))
(unwind-protect
(ok (viewport-needs-scan-p buffer 1 30))
(lem:delete-buffer buffer)))))
(deftest viewport-needs-scan-when-tick-changed
(testing "Needs scan when tick has changed"
(let ((buffer (lem:make-buffer "*test-needs-scan*" :temporary t)))
(unwind-protect
(progn
(update-scanned-region buffer 1 30)
;; Modify buffer
(lem:insert-string (lem:buffer-point buffer) "x")
;; Same viewport should need scan because tick changed
(ok (viewport-needs-scan-p buffer 1 30)))
(lem:delete-buffer buffer)))))
(deftest viewport-needs-scan-when-outside-scanned-range
(testing "Needs scan when viewport is outside scanned range"
(let ((buffer (lem:make-buffer "*test-needs-scan*" :temporary t)))
(unwind-protect
(progn
(update-scanned-region buffer 10 30)
;; Viewport before scanned region
(ok (viewport-needs-scan-p buffer 1 9)
"Should need scan for lines before scanned region")
;; Viewport after scanned region
(ok (viewport-needs-scan-p buffer 31 50)
"Should need scan for lines after scanned region")
;; Viewport overlapping start
(ok (viewport-needs-scan-p buffer 5 20)
"Should need scan when start is before scanned region")
;; Viewport overlapping end
(ok (viewport-needs-scan-p buffer 25 40)
"Should need scan when end is after scanned region"))
(lem:delete-buffer buffer)))))
(deftest viewport-no-scan-when-within-scanned-range
(testing "Does not need scan when viewport is within scanned range"
(let ((buffer (lem:make-buffer "*test-no-scan*" :temporary t)))
(unwind-protect
(progn
(update-scanned-region buffer 1 50)
;; Viewport fully within scanned region
(ng (viewport-needs-scan-p buffer 10 30)
"Should not need scan when fully within scanned region")
;; Exact match
(ng (viewport-needs-scan-p buffer 1 50)
"Should not need scan when exactly matching scanned region")
;; Single line within range
(ng (viewport-needs-scan-p buffer 25 25)
"Should not need scan for single line within range"))
(lem:delete-buffer buffer)))))
;;;; Integration Tests
(deftest syntax-scan-workflow
(testing "Complete workflow: open -> scroll -> edit -> scroll"
(let ((buffer (lem:make-buffer "*test-workflow*" :temporary t)))
(unwind-protect
(progn
;; Insert some content
(let ((point (lem:buffer-point buffer)))
(dotimes (i 100)
(lem:insert-string point (format nil "Line ~D~%" i))))
;; Clear the region to simulate fresh state
(setf (buffer-scanned-region buffer) nil)
;; Step 1: Initial viewport (lines 1-30)
(ok (viewport-needs-scan-p buffer 1 30)
"Initial viewport needs scan")
(update-scanned-region buffer 1 30)
(ng (viewport-needs-scan-p buffer 1 30)
"Same viewport should not need scan after update")
;; Step 2: Scroll down (lines 20-50) - extends beyond scanned
(ok (viewport-needs-scan-p buffer 20 50)
"Scrolled viewport needs scan (extends beyond scanned)")
(update-scanned-region buffer 20 50)
;; Region should now be 1-50
(let ((region (buffer-scanned-region buffer)))
(ok (= (second region) 1))
(ok (= (third region) 50)))
;; Step 3: Scroll within scanned range (lines 10-40)
(ng (viewport-needs-scan-p buffer 10 40)
"Viewport within scanned range should not need scan")
;; Step 4: Scroll to adjacent region (lines 45-75) - contiguous with 1-50
(ok (viewport-needs-scan-p buffer 45 75)
"Viewport extending beyond scanned range needs scan")
(update-scanned-region buffer 45 75)
;; Region should now be 1-75 (extended because contiguous)
(let ((region (buffer-scanned-region buffer)))
(ok (= (second region) 1))
(ok (= (third region) 75)))
;; Step 5: Jump to non-contiguous region (lines 90-100)
;; This should NOT extend but replace (gap between 75 and 90)
(ok (viewport-needs-scan-p buffer 90 100)
"Non-contiguous viewport needs scan")
(update-scanned-region buffer 90 100)
;; Region should now be 90-100 (replaced, not extended)
(let ((region (buffer-scanned-region buffer)))
(ok (= (second region) 90) "Start should be 90 (replaced)")
(ok (= (third region) 100) "End should be 100 (replaced)"))
;; Step 6: Previous region (1-75) now needs scan again
(ok (viewport-needs-scan-p buffer 1 30)
"Previous region needs scan after jump to non-contiguous area"))
(lem:delete-buffer buffer)))))
(deftest syntax-scan-after-edit
(testing "After buffer edit, viewport needs rescan"
(let ((buffer (lem:make-buffer "*test-edit*" :temporary t)))
(unwind-protect
(progn
;; Setup: insert content and scan initial viewport
(let ((point (lem:buffer-point buffer)))
(dotimes (i 50)
(lem:insert-string point (format nil "Line ~D~%" i))))
(setf (buffer-scanned-region buffer) nil)
;; Scan lines 1-30
(update-scanned-region buffer 1 30)
(ng (viewport-needs-scan-p buffer 1 30)
"Initial scan complete")
;; Edit buffer - tick changes
(lem:buffer-start (lem:buffer-point buffer))
(lem:insert-string (lem:buffer-point buffer) "EDIT\n")
;; After edit, same viewport needs scan (tick changed)
(ok (viewport-needs-scan-p buffer 1 30)
"After edit, viewport needs scan due to tick change")
;; Scan the viewport again
(update-scanned-region buffer 1 30)
(ng (viewport-needs-scan-p buffer 1 30)
"After rescan, viewport should not need scan")
;; Different viewport also needs scan
(ok (viewport-needs-scan-p buffer 35 50)
"Unscanned viewport needs scan"))
(lem:delete-buffer buffer)))))
(deftest scanned-region-boundary-cases
(testing "Boundary cases for scanned region"
(let ((buffer (lem:make-buffer "*test-boundary*" :temporary t)))
(unwind-protect
(progn
;; Single line scan
(update-scanned-region buffer 10 10)
(ng (viewport-needs-scan-p buffer 10 10)
"Single line should be marked as scanned")
(ok (viewport-needs-scan-p buffer 9 10)
"Adjacent line before should need scan")
(ok (viewport-needs-scan-p buffer 10 11)
"Adjacent line after should need scan")
;; Extend to adjacent lines
(update-scanned-region buffer 9 11)
(ng (viewport-needs-scan-p buffer 9 11)
"Extended range should be scanned"))
(lem:delete-buffer buffer)))))