【问题标题】:QScintilla based text editor in PyQt5 with clickable functions and variablesPyQt5 中基于 QScintilla 的文本编辑器,带有可点击的函数和变量
【发布时间】:2017-02-21 11:28:30
【问题描述】:

我正在尝试在 PyQt5 中制作一个具有基本语法高亮、代码完成和可点击函数和变量的简单文本编辑器。我最大的希望是使用 QScintilla 端口
对于 PyQt5。
我在 Eli Bendersky 网站上找到了以下基于 QScintilla 的文本编辑器示例(http://eli.thegreenplace.net/2011/04/01/sample-using-qscintilla-with-pyqt,Victor S. 已将其改编为 PyQt5)。我认为这个例子是一个很好的起点:

#-------------------------------------------------------------------------
# qsci_simple_pythoneditor.pyw
#
# QScintilla sample with PyQt
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------
import sys

import sip
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.Qsci import QsciScintilla, QsciLexerPython


class SimplePythonEditor(QsciScintilla):
    ARROW_MARKER_NUM = 8

    def __init__(self, parent=None):
        super(SimplePythonEditor, self).__init__(parent)

        # Set the default font
        font = QFont()
        font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(10)
        self.setFont(font)
        self.setMarginsFont(font)

        # Margin 0 is used for line numbers
        fontmetrics = QFontMetrics(font)
        self.setMarginsFont(font)
        self.setMarginWidth(0, fontmetrics.width("00000") + 6)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QColor("#cccccc"))

        # Clickable margin 1 for showing markers
        self.setMarginSensitivity(1, True)
#        self.connect(self,
#            SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'),
#            self.on_margin_clicked)
        self.markerDefine(QsciScintilla.RightArrow,
            self.ARROW_MARKER_NUM)
        self.setMarkerBackgroundColor(QColor("#ee1111"),
            self.ARROW_MARKER_NUM)

        # Brace matching: enable for a brace immediately before or after
        # the current position
        #
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Current line visible with special background color
        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QColor("#ffe4e4"))

        # Set Python lexer
        # Set style for Python comments (style number 1) to a fixed-width
        # courier.
        #

        lexer = QsciLexerPython()
        lexer.setDefaultFont(font)
        self.setLexer(lexer)

        text = bytearray(str.encode("Arial"))
# 32, "Courier New"         
        self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, text)

        # Don't want to see the horizontal scrollbar at all
        # Use raw message to Scintilla here (all messages are documented
        # here: http://www.scintilla.org/ScintillaDoc.html)
        self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)

        # not too small
        self.setMinimumSize(600, 450)

    def on_margin_clicked(self, nmargin, nline, modifiers):
        # Toggle marker for the line the margin was clicked on
        if self.markersAtLine(nline) != 0:
            self.markerDelete(nline, self.ARROW_MARKER_NUM)
        else:
            self.markerAdd(nline, self.ARROW_MARKER_NUM)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    editor = SimplePythonEditor()
    editor.show()
    editor.setText(open(sys.argv[0]).read())
    app.exec_()

只需将此代码复制粘贴到一个空的.py 文件中,然后运行它。您应该会在显示屏上看到以下简单的文本编辑器:

注意语法高亮是多么完美! QScintilla 当然在后台做了一些解析来实现这一点。
是否可以为这个文本编辑器制作可点击的函数和变量?每个自尊的 IDE 都有它。您单击一个函数,IDE 会跳转到函数定义。变量也一样。我想知道:

  • QScintilla 是否支持可点击的函数和变量
  • 如果没有,是否可以在 QScintilla 文本编辑器中导入另一个实现此功能的 python 模块?

编辑:
λuser 注意到以下内容:

可点击的函数名称需要对编程语言有更深入的了解才能完全解析 [..]
这超出了 Scintilla/QScintilla 的范围。 Scintilla 提供了一种在鼠标单击文本某处时做出反应的方法,但是“函数的定义在哪里”的逻辑不在 Scintilla 中,而且可能永远不会出现。
但是,一些项目专门用于此任务,例如 ctags。您可以简单地围绕这种工具编写一个包装器。

我想为 ctags 编写这样的包装器现在在我的 TODO 列表中。第一步是在用户单击函数或变量时获得反应(Qt 信号)。当您将鼠标悬停在函数/变量上时,该函数/变量可能会变蓝,以通知用户它是可点击的。我已经尝试过实现这一目标,但由于缺少 QScintilla 文档而受阻。

所以让我们将问题精简为:如何使 QScintilla 文本编辑器中的函数或变量可点击(可点击定义为“发生某事”)


编辑:
我现在才回到这个问题——几个月后。我一直在与我的朋友 Matic Kukovec 合作设计一个关于 QScintilla 的网站。这是一个关于如何使用它的初学者友好教程:

https://qscintilla.com/

我希望这项举措能填补缺乏文档的空白。

