@@ -309,10 +309,10 @@ class Reader:
309309 @dataclass
310310 class RefreshCache :
311311 render_lines : list [RenderLine ] = field (default_factory = list )
312- layout_rows : list [LayoutRow ] = field (init = False )
312+ layout_rows : list [LayoutRow ] = field (default_factory = list )
313313 line_end_offsets : list [int ] = field (default_factory = list )
314- pos : int = field ( init = False )
315- dimensions : Dimensions = field ( init = False )
314+ pos : int = 0
315+ dimensions : Dimensions = ( 0 , 0 )
316316
317317 def update_cache (self ,
318318 reader : Reader ,
@@ -338,6 +338,13 @@ def get_cached_location(
338338 * ,
339339 reuse_full : bool = False ,
340340 ) -> tuple [int , int ]:
341+ """Return (buffer_offset, num_reusable_lines) for incremental refresh.
342+
343+ Three paths:
344+ - reuse_full (overlay/message-only): reuse all cached lines.
345+ - buffer_from_pos=None (full rebuild): rewind to common cursor pos.
346+ - explicit buffer_from_pos: reuse lines before that position.
347+ """
341348 if reuse_full :
342349 if self .line_end_offsets :
343350 last_offset = self .line_end_offsets [- 1 ]
@@ -409,6 +416,9 @@ def calc_screen(self) -> RenderedScreen:
409416 self ,
410417 reuse_full = True ,
411418 )
419+ assert not self .last_refresh_cache .line_end_offsets or (
420+ self .last_refresh_cache .line_end_offsets [- 1 ] >= len (self .buffer )
421+ ), "Buffer modified without invalidate_buffer() call"
412422 else :
413423 offset , num_common_lines = self .last_refresh_cache .get_cached_location (
414424 self ,
@@ -435,14 +445,7 @@ def calc_screen(self) -> RenderedScreen:
435445 if not source_lines :
436446 # reuse_full path: _build_source_lines didn't run,
437447 # so lxy wasn't updated. Derive it from the buffer.
438- buf = self .buffer [:self .pos ]
439- lineno = buf .count ("\n " )
440- if lineno :
441- last_nl = len (buf ) - 1 - buf [::- 1 ].index ("\n " )
442- col = self .pos - last_nl - 1
443- else :
444- col = self .pos
445- self .lxy = col , lineno
448+ self .lxy = self ._compute_lxy ()
446449 self .last_refresh_cache .update_cache (
447450 self ,
448451 base_render_lines ,
@@ -461,12 +464,22 @@ def _buffer_refresh_from_pos(self) -> int:
461464 return buffer_from_pos
462465 return 0
463466
467+ def _compute_lxy (self ) -> CursorXY :
468+ """Derive logical cursor (col, lineno) from the buffer and pos."""
469+ text = "" .join (self .buffer [:self .pos ])
470+ lineno = text .count ("\n " )
471+ if lineno :
472+ col = self .pos - text .rindex ("\n " ) - 1
473+ else :
474+ col = self .pos
475+ return col , lineno
476+
464477 def _build_source_lines (
465478 self ,
466479 offset : int ,
467480 first_lineno : int ,
468481 ) -> tuple [SourceLine , ...]:
469- if offset == len (self .buffer ) and first_lineno > 0 :
482+ if offset == len (self .buffer ) and offset > 0 :
470483 return ()
471484
472485 pos = self .pos - offset
0 commit comments