25
25
26
26
from contextlib import contextmanager
27
27
from dataclasses import dataclass , field , fields
28
- import unicodedata
29
28
from _colorize import can_colorize , ANSIColors
30
29
31
30
32
31
from . import commands , console , input
33
- from .utils import wlen , unbracket , str_width
32
+ from .utils import wlen , unbracket , disp_str
34
33
from .trace import trace
35
34
36
35
39
38
from .types import Callback , SimpleContextManager , KeySpec , CommandName
40
39
41
40
42
- def disp_str (buffer : str ) -> tuple [str , list [int ]]:
43
- """disp_str(buffer:string) -> (string, [int])
44
-
45
- Return the string that should be the printed representation of
46
- |buffer| and a list detailing where the characters of |buffer|
47
- get used up. E.g.:
48
-
49
- >>> disp_str(chr(3))
50
- ('^C', [1, 0])
51
-
52
- """
53
- b : list [int ] = []
54
- s : list [str ] = []
55
- for c in buffer :
56
- if c == '\x1a ' :
57
- s .append (c )
58
- b .append (2 )
59
- elif ord (c ) < 128 :
60
- s .append (c )
61
- b .append (1 )
62
- elif unicodedata .category (c ).startswith ("C" ):
63
- c = r"\u%04x" % ord (c )
64
- s .append (c )
65
- b .append (len (c ))
66
- else :
67
- s .append (c )
68
- b .append (str_width (c ))
69
- return "" .join (s ), b
70
-
71
-
72
41
# syntax classes:
73
42
74
43
SYNTAX_WHITESPACE , SYNTAX_WORD , SYNTAX_SYMBOL = range (3 )
@@ -347,14 +316,12 @@ def calc_screen(self) -> list[str]:
347
316
pos -= offset
348
317
349
318
prompt_from_cache = (offset and self .buffer [offset - 1 ] != "\n " )
350
-
351
319
lines = "" .join (self .buffer [offset :]).split ("\n " )
352
-
353
320
cursor_found = False
354
321
lines_beyond_cursor = 0
355
322
for ln , line in enumerate (lines , num_common_lines ):
356
- ll = len (line )
357
- if 0 <= pos <= ll :
323
+ line_len = len (line )
324
+ if 0 <= pos <= line_len :
358
325
self .lxy = pos , ln
359
326
cursor_found = True
360
327
elif cursor_found :
@@ -368,34 +335,34 @@ def calc_screen(self) -> list[str]:
368
335
prompt_from_cache = False
369
336
prompt = ""
370
337
else :
371
- prompt = self .get_prompt (ln , ll >= pos >= 0 )
338
+ prompt = self .get_prompt (ln , line_len >= pos >= 0 )
372
339
while "\n " in prompt :
373
340
pre_prompt , _ , prompt = prompt .partition ("\n " )
374
341
last_refresh_line_end_offsets .append (offset )
375
342
screen .append (pre_prompt )
376
343
screeninfo .append ((0 , []))
377
- pos -= ll + 1
378
- prompt , lp = self .process_prompt (prompt )
379
- l , l2 = disp_str (line )
380
- wrapcount = (wlen (l ) + lp ) // self .console .width
381
- if wrapcount == 0 :
382
- offset += ll + 1 # Takes all of the line plus the newline
344
+ pos -= line_len + 1
345
+ prompt , prompt_len = self .process_prompt (prompt )
346
+ chars , char_widths = disp_str (line )
347
+ wrapcount = (sum (char_widths ) + prompt_len ) // self .console .width
348
+ trace ("wrapcount = {wrapcount}" , wrapcount = wrapcount )
349
+ if wrapcount == 0 or not char_widths :
350
+ offset += line_len + 1 # Takes all of the line plus the newline
383
351
last_refresh_line_end_offsets .append (offset )
384
- screen .append (prompt + l )
385
- screeninfo .append ((lp , l2 ))
352
+ screen .append (prompt + "" . join ( chars ) )
353
+ screeninfo .append ((prompt_len , char_widths ))
386
354
else :
387
- i = 0
388
- while l :
389
- prelen = lp if i == 0 else 0
355
+ pre = prompt
356
+ prelen = prompt_len
357
+ for wrap in range ( wrapcount + 1 ):
390
358
index_to_wrap_before = 0
391
359
column = 0
392
- for character_width in l2 :
393
- if column + character_width >= self .console .width - prelen :
360
+ for char_width in char_widths :
361
+ if column + char_width + prelen >= self .console .width :
394
362
break
395
363
index_to_wrap_before += 1
396
- column += character_width
397
- pre = prompt if i == 0 else ""
398
- if len (l ) > index_to_wrap_before :
364
+ column += char_width
365
+ if len (chars ) > index_to_wrap_before :
399
366
offset += index_to_wrap_before
400
367
post = "\\ "
401
368
after = [1 ]
@@ -404,11 +371,14 @@ def calc_screen(self) -> list[str]:
404
371
post = ""
405
372
after = []
406
373
last_refresh_line_end_offsets .append (offset )
407
- screen .append (pre + l [:index_to_wrap_before ] + post )
408
- screeninfo .append ((prelen , l2 [:index_to_wrap_before ] + after ))
409
- l = l [index_to_wrap_before :]
410
- l2 = l2 [index_to_wrap_before :]
411
- i += 1
374
+ render = pre + "" .join (chars [:index_to_wrap_before ]) + post
375
+ render_widths = char_widths [:index_to_wrap_before ] + after
376
+ screen .append (render )
377
+ screeninfo .append ((prelen , render_widths ))
378
+ chars = chars [index_to_wrap_before :]
379
+ char_widths = char_widths [index_to_wrap_before :]
380
+ pre = ""
381
+ prelen = 0
412
382
self .screeninfo = screeninfo
413
383
self .cxy = self .pos2xy ()
414
384
if self .msg :
@@ -537,9 +507,9 @@ def setpos_from_xy(self, x: int, y: int) -> None:
537
507
pos = 0
538
508
i = 0
539
509
while i < y :
540
- prompt_len , character_widths = self .screeninfo [i ]
541
- offset = len (character_widths ) - character_widths . count ( 0 )
542
- in_wrapped_line = prompt_len + sum (character_widths ) >= self .console .width
510
+ prompt_len , char_widths = self .screeninfo [i ]
511
+ offset = len (char_widths )
512
+ in_wrapped_line = prompt_len + sum (char_widths ) >= self .console .width
543
513
if in_wrapped_line :
544
514
pos += offset - 1 # -1 cause backslash is not in buffer
545
515
else :
@@ -560,29 +530,33 @@ def setpos_from_xy(self, x: int, y: int) -> None:
560
530
561
531
def pos2xy (self ) -> tuple [int , int ]:
562
532
"""Return the x, y coordinates of position 'pos'."""
563
- # this *is* incomprehensible, yes.
564
- p , y = 0 , 0
565
- l2 : list [int ] = []
533
+
534
+ prompt_len , y = 0 , 0
535
+ char_widths : list [int ] = []
566
536
pos = self .pos
567
537
assert 0 <= pos <= len (self .buffer )
538
+
539
+ # optimize for the common case: typing at the end of the buffer
568
540
if pos == len (self .buffer ) and len (self .screeninfo ) > 0 :
569
541
y = len (self .screeninfo ) - 1
570
- p , l2 = self .screeninfo [y ]
571
- return p + sum (l2 ) + l2 .count (0 ), y
542
+ prompt_len , char_widths = self .screeninfo [y ]
543
+ return prompt_len + sum (char_widths ), y
544
+
545
+ for prompt_len , char_widths in self .screeninfo :
546
+ offset = len (char_widths )
547
+ in_wrapped_line = prompt_len + sum (char_widths ) >= self .console .width
548
+ if in_wrapped_line :
549
+ offset -= 1 # need to remove line-wrapping backslash
572
550
573
- for p , l2 in self .screeninfo :
574
- l = len (l2 ) - l2 .count (0 )
575
- in_wrapped_line = p + sum (l2 ) >= self .console .width
576
- offset = l - 1 if in_wrapped_line else l # need to remove backslash
577
551
if offset >= pos :
578
552
break
579
553
580
- if p + sum ( l2 ) >= self . console . width :
581
- pos -= l - 1 # -1 cause backslash is not in buffer
582
- else :
583
- pos -= l + 1 # +1 cause newline is in buffer
554
+ if not in_wrapped_line :
555
+ offset += 1 # there's a newline in buffer
556
+
557
+ pos -= offset
584
558
y += 1
585
- return p + sum (l2 [:pos ]), y
559
+ return prompt_len + sum (char_widths [:pos ]), y
586
560
587
561
def insert (self , text : str | list [str ]) -> None :
588
562
"""Insert 'text' at the insertion point."""
0 commit comments