Skip to content

Commit c15ed65

Browse files
committed
Refactor _pyrepl refresh invalidation state
Make buffer, layout, prompt, overlay, and full redraw causes explicit in Reader. The invalidation matrix is still cursed, but at least now it is visible.
1 parent 61e39f1 commit c15ed65

File tree

7 files changed

+247
-73
lines changed

7 files changed

+247
-73
lines changed

Lib/_pyrepl/commands.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def kill_range(self, start: int, end: int) -> None:
7575
else:
7676
r.kill_ring.append(text)
7777
r.pos = start
78-
r.dirty = True
78+
r.invalidate_buffer(start)
7979

8080

8181
class YankCommand(Command):
@@ -126,27 +126,27 @@ def do(self) -> None:
126126
r.arg = 10 * r.arg - d
127127
else:
128128
r.arg = 10 * r.arg + d
129-
r.dirty = True
129+
r.invalidate_prompt()
130130

131131

132132
class clear_screen(Command):
133133
def do(self) -> None:
134134
r = self.reader
135135
trace("command.clear_screen")
136136
r.console.clear()
137-
r.dirty = True
137+
r.invalidate_full()
138138

139139

140140
class refresh(Command):
141141
def do(self) -> None:
142142
trace("command.refresh")
143-
self.reader.dirty = True
143+
self.reader.invalidate_full()
144144

145145

146146
class repaint(Command):
147147
def do(self) -> None:
148148
trace("command.repaint")
149-
self.reader.dirty = True
149+
self.reader.invalidate_full()
150150
self.reader.console.repaint()
151151

152152

@@ -212,9 +212,10 @@ def do(self) -> None:
212212
repl = len(r.kill_ring[-1])
213213
r.kill_ring.insert(0, r.kill_ring.pop())
214214
t = r.kill_ring[-1]
215+
start = r.pos - repl
215216
b[r.pos - repl : r.pos] = t
216217
r.pos = r.pos - repl + len(t)
217-
r.dirty = True
218+
r.invalidate_buffer(start)
218219

219220

220221
class interrupt(FinishCommand):
@@ -246,7 +247,7 @@ def do(self) -> None:
246247
r.console.prepare()
247248
r.pos = p
248249
# r.posxy = 0, 0 # XXX this is invalid
249-
r.dirty = True
250+
r.invalidate_full()
250251
trace("command.suspend sync_rendered_screen")
251252
r.console.sync_rendered_screen(RenderedScreen.empty(), r.console.posxy)
252253

@@ -374,14 +375,15 @@ class self_insert(EditCommand):
374375
def do(self) -> None:
375376
r = self.reader
376377
text = self.event * r.get_arg()
378+
start = r.pos
377379
r.insert(text)
378380
if r.paste_mode:
379381
data = ""
380382
ev = r.console.getpending()
381383
data += ev.data
382384
if data:
383385
r.insert(data)
384-
r.last_refresh_cache.invalidated = True
386+
r.invalidate_buffer(start)
385387

386388

387389
class insert_nl(EditCommand):
@@ -405,20 +407,23 @@ def do(self) -> None:
405407
del b[s]
406408
b.insert(t, c)
407409
r.pos = t
408-
r.dirty = True
410+
r.invalidate_buffer(s)
409411

410412

411413
class backspace(EditCommand):
412414
def do(self) -> None:
413415
r = self.reader
414416
b = r.buffer
417+
changed_from: int | None = None
415418
for i in range(r.get_arg()):
416419
if r.pos > 0:
417420
r.pos -= 1
418421
del b[r.pos]
419-
r.dirty = True
422+
changed_from = r.pos if changed_from is None else min(changed_from, r.pos)
420423
else:
421424
self.reader.error("can't backspace at start")
425+
if changed_from is not None:
426+
r.invalidate_buffer(changed_from)
422427

423428

424429
class delete(EditCommand):
@@ -436,12 +441,15 @@ def do(self) -> None:
436441
r.console.finish()
437442
raise EOFError
438443

444+
changed_from: int | None = None
439445
for i in range(r.get_arg()):
440446
if r.pos != len(b):
441447
del b[r.pos]
442-
r.dirty = True
448+
changed_from = r.pos if changed_from is None else min(changed_from, r.pos)
443449
else:
444450
self.reader.error("end of buffer")
451+
if changed_from is not None:
452+
r.invalidate_buffer(changed_from)
445453

446454

447455
class accept(FinishCommand):
@@ -493,7 +501,7 @@ def do(self) -> None:
493501
class paste_mode(Command):
494502
def do(self) -> None:
495503
self.reader.paste_mode = not self.reader.paste_mode
496-
self.reader.dirty = True
504+
self.reader.invalidate_prompt()
497505

498506

499507
class perform_bracketed_paste(Command):
@@ -510,4 +518,3 @@ def do(self) -> None:
510518
s=time.time() - start,
511519
)
512520
self.reader.insert(data.replace(done, ""))
513-
self.reader.last_refresh_cache.invalidated = True

