@@ -516,7 +516,9 @@ def insert_completion(self, completion):
516516 cursor = self .textCursor ()
517517 cursor .select (QTextCursor .WordUnderCursor )
518518 cursor .removeSelectedText ()
519- cursor .insertText (completion )
519+ # Insert a space if the cursor is at the end of the text
520+ at_end = cursor .position () == len (self .toPlainText ())
521+ cursor .insertText (completion + (' ' if at_end else '' ))
520522 self .setTextCursor (cursor )
521523
522524 def keyPressEvent (self , event ):
@@ -535,44 +537,35 @@ def keyPressEvent(self, event):
535537 elif event .key () == Qt .Key .Key_Escape :
536538 completer_popup .hide ()
537539 return
538- if event .key () == Qt .Key .Key_Tab :
539- cursor = self .textCursor ()
540- cursor .select (QTextCursor .WordUnderCursor )
541- prefix = cursor .selectedText ()
542- self .completer .setCompletionPrefix (prefix .upper ())
543- if self .completer .completionCount () == 1 :
544- completion = self .completer .currentCompletion ()
545- self .insert_completion (completion )
546- return
547- else :
548- # create a new cursor and move it to the start of the word
549- word_start_cursor = self .textCursor ()
550- word_start_cursor .movePosition (QTextCursor .StartOfWord )
551- rect = self .cursorRect (word_start_cursor )
552- popup_scrollbar = completer_popup .verticalScrollBar ()
553- popup_scrollbar_width = popup_scrollbar .sizeHint ().width () + 10
554- rect .setWidth (completer_popup .sizeHintForColumn (0 )
555- + popup_scrollbar_width )
556- self .completer .complete (rect )
557- return
558- elif (event .key () in (Qt .Key .Key_Enter , Qt .Key .Key_Return ) and
540+
541+ if (event .key () in (Qt .Key .Key_Enter , Qt .Key .Key_Return ) and
559542 event .modifiers () & Qt .KeyboardModifier .ShiftModifier ):
560543 query_text = self .toPlainText ().strip ()
561544 if query_text :
562545 self .append_to_history (query_text )
563546 self .execute_sql (query_text )
564547 return
548+ elif event .key () == Qt .Key .Key_Tab :
549+ prefix = self .get_word_prefix ()
550+ self .completer .setCompletionPrefix (prefix )
551+ if self .completer .completionCount () == 1 :
552+ completion = self .completer .currentCompletion ()
553+ self .insert_completion (completion )
554+ return
555+ else :
556+ self .show_autocomplete_popup ()
557+ return
565558
566559 cursor = self .textCursor ()
567- block_number = cursor .blockNumber ()
568- total_blocks = self .document ().blockCount ()
569-
560+ # for plaintext QTextEdit, blockNumber gives the line number
561+ line_num = cursor .blockNumber ()
570562 if event .key () == Qt .Key .Key_Up :
571- if block_number == 0 : # Cursor is on the first line
563+ if line_num == 0 :
572564 if self .search_and_recall_history (direction = - 1 ):
573565 return
574566 elif event .key () == Qt .Key .Key_Down :
575- if block_number == total_blocks - 1 : # Cursor is on the last line
567+ total_lines = self .document ().blockCount ()
568+ if line_num == total_lines - 1 :
576569 if self .history_index < len (self .history ) - 1 :
577570 if self .search_and_recall_history (direction = 1 ):
578571 return
@@ -581,6 +574,44 @@ def keyPressEvent(self, event):
581574 self .clear ()
582575 return
583576 super ().keyPressEvent (event )
577+ # we need to compute the prefix *after* the keypress event has been
578+ # handled so that the prefix contains the last key stroke
579+ prefix = self .get_word_prefix ()
580+ if prefix :
581+ self .completer .setCompletionPrefix (prefix )
582+ num_completion = self .completer .completionCount ()
583+ if (0 < num_completion <= self .completer .maxVisibleItems () and
584+ not completer_popup .isVisible ()):
585+ self .show_autocomplete_popup ()
586+ elif num_completion == 0 and completer_popup .isVisible ():
587+ completer_popup .hide ()
588+
589+ def show_autocomplete_popup (self ):
590+ # create a new cursor and move it to the start of the word, so that
591+ # we can position the popup correctly
592+ word_start_cursor = self .textCursor ()
593+ word_start_cursor .movePosition (QTextCursor .StartOfWord )
594+ rect = self .cursorRect (word_start_cursor )
595+ completer_popup = self .completer .popup ()
596+ popup_scrollbar = completer_popup .verticalScrollBar ()
597+ popup_scrollbar_width = popup_scrollbar .sizeHint ().width () + 10
598+ rect .setWidth (completer_popup .sizeHintForColumn (0 )
599+ + popup_scrollbar_width )
600+ self .completer .complete (rect )
601+
602+ def get_word_prefix (self ):
603+ text = self .toPlainText ()
604+ if not text :
605+ return ''
606+ cursor = self .textCursor ()
607+ cursor_pos = cursor .position ()
608+ # <= len(text) (instead of <) because cursor can be at the end
609+ assert 0 <= cursor_pos <= len (text ), f"{ cursor_pos = } { len (text )= } "
610+ word_start = cursor_pos
611+ while (word_start > 0 and
612+ text [word_start - 1 ].isalnum () or text [word_start - 1 ] == '_' ):
613+ word_start -= 1
614+ return text [word_start :cursor_pos ]
584615
585616 def search_and_recall_history (self , direction : int ):
586617 if not self .history :
0 commit comments