174 lines
5.9 KiB
Python
174 lines
5.9 KiB
Python
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)
|