2525
2626from  contextlib  import  contextmanager 
2727from  dataclasses  import  dataclass , field , fields 
28- import  unicodedata 
2928from  _colorize  import  can_colorize , ANSIColors 
3029
3130
3231from  . import  commands , console , input 
33- from  .utils  import  wlen , unbracket , str_width 
32+ from  .utils  import  wlen , unbracket , disp_str 
3433from  .trace  import  trace 
3534
3635
3938from  .types  import  Callback , SimpleContextManager , KeySpec , CommandName 
4039
4140
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- 
7241# syntax classes: 
7342
7443SYNTAX_WHITESPACE , SYNTAX_WORD , SYNTAX_SYMBOL  =  range (3 )
@@ -347,14 +316,12 @@ def calc_screen(self) -> list[str]:
347316        pos  -=  offset 
348317
349318        prompt_from_cache  =  (offset  and  self .buffer [offset  -  1 ] !=  "\n " )
350- 
351319        lines  =  "" .join (self .buffer [offset :]).split ("\n " )
352- 
353320        cursor_found  =  False 
354321        lines_beyond_cursor  =  0 
355322        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 :
358325                self .lxy  =  pos , ln 
359326                cursor_found  =  True 
360327            elif  cursor_found :
@@ -368,34 +335,34 @@ def calc_screen(self) -> list[str]:
368335                prompt_from_cache  =  False 
369336                prompt  =  "" 
370337            else :
371-                 prompt  =  self .get_prompt (ln , ll  >=  pos  >=  0 )
338+                 prompt  =  self .get_prompt (ln , line_len  >=  pos  >=  0 )
372339            while  "\n "  in  prompt :
373340                pre_prompt , _ , prompt  =  prompt .partition ("\n " )
374341                last_refresh_line_end_offsets .append (offset )
375342                screen .append (pre_prompt )
376343                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 
383351                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 ))
386354            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 ): 
390358                    index_to_wrap_before  =  0 
391359                    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 :
394362                            break 
395363                        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 :
399366                        offset  +=  index_to_wrap_before 
400367                        post  =  "\\ " 
401368                        after  =  [1 ]
@@ -404,11 +371,14 @@ def calc_screen(self) -> list[str]:
404371                        post  =  "" 
405372                        after  =  []
406373                    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 
412382        self .screeninfo  =  screeninfo 
413383        self .cxy  =  self .pos2xy ()
414384        if  self .msg :
@@ -537,9 +507,9 @@ def setpos_from_xy(self, x: int, y: int) -> None:
537507        pos  =  0 
538508        i  =  0 
539509        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 
543513            if  in_wrapped_line :
544514                pos  +=  offset  -  1   # -1 cause backslash is not in buffer 
545515            else :
@@ -560,29 +530,33 @@ def setpos_from_xy(self, x: int, y: int) -> None:
560530
561531    def  pos2xy (self ) ->  tuple [int , int ]:
562532        """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 ] =  []
566536        pos  =  self .pos 
567537        assert  0  <=  pos  <=  len (self .buffer )
538+ 
539+         # optimize for the common case: typing at the end of the buffer 
568540        if  pos  ==  len (self .buffer ) and  len (self .screeninfo ) >  0 :
569541            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 
572550
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 
577551            if  offset  >=  pos :
578552                break 
579553
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 
584558            y  +=  1 
585-         return  p  +  sum (l2 [:pos ]), y 
559+         return  prompt_len  +  sum (char_widths [:pos ]), y 
586560
587561    def  insert (self , text : str  |  list [str ]) ->  None :
588562        """Insert 'text' at the insertion point.""" 
0 commit comments