2222from __future__ import annotations
2323import os
2424import time
25+ from typing import TYPE_CHECKING
2526
2627# Categories of actions:
2728# killing
3233# finishing
3334# [completion]
3435
36+ from .render import RenderedScreen
3537from .trace import trace
3638
3739# types
38- if False :
40+ if TYPE_CHECKING :
3941 from .historical_reader import HistoricalReader
4042
4143
@@ -74,7 +76,7 @@ def kill_range(self, start: int, end: int) -> None:
7476 else :
7577 r .kill_ring .append (text )
7678 r .pos = start
77- r .dirty = True
79+ r .invalidate_buffer ( start )
7880
7981
8082class YankCommand (Command ):
@@ -125,24 +127,27 @@ def do(self) -> None:
125127 r .arg = 10 * r .arg - d
126128 else :
127129 r .arg = 10 * r .arg + d
128- r .dirty = True
130+ r .invalidate_prompt ()
129131
130132
131133class clear_screen (Command ):
132134 def do (self ) -> None :
133135 r = self .reader
136+ trace ("command.clear_screen" )
134137 r .console .clear ()
135- r .dirty = True
138+ r .invalidate_full ()
136139
137140
138141class refresh (Command ):
139142 def do (self ) -> None :
140- self .reader .dirty = True
143+ trace ("command.refresh" )
144+ self .reader .invalidate_full ()
141145
142146
143147class repaint (Command ):
144148 def do (self ) -> None :
145- self .reader .dirty = True
149+ trace ("command.repaint" )
150+ self .reader .invalidate_full ()
146151 self .reader .console .repaint ()
147152
148153
@@ -208,9 +213,10 @@ def do(self) -> None:
208213 repl = len (r .kill_ring [- 1 ])
209214 r .kill_ring .insert (0 , r .kill_ring .pop ())
210215 t = r .kill_ring [- 1 ]
216+ start = r .pos - repl
211217 b [r .pos - repl : r .pos ] = t
212218 r .pos = r .pos - repl + len (t )
213- r .dirty = True
219+ r .invalidate_buffer ( start )
214220
215221
216222class interrupt (FinishCommand ):
@@ -242,8 +248,9 @@ def do(self) -> None:
242248 r .console .prepare ()
243249 r .pos = p
244250 # r.posxy = 0, 0 # XXX this is invalid
245- r .dirty = True
246- r .console .screen = []
251+ r .invalidate_full ()
252+ trace ("command.suspend sync_rendered_screen" )
253+ r .console .sync_rendered_screen (RenderedScreen .empty (), r .console .posxy )
247254
248255
249256class up (MotionCommand ):
@@ -369,14 +376,15 @@ class self_insert(EditCommand):
369376 def do (self ) -> None :
370377 r = self .reader
371378 text = self .event * r .get_arg ()
379+ start = r .pos
372380 r .insert (text )
373381 if r .paste_mode :
374382 data = ""
375383 ev = r .console .getpending ()
376384 data += ev .data
377385 if data :
378386 r .insert (data )
379- r .last_refresh_cache . invalidated = True
387+ r .invalidate_buffer ( start )
380388
381389
382390class insert_nl (EditCommand ):
@@ -400,20 +408,23 @@ def do(self) -> None:
400408 del b [s ]
401409 b .insert (t , c )
402410 r .pos = t
403- r .dirty = True
411+ r .invalidate_buffer ( s )
404412
405413
406414class backspace (EditCommand ):
407415 def do (self ) -> None :
408416 r = self .reader
409417 b = r .buffer
418+ changed_from : int | None = None
410419 for i in range (r .get_arg ()):
411420 if r .pos > 0 :
412421 r .pos -= 1
413422 del b [r .pos ]
414- r . dirty = True
423+ changed_from = r . pos if changed_from is None else min ( changed_from , r . pos )
415424 else :
416425 self .reader .error ("can't backspace at start" )
426+ if changed_from is not None :
427+ r .invalidate_buffer (changed_from )
417428
418429
419430class delete (EditCommand ):
@@ -431,12 +442,15 @@ def do(self) -> None:
431442 r .console .finish ()
432443 raise EOFError
433444
445+ changed_from : int | None = None
434446 for i in range (r .get_arg ()):
435447 if r .pos != len (b ):
436448 del b [r .pos ]
437- r . dirty = True
449+ changed_from = r . pos if changed_from is None else min ( changed_from , r . pos )
438450 else :
439451 self .reader .error ("end of buffer" )
452+ if changed_from is not None :
453+ r .invalidate_buffer (changed_from )
440454
441455
442456class accept (FinishCommand ):
@@ -450,6 +464,7 @@ def do(self) -> None:
450464
451465 with self .reader .suspend ():
452466 self .reader .msg = _sitebuiltins ._Helper ()() # type: ignore[assignment]
467+ self .reader .invalidate_prompt ()
453468
454469
455470class invalid_key (Command ):
@@ -470,22 +485,24 @@ def do(self) -> None:
470485 from .pager import get_pager
471486 from site import gethistoryfile
472487
488+ # After the pager exits, the screen state is unknown (Unix may
489+ # restore via alternate screen, Windows shows pager output).
490+ # Clear and force a full redraw at the end for consistency.
491+ self .reader .console .clear ()
492+
473493 history = os .linesep .join (self .reader .history [:])
474494 self .reader .console .restore ()
475495 pager = get_pager ()
476496 pager (history , gethistoryfile ())
477497 self .reader .console .prepare ()
478498
479- # We need to copy over the state so that it's consistent between
480- # console and reader, and console does not overwrite/append stuff
481- self .reader .console .screen = self .reader .screen .copy ()
482- self .reader .console .posxy = self .reader .cxy
499+ self .reader .invalidate_full ()
483500
484501
485502class paste_mode (Command ):
486503 def do (self ) -> None :
487504 self .reader .paste_mode = not self .reader .paste_mode
488- self .reader .dirty = True
505+ self .reader .invalidate_prompt ()
489506
490507
491508class perform_bracketed_paste (Command ):
@@ -502,4 +519,3 @@ def do(self) -> None:
502519 s = time .time () - start ,
503520 )
504521 self .reader .insert (data .replace (done , "" ))
505- self .reader .last_refresh_cache .invalidated = True
0 commit comments