Основанный на QScintilla текстовый редактор в PyQt5 с интерактивными функциями и переменными

Я пытаюсь сделать простой текстовый редактор с подсветкой основного синтаксиса, дополнением кода и интерактивными функциями и переменными в PyQt5. Моя лучшая надежда для достижения этого - использование порта QScintilla.
для PyQt5.
Я нашел следующий пример текстового редактора на основе QScintilla на сайте Эли Бендерского ( http://eli.thegreenplace.net/2011/04/01/sample-using-qscintilla-with-pyqt, Виктор С. адаптировал его для 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 интерактивные функции и переменные?
  • Если нет, возможно ли импортировать другой модуль Python, который реализует эту функцию в текстовом редакторе QScintilla?

РЕДАКТИРОВАТЬ:
Юзер отметил следующее:

Для кликабельных имен функций требуется полный анализ с глубоким знанием языка программирования [..]
Это далеко выходит за рамки Scintilla/QScintilla. Scintilla предоставляет способ реагирования, когда мышь щелкает где-то по тексту, но логика "где определение функции" отсутствует в Scintilla и, вероятно, никогда не будет.
Однако некоторые проекты посвящены этой задаче, например ctags. Вы можете просто написать обертку вокруг этого вида инструмента.

Я думаю, что написание такой оболочки для ctags теперь в моем списке TODO. Самый первый шаг - получить реакцию (сигнал Qt), когда пользователь нажимает на функцию или переменную. И, возможно, функция / переменная должна стать немного голубоватой, когда вы наводите курсор мыши над ней, чтобы уведомить пользователя о том, что она активна. Я уже пытался добиться этого, но сдерживается нехваткой документации QScintilla.

Итак, позвольте нам урезать вопрос так: как сделать функцию или переменную в текстовом редакторе QScintilla кликабельной (кликабельная определяется как "что-то происходит")


РЕДАКТИРОВАТЬ:
Я только что вернулся к этому вопросу сейчас - несколько месяцев спустя. Я сотрудничаю с моим другом Матичем Куковцем в разработке веб-сайта о QScintilla. Это удобный для начинающих урок о том, как его использовать:

введите описание изображения здесь

https://qscintilla.com/

Я надеюсь, что эта инициатива восполняет пробел в отсутствии документации.

3 ответа

Решение

Способ использовать 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_()

Подсветка синтаксиса - это просто запуск лексера исходного файла для поиска токенов, а затем присвоение ему стилей. Лексер имеет очень базовое понимание языка программирования, он понимает только то, что представляет собой числовой литерал, ключевое слово, оператор, комментарий, несколько других и все. Это довольно простая работа, которую можно выполнить только с помощью регулярных выражений.

С другой стороны, для кликабельных имен функций требуется полный анализ с более глубоким знанием языка программирования, например, является ли это объявлением переменной или использования и т. Д. Кроме того, для этого может потребоваться анализ других исходных файлов, не открытых текущим редактором.,

Это далеко выходит за рамки Scintilla/QScintilla. Scintilla предоставляет способ реагирования, когда мышь щелкает где-то по тексту, но логика "где определение функции" отсутствует в Scintilla и, вероятно, никогда не будет.

Однако некоторые проекты посвящены этой задаче, например ctags. Вы можете просто написать обертку вокруг этого вида инструмента.

Я получил полезный ответ от Матика Куковца по почте, которым я хотел бы поделиться здесь. Matic Kukovec сделал потрясающую IDE на основе QScintilla: 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 и QScintilla горячих точек событий: http://pyqt.sourceforge.net/Docs/QScintilla2/classQsciScintillaBase.html


Прежде всего, большое спасибо господину Куковцу! У меня есть несколько вопросов относительно вашего ответа:

(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, что функции и переменные (а не другие вещи) кликабельны в исходном коде?

Большое спасибо за вашу помощь:-)

Взгляните на следующую документацию:https://qscintilla.com/

Есть два способа сделать объекты интерактивными в Qscintilla - вы можете использовать горячие точки или индикаторы. Горячие точки требуют, чтобы вы переопределили поведение по умолчанию базового лексера, но я думаю, что индикаторы более удобны для вашего варианта использования.

Я предлагаю вам взглянуть на индикаторы, которые могут помочь вам сделать текст интерактивным, и вы можете определить обработчики событий, которые запускаются при нажатии на него.https://qscintilla.com/

Другие вопросы по тегам