46
46
Interp ,
47
47
code_finished_will_parse ,
48
48
)
49
- from .manual_readline import edit_keys
49
+ from .manual_readline import edit_keys , cursor_on_closing_char_pair
50
50
from .parse import parse as bpythonparse , func_for_letter , color_for_letter
51
51
from .preprocess import preprocess
52
52
from .. import __version__
58
58
SourceNotFound ,
59
59
)
60
60
from ..translations import _
61
+ from ..line import CHARACTER_PAIR_MAP
61
62
62
63
logger = logging .getLogger (__name__ )
63
64
@@ -800,9 +801,74 @@ def process_key_event(self, e: str) -> None:
800
801
self .incr_search_mode = None
801
802
elif e in ("<SPACE>" ,):
802
803
self .add_normal_character (" " )
804
+ elif e in CHARACTER_PAIR_MAP .keys ():
805
+ if e in ["'" , '"' ]:
806
+ if self .is_closing_quote (e ):
807
+ self .insert_char_pair_end (e )
808
+ else :
809
+ self .insert_char_pair_start (e )
810
+ else :
811
+ self .insert_char_pair_start (e )
812
+ elif e in CHARACTER_PAIR_MAP .values ():
813
+ self .insert_char_pair_end (e )
803
814
else :
804
815
self .add_normal_character (e )
805
816
817
+ def is_closing_quote (self , e ):
818
+ char_count = self ._current_line .count (e )
819
+ if (
820
+ char_count % 2 == 0
821
+ and cursor_on_closing_char_pair (
822
+ self ._cursor_offset , self ._current_line , e
823
+ )[0 ]
824
+ ):
825
+ return True
826
+ return False
827
+
828
+ def insert_char_pair_start (self , e ):
829
+ """Accepts character which is a part of CHARACTER_PAIR_MAP
830
+ like brackets and quotes, and appends it to the line with
831
+ an appropriate character pair ending. Closing character can only be inserted
832
+ when the next character is either a closing character or a space
833
+
834
+ e.x. if you type "(" (lparen) , this will insert "()"
835
+ into the line
836
+ """
837
+ self .add_normal_character (e )
838
+ if self .config .brackets_completion :
839
+ allowed_chars = ["}" , ")" , "]" , " " ]
840
+ start_of_line = len (self ._current_line ) == 1
841
+ end_of_line = len (self ._current_line ) == self ._cursor_offset
842
+ can_lookup_next = len (self ._current_line ) > self ._cursor_offset
843
+ next_char = (
844
+ None
845
+ if not can_lookup_next
846
+ else self ._current_line [self ._cursor_offset ]
847
+ )
848
+ next_char_allowed = next_char in allowed_chars
849
+ if start_of_line or end_of_line or next_char_allowed :
850
+ closing_char = CHARACTER_PAIR_MAP [e ]
851
+ self .add_normal_character (closing_char , narrow_search = False )
852
+ self ._cursor_offset -= 1
853
+
854
+ def insert_char_pair_end (self , e ):
855
+ """Accepts character which is a part of CHARACTER_PAIR_MAP
856
+ like brackets and quotes, and checks whether it should be
857
+ inserted to the line or overwritten
858
+
859
+ e.x. if you type ")" (rparen) , and your cursor is directly
860
+ above another ")" (rparen) in the cmd, this will just skip
861
+ it and move the cursor.
862
+ If there is no same character underneath the cursor, the
863
+ character will be printed/appended to the line
864
+ """
865
+ if self .config .brackets_completion :
866
+ if self .cursor_offset < len (self ._current_line ):
867
+ if self ._current_line [self .cursor_offset ] == e :
868
+ self .cursor_offset += 1
869
+ return
870
+ self .add_normal_character (e )
871
+
806
872
def get_last_word (self ):
807
873
808
874
previous_word = _last_word (self .rl_history .entry )
@@ -903,7 +969,15 @@ def only_whitespace_left_of_cursor():
903
969
for unused in range (to_add ):
904
970
self .add_normal_character (" " )
905
971
return
906
-
972
+ # if cursor on closing character from pair,
973
+ # moves cursor behind it on tab
974
+ # ? should we leave it here as default?
975
+ if self .config .brackets_completion :
976
+ on_closing_char , _ = cursor_on_closing_char_pair (
977
+ self ._cursor_offset , self ._current_line
978
+ )
979
+ if on_closing_char :
980
+ self ._cursor_offset += 1
907
981
# run complete() if we don't already have matches
908
982
if len (self .matches_iter .matches ) == 0 :
909
983
self .list_win_visible = self .complete (tab = True )
@@ -915,7 +989,6 @@ def only_whitespace_left_of_cursor():
915
989
# using _current_line so we don't trigger a completion reset
916
990
if not self .matches_iter .matches :
917
991
self .list_win_visible = self .complete ()
918
-
919
992
elif self .matches_iter .matches :
920
993
self .current_match = (
921
994
back and self .matches_iter .previous () or next (self .matches_iter )
@@ -924,6 +997,24 @@ def only_whitespace_left_of_cursor():
924
997
self ._cursor_offset , self ._current_line = cursor_and_line
925
998
# using _current_line so we don't trigger a completion reset
926
999
self .list_win_visible = True
1000
+ if self .config .brackets_completion :
1001
+ # appends closing char pair if completion is a callable
1002
+ if self .is_completion_callable (self ._current_line ):
1003
+ self ._current_line = self .append_closing_character (
1004
+ self ._current_line
1005
+ )
1006
+
1007
+ def is_completion_callable (self , completion ):
1008
+ """Checks whether given completion is callable (e.x. function)"""
1009
+ completion_end = completion [- 1 ]
1010
+ return completion_end in CHARACTER_PAIR_MAP
1011
+
1012
+ def append_closing_character (self , completion ):
1013
+ """Appends closing character/bracket to the completion"""
1014
+ completion_end = completion [- 1 ]
1015
+ if completion_end in CHARACTER_PAIR_MAP :
1016
+ completion = f"{ completion } { CHARACTER_PAIR_MAP [completion_end ]} "
1017
+ return completion
927
1018
928
1019
def on_control_d (self ):
929
1020
if self .current_line == "" :
@@ -1071,7 +1162,7 @@ def toggle_file_watch(self):
1071
1162
)
1072
1163
1073
1164
# Handler Helpers
1074
- def add_normal_character (self , char ):
1165
+ def add_normal_character (self , char , narrow_search = True ):
1075
1166
if len (char ) > 1 or is_nop (char ):
1076
1167
return
1077
1168
if self .incr_search_mode :
@@ -1087,12 +1178,18 @@ def add_normal_character(self, char):
1087
1178
reset_rl_history = False ,
1088
1179
clear_special_mode = False ,
1089
1180
)
1090
- self .cursor_offset += 1
1181
+ if narrow_search :
1182
+ self .cursor_offset += 1
1183
+ else :
1184
+ self ._cursor_offset += 1
1091
1185
if self .config .cli_trim_prompts and self .current_line .startswith (
1092
1186
self .ps1
1093
1187
):
1094
1188
self .current_line = self .current_line [4 :]
1095
- self .cursor_offset = max (0 , self .cursor_offset - 4 )
1189
+ if narrow_search :
1190
+ self .cursor_offset = max (0 , self .cursor_offset - 4 )
1191
+ else :
1192
+ self ._cursor_offset += max (0 , self .cursor_offset - 4 )
1096
1193
1097
1194
def add_to_incremental_search (self , char = None , backspace = False ):
1098
1195
"""Modify the current search term while in incremental search.
0 commit comments