@@ -83,18 +83,6 @@ pub struct TerminalSnapshot {
83
83
pub screen_lines : usize ,
84
84
}
85
85
86
- /// Manages pending terminal updates and their rendering state.
87
- ///
88
- /// This struct handles two types of updates:
89
- /// 1. **Synchronized updates** - These come with damage events and create snapshots immediately.
90
- /// Snapshots capture the terminal state at the time of the damage event, avoiding the need
91
- /// to lock the terminal during rendering. This is ideal for synchronized terminal sequences
92
- /// where we know exactly what changed.
93
- ///
94
- /// 2. **Non-synchronized updates** - These come via Wakeup events and defer damage checking.
95
- /// Instead of creating snapshots immediately (which would require locking the terminal for
96
- /// each update), we just mark the update as pending and check for damage at render time.
97
- /// This reduces lock contention for rapid, non-synchronized terminal output like in notcurses-demo.
98
86
#[ derive( Debug , Default ) ]
99
87
pub struct PendingUpdate {
100
88
/// Whether there's any pending update that needs rendering
@@ -110,25 +98,7 @@ impl PendingUpdate {
110
98
111
99
/// Mark as needing to check for damage on next render
112
100
/// This is used by Wakeup events to defer damage calculation
113
- pub fn mark_for_damage_check ( & mut self ) {
114
- self . dirty = true ;
115
- }
116
-
117
- /// Mark as needing update.
118
- /// The actual snapshot will be computed at render time.
119
- pub fn invalidate < U : rio_backend:: event:: EventListener > (
120
- & mut self ,
121
- _damage : TerminalDamage ,
122
- _terminal : & FairMutex < Crosswords < U > > ,
123
- ) {
124
- self . dirty = true ;
125
- }
126
-
127
- /// Mark as needing full update
128
- pub fn invalidate_full < U : rio_backend:: event:: EventListener > (
129
- & mut self ,
130
- _terminal : & FairMutex < Crosswords < U > > ,
131
- ) {
101
+ pub fn set_dirty ( & mut self ) {
132
102
self . dirty = true ;
133
103
}
134
104
@@ -137,248 +107,3 @@ impl PendingUpdate {
137
107
self . dirty = false ;
138
108
}
139
109
}
140
-
141
- #[ cfg( test) ]
142
- mod tests {
143
- use super :: * ;
144
- use rio_backend:: crosswords:: pos:: { Column , Line , Pos } ;
145
- use rio_backend:: crosswords:: { Crosswords , LineDamage } ;
146
- use rio_backend:: event:: VoidListener ;
147
- use std:: collections:: BTreeSet ;
148
-
149
- // Helper to create a test terminal
150
- fn create_test_terminal ( ) -> FairMutex < Crosswords < VoidListener > > {
151
- use rio_backend:: ansi:: CursorShape ;
152
- use rio_backend:: crosswords:: CrosswordsSize ;
153
- use rio_window:: window:: WindowId ;
154
-
155
- let dimensions = CrosswordsSize :: new ( 80 , 24 ) ;
156
- let terminal = Crosswords :: new (
157
- dimensions,
158
- CursorShape :: Block ,
159
- VoidListener ,
160
- WindowId :: from ( 0 ) ,
161
- 0 ,
162
- ) ;
163
- FairMutex :: new ( terminal)
164
- }
165
-
166
- #[ test]
167
- fn test_hint_matches_persistence ( ) {
168
- let mut content = RenderableContent :: from_cursor_config ( & CursorConfig :: default ( ) ) ;
169
-
170
- // Set hint matches
171
- let matches = vec ! [
172
- Pos :: new( Line ( 0 ) , Column ( 0 ) ) ..=Pos :: new( Line ( 0 ) , Column ( 4 ) ) ,
173
- Pos :: new( Line ( 1 ) , Column ( 5 ) ) ..=Pos :: new( Line ( 1 ) , Column ( 9 ) ) ,
174
- ] ;
175
- content. hint_matches = Some ( matches. clone ( ) ) ;
176
-
177
- // Verify matches persist
178
- assert_eq ! ( content. hint_matches, Some ( matches) ) ;
179
- }
180
-
181
- #[ test]
182
- fn test_hint_labels_with_damage ( ) {
183
- let mut content = RenderableContent :: from_cursor_config ( & CursorConfig :: default ( ) ) ;
184
- let terminal = create_test_terminal ( ) ;
185
-
186
- // Reset terminal damage to start fresh
187
- terminal. lock ( ) . reset_damage ( ) ;
188
-
189
- // Add hint labels
190
- content. hint_labels . push ( HintLabel {
191
- position : Pos :: new ( Line ( 5 ) , Column ( 10 ) ) ,
192
- label : vec ! [ 'a' , 'b' ] ,
193
- is_first : true ,
194
- } ) ;
195
- content. hint_labels . push ( HintLabel {
196
- position : Pos :: new ( Line ( 10 ) , Column ( 20 ) ) ,
197
- label : vec ! [ 'c' ] ,
198
- is_first : false ,
199
- } ) ;
200
-
201
- // Create damage for the hint label lines
202
- let mut damaged_lines = BTreeSet :: new ( ) ;
203
- damaged_lines. insert ( LineDamage :: new ( 5 , true ) ) ;
204
- damaged_lines. insert ( LineDamage :: new ( 10 , true ) ) ;
205
- let damage = TerminalDamage :: Partial ( damaged_lines) ;
206
-
207
- // Invalidate with damage
208
- content. pending_update . invalidate ( damage, & terminal) ;
209
-
210
- // Verify update is marked as dirty
211
- assert ! ( content. pending_update. is_dirty( ) ) ;
212
-
213
- // Take snapshot and verify damage
214
- let snapshot = content. pending_update . take_snapshot ( ) . unwrap ( ) ;
215
- match snapshot. damage {
216
- TerminalDamage :: Partial ( lines) => {
217
- assert_eq ! ( lines. len( ) , 2 ) ;
218
- assert ! ( lines. iter( ) . any( |l| l. line == 5 ) ) ;
219
- assert ! ( lines. iter( ) . any( |l| l. line == 10 ) ) ;
220
- }
221
- _ => panic ! ( "Expected partial damage" ) ,
222
- }
223
- }
224
-
225
- #[ test]
226
- fn test_clear_hint_state_triggers_full_damage ( ) {
227
- let mut content = RenderableContent :: from_cursor_config ( & CursorConfig :: default ( ) ) ;
228
- let terminal = create_test_terminal ( ) ;
229
-
230
- // Set hint state
231
- content. hint_matches = Some ( vec ! [
232
- Pos :: new( Line ( 0 ) , Column ( 0 ) ) ..=Pos :: new( Line ( 0 ) , Column ( 4 ) ) ,
233
- ] ) ;
234
- content. hint_labels . push ( HintLabel {
235
- position : Pos :: new ( Line ( 0 ) , Column ( 0 ) ) ,
236
- label : vec ! [ 'a' ] ,
237
- is_first : true ,
238
- } ) ;
239
-
240
- // Clear hint state and trigger full damage
241
- content. hint_matches = None ;
242
- content. hint_labels . clear ( ) ;
243
- content
244
- . pending_update
245
- . invalidate ( TerminalDamage :: Full , & terminal) ;
246
-
247
- // Verify update is marked as dirty
248
- assert ! ( content. pending_update. is_dirty( ) ) ;
249
-
250
- // Take snapshot and verify full damage
251
- let snapshot = content. pending_update . take_snapshot ( ) . unwrap ( ) ;
252
- assert_eq ! ( snapshot. damage, TerminalDamage :: Full ) ;
253
- }
254
-
255
- #[ test]
256
- fn test_damage_merging ( ) {
257
- let mut content = RenderableContent :: from_cursor_config ( & CursorConfig :: default ( ) ) ;
258
- let terminal = create_test_terminal ( ) ;
259
-
260
- // Reset terminal damage to start fresh
261
- terminal. lock ( ) . reset_damage ( ) ;
262
-
263
- // First invalidation with partial damage
264
- let mut damaged_lines1 = BTreeSet :: new ( ) ;
265
- damaged_lines1. insert ( LineDamage :: new ( 5 , true ) ) ;
266
- content
267
- . pending_update
268
- . invalidate ( TerminalDamage :: Partial ( damaged_lines1) , & terminal) ;
269
-
270
- // Second invalidation with different partial damage
271
- let mut damaged_lines2 = BTreeSet :: new ( ) ;
272
- damaged_lines2. insert ( LineDamage :: new ( 10 , true ) ) ;
273
- damaged_lines2. insert ( LineDamage :: new ( 15 , true ) ) ;
274
- content
275
- . pending_update
276
- . invalidate ( TerminalDamage :: Partial ( damaged_lines2) , & terminal) ;
277
-
278
- // Take snapshot and verify merged damage
279
- let snapshot = content. pending_update . take_snapshot ( ) . unwrap ( ) ;
280
- match snapshot. damage {
281
- TerminalDamage :: Partial ( lines) => {
282
- assert_eq ! ( lines. len( ) , 3 ) ;
283
- assert ! ( lines. iter( ) . any( |l| l. line == 5 ) ) ;
284
- assert ! ( lines. iter( ) . any( |l| l. line == 10 ) ) ;
285
- assert ! ( lines. iter( ) . any( |l| l. line == 15 ) ) ;
286
- }
287
- _ => panic ! ( "Expected partial damage" ) ,
288
- }
289
- }
290
-
291
- #[ test]
292
- fn test_full_damage_overrides_partial ( ) {
293
- let mut content = RenderableContent :: from_cursor_config ( & CursorConfig :: default ( ) ) ;
294
- let terminal = create_test_terminal ( ) ;
295
-
296
- // First invalidation with partial damage
297
- let mut damaged_lines = BTreeSet :: new ( ) ;
298
- damaged_lines. insert ( LineDamage :: new ( 5 , true ) ) ;
299
- content
300
- . pending_update
301
- . invalidate ( TerminalDamage :: Partial ( damaged_lines) , & terminal) ;
302
-
303
- // Second invalidation with full damage
304
- content
305
- . pending_update
306
- . invalidate ( TerminalDamage :: Full , & terminal) ;
307
-
308
- // Take snapshot and verify full damage
309
- let snapshot = content. pending_update . take_snapshot ( ) . unwrap ( ) ;
310
- assert_eq ! ( snapshot. damage, TerminalDamage :: Full ) ;
311
- }
312
-
313
- #[ test]
314
- fn test_hint_state_update_flow ( ) {
315
- let mut content = RenderableContent :: from_cursor_config ( & CursorConfig :: default ( ) ) ;
316
- let terminal = create_test_terminal ( ) ;
317
-
318
- // Simulate hint activation
319
- content. hint_matches = Some ( vec ! [
320
- Pos :: new( Line ( 2 ) , Column ( 5 ) ) ..=Pos :: new( Line ( 2 ) , Column ( 10 ) ) ,
321
- Pos :: new( Line ( 5 ) , Column ( 0 ) ) ..=Pos :: new( Line ( 5 ) , Column ( 7 ) ) ,
322
- ] ) ;
323
- content. hint_labels . push ( HintLabel {
324
- position : Pos :: new ( Line ( 2 ) , Column ( 5 ) ) ,
325
- label : vec ! [ 'a' ] ,
326
- is_first : true ,
327
- } ) ;
328
- content. hint_labels . push ( HintLabel {
329
- position : Pos :: new ( Line ( 5 ) , Column ( 0 ) ) ,
330
- label : vec ! [ 'b' ] ,
331
- is_first : true ,
332
- } ) ;
333
-
334
- // Trigger damage for hint lines
335
- let mut damaged_lines = BTreeSet :: new ( ) ;
336
- damaged_lines. insert ( LineDamage :: new ( 2 , true ) ) ;
337
- damaged_lines. insert ( LineDamage :: new ( 5 , true ) ) ;
338
- content
339
- . pending_update
340
- . invalidate ( TerminalDamage :: Partial ( damaged_lines) , & terminal) ;
341
-
342
- assert ! ( content. pending_update. is_dirty( ) ) ;
343
-
344
- // Simulate hint deactivation
345
- content. hint_matches = None ;
346
- content. hint_labels . clear ( ) ;
347
- content
348
- . pending_update
349
- . invalidate ( TerminalDamage :: Full , & terminal) ;
350
-
351
- // Verify state after deactivation
352
- assert ! ( content. hint_matches. is_none( ) ) ;
353
- assert ! ( content. hint_labels. is_empty( ) ) ;
354
- assert ! ( content. pending_update. is_dirty( ) ) ;
355
- }
356
-
357
- #[ test]
358
- fn test_multiple_snapshots ( ) {
359
- let mut content = RenderableContent :: from_cursor_config ( & CursorConfig :: default ( ) ) ;
360
- let terminal = create_test_terminal ( ) ;
361
-
362
- // First update
363
- content
364
- . pending_update
365
- . invalidate ( TerminalDamage :: Full , & terminal) ;
366
- assert ! ( content. pending_update. is_dirty( ) ) ;
367
-
368
- // Reset dirty flag - simulating render
369
- content. pending_update . reset ( ) ;
370
- assert ! ( !content. pending_update. is_dirty( ) ) ;
371
-
372
- // Second update
373
- let mut damaged_lines = BTreeSet :: new ( ) ;
374
- damaged_lines. insert ( LineDamage :: new ( 3 , true ) ) ;
375
- content
376
- . pending_update
377
- . invalidate ( TerminalDamage :: Partial ( damaged_lines) , & terminal) ;
378
- assert ! ( content. pending_update. is_dirty( ) ) ;
379
-
380
- // Reset dirty flag again
381
- content. pending_update . reset ( ) ;
382
- assert ! ( !content. pending_update. is_dirty( ) ) ;
383
- }
384
- }
0 commit comments