import re from PyQt6.QtCore import Qt, QPoint, QStringListModel from PyQt6.QtWidgets import ( QDockWidget, QTextEdit, QPlainTextEdit, QSplitter, QMenu, QCompleter, ) from PyQt6.QtGui import ( QFont, QTextCursor, QKeySequence, QAction, QPalette, QColorConstants, ) from IPython.core.completer import ( provisionalcompleter, cursor_to_position, _deduplicate_completions ) from text_term import text_term class completer_py(QCompleter): def __init__(self, widget, *args): super().__init__(*args) model = QStringListModel([], self) self.setWidget(widget) self.setModel(model) self.setModelSorting(QCompleter.ModelSorting.CaseInsensitivelySortedModel) self.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) self.setWrapAround(False) self.setCompletionMode(QCompleter.CompletionMode.PopupCompletion) self.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) self.activated.connect(widget.insertCompletion) class text_input(QPlainTextEdit): def __init__(self, window, *arg): super().__init__(*arg) self.window = window self.setTabStopDistance(40) self.setPlaceholderText( "按 %s 发送命令\n" "按 %s 自动补全\n" "按 %s 或 %s 浏览命令历史\n" % ( QKeySequence("Ctrl+Enter").toString(QKeySequence.SequenceFormat.NativeText), QKeySequence("Ctrl+E").toString(QKeySequence.SequenceFormat.NativeText), QKeySequence("Ctrl+Up").toString(QKeySequence.SequenceFormat.NativeText), QKeySequence("Ctrl+Down").toString(QKeySequence.SequenceFormat.NativeText), ) ) self.completer_py = completer_py(self) self.history = [""] self.history_idx = 0 def insertCompletion(self, completion): tc = self.textCursor() extra = len(completion) - len(self.completer_py.completionPrefix()) tc.movePosition(QTextCursor.MoveOperation.Left) tc.movePosition(QTextCursor.MoveOperation.EndOfWord) tc.insertText(completion[-extra:]) self.setTextCursor(tc) def textUnderCursor(self): tc = self.textCursor() tc.select(QTextCursor.SelectionType.WordUnderCursor) return tc.selectedText() def focusInEvent(self, event): if self.completer_py is not None: self.completer_py.setWidget(self) super().focusInEvent(event) def keyPressEvent(self, event): if( event.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return) and bool(event.modifiers() & Qt.KeyboardModifier.ControlModifier) ): s = self.toPlainText() if not s: return self.window.process(s) self.history[-1] = s if len(self.history) >= 2 and self.history[-1] == self.history[-2]: self.history.pop() self.history_idx = len(self.history) self.history.append("") self.clear() return if( event.key() == Qt.Key.Key_Up and bool(event.modifiers() & Qt.KeyboardModifier.ControlModifier) and self.history_idx > 0 ): if self.history_idx == len(self.history) - 1: self.history[-1] = self.toPlainText() self.history_idx -= 1 self.setPlainText(self.history[self.history_idx]) return if( event.key() == Qt.Key.Key_Down and bool(event.modifiers() & Qt.KeyboardModifier.ControlModifier) and self.history_idx < len(self.history) - 1 ): self.history_idx += 1 self.setPlainText(self.history[self.history_idx]) return if self.completer_py is not None and self.completer_py.popup().isVisible(): # The following keys are forwarded by the completer to the widget. if event.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return, Qt.Key.Key_Escape, Qt.Key.Key_Tab, Qt.Key.Key_Backtab): event.ignore() # Let the completer do default behavior. return isShortcut = (bool(event.modifiers() & Qt.KeyboardModifier.ControlModifier) and event.key() == Qt.Key.Key_E) if self.completer_py is None or not isShortcut: # Do not process the shortcut when we have a completer. super().keyPressEvent(event) ctrlOrShift = event.modifiers() & (Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier) if self.completer_py is None or (ctrlOrShift and len(event.text()) == 0): return eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=" hasModifier = (event.modifiers() != Qt.KeyboardModifier.NoModifier) and not ctrlOrShift completionPrefix = self.textUnderCursor() if not isShortcut and (hasModifier or len(event.text()) == 0 or len(completionPrefix) < 3 or event.text()[-1] in eow): self.completer_py.popup().hide() return pos = self.textCursor().position() value = self.document().toRawText() hint = re.search(r"[_0-9a-zA-Z.%]*$", value[:pos]).group() pos_hint = pos - len(hint) hint += re.search(r"^[_0-9a-zA-Z.%]*", value[pos:]).group() with provisionalcompleter(): complete_list = _deduplicate_completions(hint, self.window.ipshell.Completer.completions(hint, pos - pos_hint)) complete_list = list(complete_list) complete_list = [i.text for i in complete_list] # complete_list = self.window.ipshell.complete(hint, cursor_pos = pos - pos_hint) # print(complete_list) self.completer_py.model().setStringList(complete_list) if completionPrefix != self.completer_py.completionPrefix(): self.completer_py.setCompletionPrefix(completionPrefix) self.completer_py.popup().setCurrentIndex( self.completer_py.completionModel().index(0, 0)) cr = self.cursorRect() cr.setWidth(self.completer_py.popup().sizeHintForColumn(0) + self.completer_py.popup().verticalScrollBar().sizeHint().width()) self.completer_py.complete(cr) class widget_term(QDockWidget): def __init__(self, window, *arg): super().__init__(*arg) self.window = window self.setAllowedAreas( Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea | Qt.DockWidgetArea.TopDockWidgetArea | Qt.DockWidgetArea.BottomDockWidgetArea ) font = QFont("consolas", 12) font.setFamilies(["consolas", "黑体"]) self.setFont(font) split = QSplitter(Qt.Orientation.Horizontal) self.setWidget(split) self.text_term = text_term(window, limit = 2 ** 16) split.addWidget(self.text_term) self.text_input = text_input(window) split.addWidget(self.text_input)