【问题讨论】:

  • 这里有两个单独的问题。第一个问题是单击文本并在给定位置识别符号。这是支持的,因为所有低级功能都存在于 QScintilla/Scintilla 中,供您自己编写实现。第二个问题是将符号链接到定义,这是通常在 IDE 中找到的功能,而不是“简单的文本编辑器”。对此没有直接支持,即使您使用 ctags 之类的东西,将其整合到文本编辑器中仍然需要大量工作。
  • 嗨@ekhumoro,非常感谢您的帮助。我不怕很多工作,只要我能以某种方式开始。此刻让我退缩的是使函数和变量在“发生某些事情”的意义上“可点击”。一旦我明白了,我就可以继续为ctags 制作一个包装器。你知道如何完成这第一步吗? QScintilla 文档很差...
  • 我认为你应该从较低的目标开始。设置custom context menu handler,然后尝试使用wordAtPoint 之类的方法来获取给定QPoint 的符号。 (PS:深入了解文档的方法是先阅读 Scintilla 文档,然后再返回 QScintilla 文档查看提供了哪些高级 API。SciTE docs 有时也很有用)。

标签: python python-3.x pyqt pyqt5 qscintilla


【解决方案1】:

语法高亮只是在源文件上运行词法分析器以查找标记,然后为其添加样式。词法分析器对编程语言有非常基本的了解,它只了解数字文字、关键字、运算符、注释以及其他一些内容,仅此而已。这是一项简单的工作,只需使用正则表达式即可完成。

另一方面,可点击的函数名称需要对编程语言有更深入的了解才能进行完整解析,例如这是声明变量还是使用等。此外,这可能需要解析当前编辑器未打开的其他源文件。

这远远超出了 Scintilla/QScintilla 的范围。 Scintilla 提供了一种在鼠标单击文本某处时做出反应的方法,但是“函数的定义在哪里”的逻辑在 Scintilla 中不存在,而且可能永远不会存在。

但是,有些项目专门用于此任务,例如 ctags。您可以简单地围绕这种工具编写一个包装器。

【讨论】:

  • 非常感谢您的有趣评论。我想编写这样的包装器至少需要一个函数或变量在 QScintilla 文本编辑器中是“可点击的”。如何让单词可点击?
【解决方案2】:

一种使用带有可点击函数和变量选项的 Pyqt5 的方法。 引用了可点击部分的脚本在 PyQt5 中看起来像这样,带有自定义信号。

PyQt4 信号

self.connect(self,SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'), 
self.on_margin_clicked)

PyQt5 信号

self.marginClicked.connect(self.on_margin_clicked)

PyQt5

import sys

import sip
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.Qsci import QsciScintilla, QsciLexerPython


class SimplePythonEditor(QsciScintilla):
    ARROW_MARKER_NUM = 8

    def __init__(self, parent=None):
        super(SimplePythonEditor, self).__init__(parent)

        # Set the default font
        font = QFont()
        font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(10)
        self.setFont(font)
        self.setMarginsFont(font)

        # Margin 0 is used for line numbers
        fontmetrics = QFontMetrics(font)
        self.setMarginsFont(font)
        self.setMarginWidth(0, fontmetrics.width("00000") + 6)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QColor("#cccccc"))

        # Clickable margin 1 for showing markers
        self.setMarginSensitivity(1, True)
        self.marginClicked.connect(self.on_margin_clicked)
        self.markerDefine(QsciScintilla.RightArrow,
            self.ARROW_MARKER_NUM)
        self.setMarkerBackgroundColor(QColor("#ee1111"),
            self.ARROW_MARKER_NUM)

        # Brace matching: enable for a brace immediately before or after
        # the current position
        #
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Current line visible with special background color
        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QColor("#ffe4e4"))

        # Set Python lexer
        # Set style for Python comments (style number 1) to a fixed-width
        # courier.
        #

        lexer = QsciLexerPython()
        lexer.setDefaultFont(font)
        self.setLexer(lexer)

        text = bytearray(str.encode("Arial"))
# 32, "Courier New"
        self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, text)

        # Don't want to see the horizontal scrollbar at all
        # Use raw message to Scintilla here (all messages are documented
        # here: http://www.scintilla.org/ScintillaDoc.html)
        self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)

        # not too small
        self.setMinimumSize(600, 450)

    def on_margin_clicked(self, nmargin, nline, modifiers):
        # Toggle marker for the line the margin was clicked on
        if self.markersAtLine(nline) != 0:
            self.markerDelete(nline, self.ARROW_MARKER_NUM)
        else:
            self.markerAdd(nline, self.ARROW_MARKER_NUM)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    editor = SimplePythonEditor()
    editor.show()
    editor.setText(open(sys.argv[0]).read())
    app.exec_()

