3030from bpython .curtsiesfrontend import sitefix ; sitefix .monkeypatch_quit ()
3131import bpython .curtsiesfrontend .replpainter as paint
3232import curtsies .events as events
33- from bpython .curtsiesfrontend .friendly import NotImplementedError
3433from bpython .curtsiesfrontend .coderunner import CodeRunner , FakeOutput
3534
3635#TODO figure out how config.list_win_visible behaves and implement it, or stop using it
@@ -263,7 +262,9 @@ def process_event(self, e):
263262 self .run_code_and_maybe_finish ()
264263 elif isinstance (e , events .WindowChangeEvent ):
265264 logging .debug ('window change to %d %d' , e .width , e .height )
265+ self .scroll_offset -= e .cursor_dy
266266 self .width , self .height = e .width , e .height
267+
267268 elif self .status_bar .has_focus :
268269 return self .status_bar .process_event (e )
269270 elif self .stdin .has_focus :
@@ -275,9 +276,15 @@ def process_event(self, e):
275276 self .update_completion ()
276277 return
277278 elif isinstance (e , events .PasteEvent ):
279+ ctrl_char = compress_paste_event (e )
280+ if ctrl_char is not None :
281+ return self .process_event (ctrl_char )
278282 with self .in_paste_mode ():
279283 for ee in e .events :
280- self .process_simple_event (ee )
284+ if self .stdin .has_focus :
285+ self .stdin .process_event (ee )
286+ else :
287+ self .process_simple_event (ee )
281288 self .update_completion ()
282289
283290 elif e in self .rl_char_sequences :
@@ -295,23 +302,23 @@ def process_event(self, e):
295302 self ._current_line = self .rl_history .forward (False )
296303 self .cursor_offset_in_line = len (self ._current_line )
297304 self .update_completion ()
298- elif e in key_dispatch [self .config .search_key ]: #TODO
299- raise NotImplementedError ()
305+ elif e in key_dispatch [self .config .search_key ]: #TODO Not Implemented
306+ pass
300307 #TODO add rest of history commands
301308
302309 # Need to figure out what these are, but I think they belong in manual_realine
303310 # under slightly different names
304- elif e in key_dispatch [self .config .cut_to_buffer_key ]: #TODO
305- raise NotImplementedError ()
306- elif e in key_dispatch [self .config .yank_from_buffer_key ]: #TODO
307- raise NotImplementedError ()
311+ elif e in key_dispatch [self .config .cut_to_buffer_key ]: #TODO Not Implemented
312+ pass
313+ elif e in key_dispatch [self .config .yank_from_buffer_key ]: #TODO Not Implemented
314+ pass
308315
309316 elif e in key_dispatch [self .config .clear_screen_key ]:
310317 self .request_paint_to_clear_screen = True
311- elif e in key_dispatch [self .config .last_output_key ]: #TODO
312- raise NotImplementedError ()
313- elif e in key_dispatch [self .config .show_source_key ]: #TODO
314- raise NotImplementedError ()
318+ elif e in key_dispatch [self .config .last_output_key ]: #TODO Not Implemented
319+ pass
320+ elif e in key_dispatch [self .config .show_source_key ]: #TODO Not Implemented
321+ pass
315322 elif e in key_dispatch [self .config .suspend_key ]:
316323 raise SystemExit ()
317324 elif e in ("" ,) + key_dispatch [self .config .exit_key ]:
@@ -414,7 +421,8 @@ def process_simple_event(self, e):
414421 elif isinstance (e , events .Event ):
415422 pass # ignore events
416423 else :
417- self .add_normal_character (e if len (e ) == 1 else e [- 1 ]) #strip control seq
424+ if len (e ) == 1 :
425+ self .add_normal_character (e if len (e ) == 1 else e [- 1 ]) #strip control seq
418426
419427 def send_current_block_to_external_editor (self , filename = None ):
420428 text = self .send_to_external_editor (self .get_current_block ())
@@ -551,7 +559,7 @@ def unhighlight_paren(self):
551559 logging .debug ('trying to unhighlight a paren on line %r' , lineno )
552560 logging .debug ('with these tokens: %r' , saved_tokens )
553561 new = bpythonparse (format (saved_tokens , self .formatter ))
554- self .display_buffer [lineno ] = self .display_buffer [lineno ].setslice (0 , len (new ), new )
562+ self .display_buffer [lineno ] = self .display_buffer [lineno ].setslice_with_length (0 , len (new ), new , len ( self . display_buffer [ lineno ]) )
555563
556564 def clear_current_block (self , remove_from_history = True ):
557565 self .display_buffer = []
@@ -626,26 +634,30 @@ def current_word(self):
626634 so must match its definition of current word - changing how it behaves
627635 has many repercussions.
628636 """
629- words = re .split (r'([\w_][\w0-9._]*[(]?)' , self ._current_line )
630- chars = 0
631- cw = None
632- for word in words :
633- chars += len (word )
634- if chars == self .cursor_offset_in_line and word and word .count (' ' ) == 0 :
635- cw = word
636- if cw and re .match (r'^[\w_][\w0-9._]*[(]?$' , cw ):
637- return cw
637+
638+ start , end , word = self ._get_current_word ()
639+ return word
640+
641+ def _get_current_word (self ):
642+ pos = self .cursor_offset_in_line
643+
644+ matches = list (re .finditer (r'[\w_][\w0-9._]*[(]?' , self ._current_line ))
645+ start = pos
646+ end = pos
647+ word = None
648+ for m in matches :
649+ if m .start () < pos and m .end () >= pos :
650+ start = m .start ()
651+ end = m .end ()
652+ word = m .group ()
653+ return (start , end , word )
638654
639655 @current_word .setter
640656 def current_word (self , value ):
641- # current word means word cursor is at the end of, so delete from cursor back to [ ."']
642- pos = self .cursor_offset_in_line - 1
643- if pos > - 1 and self ._current_line [pos ] not in tuple (' :)' ):
644- pos -= 1
645- while pos > - 1 and self ._current_line [pos ] not in tuple (' :()\' "' ):
646- pos -= 1
647- start = pos + 1 ; del pos
648- self ._current_line = self ._current_line [:start ] + value + self ._current_line [self .cursor_offset_in_line :]
657+ # current word means word cursor is at the end of
658+ start , end , word = self ._get_current_word ()
659+
660+ self ._current_line = self ._current_line [:start ] + value + self ._current_line [end :]
649661 self .cursor_offset_in_line = start + len (value )
650662
651663 @property
@@ -688,17 +700,6 @@ def current_output_line(self, value):
688700 def paint (self , about_to_exit = False , user_quit = False ):
689701 """Returns an array of min_height or more rows and width columns, plus cursor position
690702
691- Also increments self.scroll_offset by the amount the terminal must have scrolled
692- to display the entire array.
693- """
694- arr , (row , col ) = self ._paint (about_to_exit = about_to_exit , user_quit = user_quit )
695- if arr .height > self .height :
696- self .scroll_offset += arr .height - self .height
697- return arr , (row , col )
698-
699- def _paint (self , about_to_exit = False , user_quit = False ):
700- """Returns an array of min_height or more rows and width columns, plus cursor position
701-
702703 Paints the entire screen - ideally the terminal display layer will take a diff and only
703704 write to the screen in portions that have changed, but the idea is that we don't need
704705 to worry about that here, instead every frame is completely redrawn because
@@ -726,7 +727,6 @@ def _paint(self, about_to_exit=False, user_quit=False):
726727 #TODO test case of current line filling up the whole screen (there aren't enough rows to show it)
727728
728729 if current_line_start_row < 0 : #if current line trying to be drawn off the top of the screen
729- #assert True, 'no room for current line: contiguity of history broken!'
730730 logging .debug ('#<---History contiguity broken by rewind--->' )
731731 msg = "#<---History contiguity broken by rewind--->"
732732 arr [0 , 0 :min (len (msg ), width )] = [msg [:width ]]
@@ -758,7 +758,8 @@ def _paint(self, about_to_exit=False, user_quit=False):
758758
759759 lines = paint .display_linize (self .current_cursor_line + 'X' , width )
760760 # extra character for space for the cursor
761- cursor_row = current_line_start_row + len (lines ) - 1
761+ current_line_end_row = current_line_start_row + len (lines ) - 1
762+ cursor_row = current_line_start_row + (len (self .current_cursor_line ) - len (self ._current_line ) + self .cursor_offset_in_line ) / width
762763 if self .stdin .has_focus :
763764 cursor_column = (len (self .current_stdouterr_line ) + self .stdin .cursor_offset_in_line ) % width
764765 assert cursor_column >= 0 , cursor_column
@@ -772,15 +773,15 @@ def _paint(self, about_to_exit=False, user_quit=False):
772773 if self .list_win_visible :
773774 logging .debug ('infobox display code running' )
774775 visible_space_above = history .height
775- visible_space_below = min_height - cursor_row - 1
776+ visible_space_below = min_height - current_line_end_row - 1
776777
777778 info_max_rows = max (visible_space_above , visible_space_below )
778779 infobox = paint .paint_infobox (info_max_rows , int (width * self .config .cli_suggestion_width ), self .matches , self .argspec , self .current_word , self .docstring , self .config )
779780
780781 if visible_space_above >= infobox .height and self .config .curtsies_list_above :
781782 arr [current_line_start_row - infobox .height :current_line_start_row , 0 :infobox .width ] = infobox
782783 else :
783- arr [cursor_row + 1 :cursor_row + 1 + infobox .height , 0 :infobox .width ] = infobox
784+ arr [current_line_end_row + 1 :current_line_end_row + 1 + infobox .height , 0 :infobox .width ] = infobox
784785 logging .debug ('slamming infobox of shape %r into arr of shape %r' , infobox .shape , arr .shape )
785786
786787 logging .debug ('about to exit: %r' , about_to_exit )
@@ -929,6 +930,26 @@ def getstdout(self):
929930 s = '\n ' .join ([x .s if isinstance (x , FmtStr ) else x for x in lines ]
930931 ) if lines else ''
931932 return s
933+
934+ def compress_paste_event (paste_event ):
935+ """If all events in a paste event are identical and not simple characters, returns one of them
936+
937+ Useful for when the UI is running so slowly that repeated keypresses end up in a paste event.
938+ If we value not getting delayed and assume the user is holding down a key to produce such frequent
939+ key events, it makes sense to drop some of the events.
940+ """
941+ if not all (paste_event .events [0 ] == e for e in paste_event .events ):
942+ return None
943+ event = paste_event .events [0 ]
944+ if len (event ) > 1 :# basically "is there a special curses names for this key?"
945+ return event
946+ elif ord (event ) < 0x20 :
947+ return event
948+ elif event == '\x7f ' :
949+ return event
950+ else :
951+ return None
952+
932953def simple_repl ():
933954 refreshes = []
934955 def request_refresh ():
0 commit comments