基于Python开发自定义语言编辑器:带空格单引号变量补全求助
解决方案
一、实现带空格+单引号包裹变量的自动补全
PyQt/PySide的QCompleter并非不能处理这类场景,只是默认逻辑不匹配,需要自定义补全规则:
- 自定义补全触发逻辑:
用QPlainTextEdit作为编辑器(轻量且适配代码编辑场景),监听光标位置与输入内容。当检测到光标前是单引号开头的片段时,提取当前输入的前缀(比如用户输入'THIS IS A,就提取THIS IS A作为匹配依据),触发补全。 - 重写QCompleter匹配规则:
继承QCompleter并将过滤模式改为Qt.MatchContains(默认是前缀匹配,改为包含匹配更适配长变量名),同时重写pathFromIndex方法,确保插入到编辑器的内容自动带上单引号。 - 实时更新候选列表:
绑定变量列表到补全模型,当表格中的变量更新时,同步刷新补全候选池。
二、独立表格窗口管理变量
用QTableWidget实现变量的动态增删改,同步维护变量列表供编辑器调用:
- 表格结构设计:表格设一列用于输入变量名(无需带单引号,统一由编辑器处理),搭配“添加行”“删除选中行”按钮。
- 数据同步机制:维护全局
list存储变量名,表格的增删改操作直接同步更新该列表。比如新增行输入变量名后,自动将名称加入列表;删除行则从列表移除对应名称。 - 编辑器解析关联:编辑器解析代码时,直接读取该变量列表,判断单引号包裹的字符串是否属于已定义变量。
极简示例代码(PySide6)
from PySide6.QtWidgets import (QApplication, QMainWindow, QPlainTextEdit, QCompleter, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, QPushButton) from PySide6.QtCore import QStringListModel, Qt, QTimer class CustomCompleter(QCompleter): def __init__(self, parent=None): super().__init__(parent) self.setFilterMode(Qt.MatchContains) # 改为包含匹配,适配长变量名 def pathFromIndex(self, index): # 插入时自动为变量名添加单引号 return f"'{super().pathFromIndex(index)}'" class VariableTable(QWidget): def __init__(self, var_list, parent=None): super().__init__(parent) self.var_list = var_list layout = QVBoxLayout() self.table = QTableWidget() self.table.setColumnCount(1) self.table.setHorizontalHeaderLabels(["变量名"]) add_btn = QPushButton("添加变量") add_btn.clicked.connect(self.add_row) del_btn = QPushButton("删除选中变量") del_btn.clicked.connect(self.del_row) layout.addWidget(self.table) layout.addWidget(add_btn) layout.addWidget(del_btn) self.setLayout(layout) def add_row(self): row = self.table.rowCount() self.table.insertRow(row) self.table.setItem(row, 0, QTableWidgetItem("")) def del_row(self): current_row = self.table.currentRow() if current_row >= 0: item = self.table.item(current_row, 0) if item and item.text().strip() in self.var_list: self.var_list.remove(item.text().strip()) self.table.removeRow(current_row) def sync_vars(self): # 同步表格内容到变量列表 self.var_list.clear() for row in range(self.table.rowCount()): item = self.table.item(row, 0) if item and item.text().strip(): self.var_list.append(item.text().strip()) class CodeEditor(QPlainTextEdit): def __init__(self, var_list, parent=None): super().__init__(parent) self.var_list = var_list self.completer = CustomCompleter(self) self.completer.setWidget(self) self.completer.activated.connect(self.insert_completion) def keyPressEvent(self, event): # 处理特殊按键,关闭补全弹窗 if event.key() in (Qt.Key_Return, Qt.Key_Enter, Qt.Key_Escape): self.completer.popup().hide() super().keyPressEvent(event) return # 提取单引号内的输入前缀,触发补全 cursor = self.textCursor() block_text = self.document().findBlock(cursor.position()).text() before_cursor = block_text[:cursor.positionInBlock()] if "'" in before_cursor: last_quote_pos = before_cursor.rfind("'") prefix = before_cursor[last_quote_pos+1:] # 更新补全候选列表 self.completer.setModel(QStringListModel(self.var_list)) self.completer.setCompletionPrefix(prefix) # 显示补全弹窗 cr = self.cursorRect() cr.setWidth(self.completer.popup().sizeHintForColumn(0) + self.completer.popup().verticalScrollBar().sizeHint().width()) self.completer.complete(cr) super().keyPressEvent(event) def insert_completion(self, completion): # 替换当前输入的前缀,插入完整带引号的变量名 cursor = self.textCursor() block_text = self.document().findBlock(cursor.position()).text() before_cursor = block_text[:cursor.positionInBlock()] last_quote_pos = before_cursor.rfind("'") cursor.setPosition(cursor.block().position() + last_quote_pos + 1) cursor.movePosition(cursor.EndOfWord, cursor.KeepAnchor) cursor.insertText(completion.strip("'")) if __name__ == "__main__": app = QApplication([]) var_list = [] # 编辑器主窗口 main_win = QMainWindow() editor = CodeEditor(var_list) main_win.setCentralWidget(editor) main_win.show() # 变量管理表格窗口 table_win = VariableTable(var_list) table_win.show() # 定时同步表格与变量列表 sync_timer = QTimer() sync_timer.timeout.connect(table_win.sync_vars) sync_timer.start(500) app.exec()
这个示例实现了:
- 表格窗口动态增删变量,每0.5秒同步一次变量列表
- 编辑器在输入单引号后自动匹配带空格的变量名,补全时自动添加单引号
- 自定义补全逻辑适配包含空格的长变量名匹配
内容的提问来源于stack exchange,提问作者InfiniteNopes




