class CompleterLineEdit(QLineEdit): '''# does new completion after COMMA ,''' def __init__(self, wordlist, *args): QLineEdit.__init__(self, *args) self.mycompleter = QCompleter(wordlist) self.mycompleter.setCaseSensitivity(Qt.CaseInsensitive) self.mycompleter.setWidget(self) self.textChanged.connect(self.text_changed) self.mycompleter.activated.connect(self.complete_text) def text_changed(self, text): all_text = text text = all_text[:self.cursorPosition()] prefix = text.split(',')[-1].strip() self.mycompleter.setCompletionPrefix(prefix) if prefix != '': self.mycompleter.complete() def complete_text(self, text): cursor_pos = self.cursorPosition() before_text = self.text()[:cursor_pos] after_text = self.text()[cursor_pos:] prefix_len = len(before_text.split(',')[-1].strip()) self.setText(before_text[:cursor_pos - prefix_len] + text + after_text) self.setCursorPosition(cursor_pos - prefix_len + len(text)) textChangedX = Signal(str)
class AutoCompleteLineEdit(QLineEdit): # http://blog.elentok.com/2011/08/autocomplete-textbox-for-multiple.html def __init__(self, items, parent=None): super().__init__(parent) self._separators = [",", " "] self._completer = QCompleter(items, self) self._completer.setWidget(self) self._completer.activated[str].connect(self.__insertCompletion) self._completer.setCaseSensitivity(Qt.CaseInsensitive) self.__keysToIgnore = [ Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab ] def __insertCompletion(self, completion): extra = len(completion) - len(self._completer.completionPrefix()) extra_text = completion[-extra:] extra_text += ", " self.setText(self.text() + extra_text) def textUnderCursor(self): text = self.text() text_under_cursor = "" i = self.cursorPosition() - 1 while i >= 0 and text[i] not in self._separators: text_under_cursor = text[i] + text_under_cursor i -= 1 return text_under_cursor def keyPressEvent(self, event): if self._completer.popup().isVisible(): if event.key() in self.__keysToIgnore: event.ignore() return super().keyPressEvent(event) completion_prefix = self.textUnderCursor() if completion_prefix != self._completer.completionPrefix(): self.__updateCompleterPopupItems(completion_prefix) if len(event.text()) > 0 and len(completion_prefix) > 0: self._completer.complete() if len(completion_prefix) == 0: self._completer.popup().hide() def __updateCompleterPopupItems(self, completionPrefix): self._completer.setCompletionPrefix(completionPrefix) self._completer.popup().setCurrentIndex( self._completer.completionModel().index(0, 0))
class IdePanel(QPlainTextEdit): def __init__(self): QPlainTextEdit.__init__(self) self.setWordWrapMode(QTextOption.NoWrap) self.setFont(QFont("monospace", 10)) self.setCursorWidth(2) self.installEventFilter(self) self.completer = QCompleter(self) self.completer.setWidget(self) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.activated.connect(self.insertCompletion) auto_complete = QShortcut(QKeySequence("Ctrl+Space"), self) auto_complete.activated.connect(self.activateCompleter) copy_line = QShortcut(QKeySequence("Ctrl+D"), self) copy_line.activated.connect(self.duplicateLine) select_fragment = QShortcut(QKeySequence("Ctrl+J"), self) select_fragment.activated.connect(self.selectFragment) def getText(self): return self.document().toPlainText() def eventFilter(self, qobject, qevent): if qobject == self and qevent.type() == QEvent.ToolTip: text_cursor = self.cursorForPosition(qevent.pos()) pos = text_cursor.positionInBlock() user_data = text_cursor.block().userData() if user_data is not None: #: :type: ConfigurationLine configuration_line = user_data.configuration_line # if configuration_line.keyword().hasKeywordDefinition(): # print(configuration_line.keyword().keywordDefinition().documentation) if pos in configuration_line.keyword(): self.setToolTip( configuration_line.validationStatusForToken( configuration_line.keyword()).message()) else: for argument in configuration_line.arguments(): if pos in argument: self.setToolTip( configuration_line.validationStatusForToken( argument).message()) else: self.setToolTip("") return QPlainTextEdit.eventFilter(self, qobject, qevent) def activateCompleter(self): text_cursor = self.textCursor() block = self.document().findBlock(text_cursor.position()) position_in_block = text_cursor.positionInBlock() self.selectWordUnderCursor(text_cursor) word = unicode(text_cursor.selectedText()) user_data = block.userData() self.completer.setCompletionPrefix(word) show_completer = False if user_data is None: self.completer.setModel(QStringListModel(self.handler_names)) show_completer = True else: keyword = user_data.keyword options = keyword.handler.parameterOptions(keyword, word, position_in_block) if len(options) == 1: self.insertCompletion(options[0]) elif len(options) > 1: self.completer.setModel(QStringListModel(options)) if self.completer.completionCount() == 1: self.insertCompletion(self.completer.currentCompletion()) else: show_completer = True if show_completer: rect = self.cursorRect(text_cursor) rect.setWidth( self.completer.popup().sizeHintForColumn(0) + self.completer.popup().verticalScrollBar().sizeHint().width()) self.completer.complete(rect) def keyPressEvent(self, qkeyevent): if self.completer.popup().isVisible(): dead_keys = [ Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab, ] if qkeyevent.key() in dead_keys: qkeyevent.ignore() return if qkeyevent.modifiers() == Qt.ShiftModifier: if qkeyevent.key() & Qt.Key_Delete == Qt.Key_Delete: self.deleteLine() QPlainTextEdit.keyPressEvent(self, qkeyevent) def insertCompletion(self, string): text_cursor = self.textCursor() self.selectWordUnderCursor(text_cursor) text_cursor.insertText(string) def isCursorInSpace(self): text_cursor = self.textCursor() if text_cursor.positionInBlock() > 0: text_cursor.movePosition(QTextCursor.Left, QTextCursor.MoveAnchor) text_cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor) if text_cursor.positionInBlock() < text_cursor.block().length() - 1: text_cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor) if unicode(text_cursor.selectedText()).strip() == "": return True return False def selectWordUnderCursor(self, text_cursor): if not self.isCursorInSpace(): # text_cursor.select(QTextCursor.WordUnderCursor) # pattern = "[\s|\v|\f|\n|\r|\t|\xe2\x80\xa8|\xe2\x80\xa9]" # pattern = "[\\s|\\xe2\\x80\\xa9|\\xe2\\x80\\xa8]" block_start = 0 block_end = text_cursor.block().length() cursor_pos = text_cursor.positionInBlock() pos = cursor_pos pattern = u"[\\s\u2029\u2028]" while pos >= block_start: text_cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor) text = text_cursor.selectedText() if re.search(pattern, text): break pos -= 1 text_cursor.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor) while pos < block_end: text_cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor) text = text_cursor.selectedText() if re.search(pattern, text): break pos += 1 text_cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor) # pattern = "[\\s]" # start = self.document().find(QRegExp(pattern), text_cursor, QTextDocument.FindBackward | QTextDocument.FindCaseSensitively) # end = self.document().find(QRegExp(pattern), text_cursor, QTextDocument.FindCaseSensitively) # block_end_pos = text_cursor.block().position() + text_cursor.block().length() # # text_cursor.setPosition(start.position(), QTextCursor.MoveAnchor) # # text_cursor.setPosition(min(block_end_pos, end.position() - 1), QTextCursor.KeepAnchor) # text_cursor.setPosition(end.position() - 1, QTextCursor.KeepAnchor) def deleteLine(self): text_cursor = self.textCursor() text_cursor.beginEditBlock() text_cursor.select(QTextCursor.LineUnderCursor) text_cursor.removeSelectedText() text_cursor.deletePreviousChar() text_cursor.movePosition(QTextCursor.NextBlock) text_cursor.movePosition(QTextCursor.StartOfLine) self.setTextCursor(text_cursor) text_cursor.endEditBlock() def duplicateLine(self): text_cursor = self.textCursor() text_cursor.beginEditBlock() text_cursor.select(QTextCursor.LineUnderCursor) text = text_cursor.selectedText() text_cursor.movePosition(QTextCursor.EndOfLine) text_cursor.insertBlock() text_cursor.insertText(text) text_cursor.endEditBlock() def selectFragment(self): text_cursor = self.textCursor() self.selectWordUnderCursor(text_cursor) self.setTextCursor(text_cursor)
class AutoComplete(QObject): def __init__(self, parent): super(AutoComplete, self).__init__(parent) self.mode = COMPLETE_MODE.INLINE self.completer = None self._last_key = None parent.edit.installEventFilter(self) self.init_completion_list([]) def eventFilter(self, widget, event): if event.type() == QEvent.KeyPress: return bool(self.key_pressed_handler(event)) return False def key_pressed_handler(self, event): intercepted = False key = event.key() if key == Qt.Key_Tab: intercepted = self.handle_tab_key(event) elif key in (Qt.Key_Return, Qt.Key_Enter, Qt.Key_Space): intercepted = self.handle_complete_key(event) elif key == Qt.Key_Escape: intercepted = self.hide_completion_suggestions() self._last_key = key self.update_completion(key) return intercepted def handle_tab_key(self, event): if self.parent()._textCursor().hasSelection(): return False if self.mode == COMPLETE_MODE.DROPDOWN: if self.parent().input_buffer().strip(): if self.completing(): self.complete() else: self.trigger_complete() event.accept() return True elif self.mode == COMPLETE_MODE.INLINE: if self._last_key == Qt.Key_Tab: self.trigger_complete() event.accept() return True def handle_complete_key(self, event): if self.completing(): self.complete() event.accept() return True def init_completion_list(self, words): self.completer = QCompleter(words, self) self.completer.setCompletionPrefix(self.parent().input_buffer()) self.completer.setWidget(self.parent().edit) self.completer.setCaseSensitivity(Qt.CaseSensitive) self.completer.setModelSorting(QCompleter.CaseSensitivelySortedModel) if self.mode == COMPLETE_MODE.DROPDOWN: self.completer.setCompletionMode(QCompleter.PopupCompletion) self.completer.activated.connect(self.insert_completion) else: self.completer.setCompletionMode(QCompleter.InlineCompletion) def trigger_complete(self): _buffer = self.parent().input_buffer().strip() self.show_completion_suggestions(_buffer) def show_completion_suggestions(self, _buffer): words = self.parent().get_completions(_buffer) # No words to show, just return if len(words) == 0: return # Close any popups before creating a new one if self.completer.popup(): self.completer.popup().close() self.init_completion_list(words) leastcmn = long_substr(words) self.insert_completion(leastcmn) # If only one word to complete, just return and don't display options if len(words) == 1: return if self.mode == COMPLETE_MODE.DROPDOWN: cr = self.parent().edit.cursorRect() sbar_w = self.completer.popup().verticalScrollBar() popup_width = self.completer.popup().sizeHintForColumn(0) popup_width += sbar_w.sizeHint().width() cr.setWidth(popup_width) self.completer.complete(cr) elif self.mode == COMPLETE_MODE.INLINE: cl = columnize(words, colsep=' | ') self.parent()._insert_output_text('\n\n' + cl + '\n', lf=True, keep_buffer=True) def hide_completion_suggestions(self): if self.completing(): self.completer.popup().close() return True def completing(self): if self.mode == COMPLETE_MODE.DROPDOWN: return (self.completer.popup() and self.completer.popup().isVisible()) else: return False def insert_completion(self, completion): _buffer = self.parent().input_buffer().strip() # Handling the . operator in object oriented languages so we don't # overwrite the . when we are inserting the completion. Its not the . # operator If the buffer starts with a . (dot), but something else # perhaps terminal specific so do nothing. if '.' in _buffer and _buffer[0] != '.': idx = _buffer.rfind('.') + 1 _buffer = _buffer[idx:] if self.mode == COMPLETE_MODE.DROPDOWN: self.parent().insert_input_text(completion[len(_buffer):]) elif self.mode == COMPLETE_MODE.INLINE: self.parent().clear_input_buffer() self.parent().insert_input_text(completion) words = self.parent().get_completions(completion) if len(words) == 1: self.parent().insert_input_text(' ') def update_completion(self, key): if self.completing(): _buffer = self.parent().input_buffer() if len(_buffer) > 1: self.show_completion_suggestions(_buffer) self.completer.setCurrentRow(0) model = self.completer.completionModel() self.completer.popup().setCurrentIndex(model.index(0, 0)) else: self.completer.popup().hide() def complete(self): if self.completing() and self.mode == COMPLETE_MODE.DROPDOWN: index = self.completer.popup().currentIndex() model = self.completer.completionModel() word = model.itemData(index)[0] self.insert_completion(word) self.completer.popup().hide()