【讨论】:

    【解决方案3】:

    我通过邮件从 Matic Kukovec 那里得到了一个有用的答复,我想在这里分享。 Matic Kukovec 基于 QScintilla 制作了一个令人难以置信的 IDE:https://github.com/matkuki/ExCo。也许它会激发更多人深入研究 QScintilla(以及可点击的变量和函数)。


    热点使文本可点击。您必须使用 QScintilla.SendScintilla 函数手动设置它的样式。 我在编辑器 Ex.Co 中使用的示例函数。 (https://github.com/matkuki/ExCo):

    def style_hotspot(self, index_from, length, color=0xff0000):
        """Style the text from/to with a hotspot"""
        send_scintilla = 
        #Use the scintilla low level messaging system to set the hotspot
        self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_STYLESETHOTSPOT, 2, True)
        self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_SETHOTSPOTACTIVEFORE, True, color)
        self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_SETHOTSPOTACTIVEUNDERLINE, True)
        self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_STARTSTYLING, index_from, 2)
        self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_SETSTYLING, length, 2)
    

    当您将鼠标悬停在 QScintilla 编辑器上时,这会使文本可点击。 上述函数中的数字 2 为热点样式编号。 要捕获单击热点时触发的事件,请连接到这些信号:

    QScintilla.SCN_HOTSPOTCLICK
    QScintilla.SCN_HOTSPOTDOUBLECLICK
    QScintilla.SCN_HOTSPOTRELEASECLICK
    

    有关更多详细信息,请参阅 Scintilla 热点文档: http://www.scintilla.org/ScintillaDoc.html#SCI_STYLESETHOTSPOT 和 QScintilla 热点事件: http://pyqt.sourceforge.net/Docs/QScintilla2/classQsciScintillaBase.html#a5eff383e6fa96cbbaba6a2558b076c0b


    首先,非常感谢 Kukovec 先生!关于您的回答,我有几个问题:

    (1)在您的示例函数中有几处我不明白。

    def style_hotspot(self, index_from, length, color=0xff0000):
        """Style the text from/to with a hotspot"""
        send_scintilla =     # you undefine send_scintilla?
        #Use the scintilla low level messaging system to set the hotspot
        self.SendScintilla(..) # What object does 'self' refer to in this
        self.SendScintilla(..) # context?
        self.SendScintilla(..)
    

    (2) 你说“要捕捉点击热点时触发的事件,请连接到这些信号:”

    QScintilla.SCN_HOTSPOTCLICK
    QScintilla.SCN_HOTSPOTDOUBLECLICK
    QScintilla.SCN_HOTSPOTRELEASECLICK
    

    您实际上是如何连接到这些信号的?你能举一个例子吗?我习惯了 PyQt 信号槽机制,但我从未在 QScintilla 上使用过它。看到一个例子会很有帮助:-)

    (3) 也许我遗漏了一些东西,但我没有看到您在 QScintilla 中的何处定义函数和变量(而不是其他东西)在源代码中是可点击的?

    非常感谢您的帮助:-)

    【讨论】:

      【解决方案4】:

      查看以下文档: https://qscintilla.com/#clickable_text

      在 Qscintilla 中有两种方法可以使内容可点击 - 您可以使用热点或指示器。热点要求您覆盖底层词法分析器的默认行为,但我认为指标更适合您的用例。

      我建议您查看可以帮助您使文本可点击的指标,并且您可以定义在点击时执行的事件处理程序。 https://qscintilla.com/#clickable_text/indicators

      【讨论】:

      • 谢谢@Rida。几年后回到这个问题很有趣。对 QScintilla 上的不良文档感到沮丧,我与 Matic Kukovec 合作从头开始编写文档,从而创建了我们共同建立的这个网站 qscintilla.com。很高兴看到您现在链接到我们的网站 ;-)
      • 你最后的评论让我很开心
      • 我正在使用“pyqt4”开发一个桌面应用程序。该应用程序包含一个在 Qscintilla 之上实现的 XML 编辑器。但是,每当我单击通过指示器定义的类似超链接的文本时,我都会遇到问题。调用了“indicatorClicked”事件,但是当我在其中执行 SCI_GOTOLINE API 时,它会正确地转到所需的行,但不幸的是,由于某种原因,它会从单击的文本位置选择文本直到目标行。对我来说,似乎鼠标没有被释放!我也尝试使用“indicatorReleased”事件,但没有成功!你知道如何解决这个问题吗?
      • 您好@Rida,我刚刚将您的问题发送给了我的朋友 Matic。我会让你知道他的答案:-)。无论如何,我建议在 PyQt5 和最新的 QScintilla 安装中尝试它。 PyQt4 已经很老了。
      • @Rida 是的,我们遇到了完全相同的问题。有两种解决方案:在indicatorReleased 事件内使用QTimer.singleShot 在短暂延迟(我使用50ms)后执行SCI_GOTOLINE,或在indicatorReleased 事件内使用:while mouse_state != data.Qt.NoButton: PyQt5.QTest.qWait(1) 等到没有muse 按钮按下,然后执行 SCI_GOTOLINE。希望对您有所帮助。
      猜你喜欢
      • 1970-01-01
      • 2017-02-10
      • 1970-01-01
      • 1970-01-01
      • 2011-12-03
      • 2011-01-07
      • 2011-01-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多