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
相关产品推荐
相关产品推荐