Lib/_pyrepl/completing_reader.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def do(self) -> None:
181181
elif len(completions) == 1:
182182
if completions_unchangable and len(completions[0]) == len(stem):
183183
r.msg = "[ sole completion ]"
184-
r.dirty = True
184+
r.invalidate_message()
185185
r.insert(completions[0][len(stem):])
186186
else:
187187
p = prefix(completions, len(stem))
@@ -193,15 +193,15 @@ def do(self) -> None:
193193
r.cmpltn_menu, r.cmpltn_menu_end = build_menu(
194194
r.console, completions, r.cmpltn_menu_end,
195195
r.use_brackets, r.sort_in_column)
196-
r.dirty = True
196+
r.invalidate_overlay()
197197
elif not r.cmpltn_menu_visible:
198198
r.cmpltn_message_visible = True
199199
if stem + p in completions:
200200
r.msg = "[ complete but not unique ]"
201-
r.dirty = True
201+
r.invalidate_message()
202202
else:
203203
r.msg = "[ not unique ]"
204-
r.dirty = True
204+
r.invalidate_message()
205205

206206

207207
class self_insert(commands.self_insert):
@@ -221,6 +221,7 @@ def do(self) -> None:
221221
r.cmpltn_menu, r.cmpltn_menu_end = build_menu(
222222
r.console, completions, 0,
223223
r.use_brackets, r.sort_in_column)
224+
r.invalidate_overlay()
224225
else:
225226
r.cmpltn_reset()
226227

@@ -273,6 +274,8 @@ def finish(self) -> None:
273274
self.cmpltn_reset()
274275

275276
def cmpltn_reset(self) -> None:
277+
if getattr(self, "cmpltn_menu_visible", False):
278+
self.invalidate_overlay()
276279
self.cmpltn_menu = []
277280
self.cmpltn_menu_visible = False
278281
self.cmpltn_message_visible = False

Lib/_pyrepl/historical_reader.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def do(self) -> None:
9090
if r.get_unicode() != r.history[r.historyi]:
9191
r.buffer = list(r.history[r.historyi])
9292
r.pos = len(r.buffer)
93-
r.dirty = True
93+
r.invalidate_buffer(0)
9494

9595

9696
class first_history(commands.Command):
@@ -130,10 +130,11 @@ def do(self) -> None:
130130
o = len(r.yank_arg_yanked)
131131
else:
132132
o = 0
133+
start = r.pos - o
133134
b[r.pos - o : r.pos] = list(w)
134135
r.yank_arg_yanked = w
135136
r.pos += len(w) - o
136-
r.dirty = True
137+
r.invalidate_buffer(start)
137138

138139

139140
class forward_history_isearch(commands.Command):
@@ -142,15 +143,15 @@ def do(self) -> None:
142143
r.isearch_direction = ISEARCH_DIRECTION_FORWARDS
143144
r.isearch_start = r.historyi, r.pos
144145
r.isearch_term = ""
145-
r.dirty = True
146+
r.invalidate_prompt()
146147
r.push_input_trans(r.isearch_trans)
147148

148149

149150
class reverse_history_isearch(commands.Command):
150151
def do(self) -> None:
151152
r = self.reader
152153
r.isearch_direction = ISEARCH_DIRECTION_BACKWARDS
153-
r.dirty = True
154+
r.invalidate_prompt()
154155
r.isearch_term = ""
155156
r.push_input_trans(r.isearch_trans)
156157
r.isearch_start = r.historyi, r.pos
@@ -163,15 +164,15 @@ def do(self) -> None:
163164
r.pop_input_trans()
164165
r.select_item(r.isearch_start[0])
165166
r.pos = r.isearch_start[1]
166-
r.dirty = True
167+
r.invalidate_prompt()
167168

168169

169170
class isearch_add_character(commands.Command):
170171
def do(self) -> None:
171172
r = self.reader
172173
b = r.buffer
173174
r.isearch_term += self.event[-1]
174-
r.dirty = True
175+
r.invalidate_prompt()
175176
p = r.pos + len(r.isearch_term) - 1
176177
if b[p : p + 1] != [r.isearch_term[-1]]:
177178
r.isearch_next()
@@ -182,7 +183,7 @@ def do(self) -> None:
182183
r = self.reader
183184
if len(r.isearch_term) > 0:
184185
r.isearch_term = r.isearch_term[:-1]
185-
r.dirty = True
186+
r.invalidate_prompt()
186187
else:
187188
r.error("nothing to rubout")
188189

@@ -207,7 +208,7 @@ def do(self) -> None:
207208
r.isearch_direction = ISEARCH_DIRECTION_NONE
208209
r.console.forgetinput()
209210
r.pop_input_trans()
210-
r.dirty = True
211+
r.invalidate_prompt()
211212

212213

213214
@dataclass
@@ -278,8 +279,7 @@ def select_item(self, i: int) -> None:
278279
self.buffer = list(buf)
279280
self.historyi = i
280281
self.pos = len(self.buffer)
281-
self.dirty = True
282-
self.last_refresh_cache.invalidated = True
282+
self.invalidate_buffer(0)
283283

284284
def get_item(self, i: int) -> str:
285285
if i != len(self.history):
@@ -357,7 +357,7 @@ def search_next(self, *, forwards: bool) -> None:
357357
if forwards and not match_prefix:
358358
self.pos = 0
359359
self.buffer = []
360-
self.dirty = True
360+
self.invalidate_buffer(0)
361361
else:
362362
self.error("not found")
363363
return

0 commit comments

Comments
 (0)