You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于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()

这个示例实现了:

  1. 表格窗口动态增删变量,每0.5秒同步一次变量列表
  2. 编辑器在输入单引号后自动匹配带空格的变量名,补全时自动添加单引号
  3. 自定义补全逻辑适配包含空格的长变量名匹配

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

火山引擎 最新活动