You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5实现类代码风格嵌套字段自动补全(支持QTextEdit)

PyQt5 QTextEdit实现嵌套字段自动补全方案

核心问题分析

QTextEdit默认补全逻辑仅提取光标下的单个单词,无法识别.连接的嵌套路径;同时默认QCompleter的splitPath方法需要配合正确的路径提取逻辑才能生效。要解决问题,需从路径提取和补全模型动态更新两个核心方向入手。

实现步骤

1. 自定义PathCompleter:处理嵌套路径拆分与模型更新

重写splitPath方法确保按.拆分路径,并实现根据路径片段动态生成对应层级补全项的逻辑:

from PyQt5.QtWidgets import QCompleter, QStandardItemModel, QStandardItem, QTextEdit, QApplication
from PyQt5.QtCore import Qt, QModelIndex

class PathCompleter(QCompleter):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setModel(QStandardItemModel(self))
        # 模拟业务字段数据(可替换为实际对象结构)
        self._field_data = {
            "obj": {"field1": {"subfield1": "", "subfield2": ""}, "field2": ""},
            "obj1": {"field1": {"subfieldA": "", "subfieldB": ""}, "field3": ""}
        }

    def splitPath(self, path):
        # 强制按`.`拆分路径,为嵌套补全提供层级依据
        return path.split('.')

    def update_completion_model(self, path_segments):
        """根据路径片段更新补全候选列表"""
        model = self.model()
        model.clear()
        current_level_data = self._field_data

        # 遍历路径片段,定位到当前层级的数据
        for seg in path_segments[:-1]:
            if seg in current_level_data:
                current_level_data = current_level_data[seg]
            else:
                current_level_data = {}
                break

        # 匹配当前层级中以最后一个片段为前缀的字段
        prefix = path_segments[-1] if path_segments else ""
        for field in current_level_data.keys():
            if field.startswith(prefix):
                model.appendRow(QStandardItem(field))

2. 自定义CodeTextEdit:提取光标处完整路径与补全触发

实现光标处嵌套路径的提取逻辑,替换默认按键处理逻辑来触发补全:

class CodeTextEdit(QTextEdit):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.completer = PathCompleter(self)
        self.completer.setWidget(self)
        self.completer.activated.connect(self._insert_completion)

    def _insert_completion(self, completion):
        """将选中的补全项替换到光标位置"""
        cursor = self.textCursor()
        path_segments = self._get_current_path_segments()
        if not path_segments:
            return

        # 计算需要替换的字符长度(当前路径最后一段的长度)
        current_seg_len = len(path_segments[-1])
        cursor.movePosition(cursor.Left, cursor.KeepAnchor, current_seg_len)
        cursor.removeSelectedText()
        cursor.insertText(completion)
        self.setTextCursor(cursor)

    def _get_current_path_segments(self):
        """提取光标所在位置的完整嵌套路径(按`.`分割)"""
        cursor = self.textCursor()
        cursor.select(cursor.WordUnderCursor)

        # 向前扩展选中范围,把`.`连接的完整路径都选中
        while True:
            start_pos = cursor.selectionStart()
            if start_pos > 0 and self.toPlainText()[start_pos - 1] == '.':
                cursor.setPosition(start_pos - 1, cursor.KeepAnchor)
            else:
                break

        current_path = cursor.selectedText().strip()
        return current_path.split('.') if current_path else []

    def keyPressEvent(self, event):
        """重写按键事件,触发补全逻辑"""
        # 补全菜单显示时,优先处理导航按键
        if self.completer.popup().isVisible():
            if event.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab):
                event.ignore()
                return

        super().keyPressEvent(event)

        # 触发补全的按键:字母、`.`、退格
        trigger_keys = (Qt.Key_Period, Qt.Key_Backspace)
        if event.key() in trigger_keys or event.text().isalnum():
            path_segments = self._get_current_path_segments()
            self.completer.update_completion_model(path_segments)
            # 在光标位置显示补全菜单
            cursor_rect = self.cursorRect()
            popup_width = self.completer.popup().sizeHintForColumn(0) + self.completer.popup().verticalScrollBar().sizeHint().width()
            cursor_rect.setWidth(popup_width)
            self.completer.complete(cursor_rect)
        else:
            self.completer.popup().hide()

3. 测试代码

if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    editor = CodeTextEdit()
    editor.setPlaceholderText("输入 obj. / obj.field1. / obj1.field1. 测试嵌套补全")
    editor.resize(600, 400)
    editor.show()
    sys.exit(app.exec_())

关键说明

  • _get_current_path_segments解决了QTextEdit仅提取单个单词的问题,会将光标处.连接的完整路径全部提取
  • splitPath方法确保补全器能识别路径层级,配合update_completion_model实现逐层匹配嵌套字段
  • 补全替换逻辑精准替换当前路径片段,不会影响其他文本内容

内容的提问来源于stack exchange,提问作者Diogo Martins

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 00:06:21