QScintilla based text editor in PyQt5 with clickable functions and variables
<p>I am trying to make a simple texteditor with basic syntax highlighting, code completion and clickable functions &amp; variables in PyQt5. My best hope to achieve this is using the QScintilla port <br>
for PyQt5.<br>
I have found the following QScintilla-based texteditor example on the Eli Bendersky website (<a href="http://eli.thegreenplace.net/2011/04/01/sample-using-qscintilla-with-pyqt" rel="nofollow">http://eli.thegreenplace.net/2011/04/01/sample-using-qscintilla-with-pyqt</a>, Victor S. has adapted it to PyQt5). I think this example is a good starting point:</p>

<pre><code>#-------------------------------------------------------------------------
# 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_()
</code></pre>

<p>Just copy-paste this code into an empty <code>.py</code> file, and run it. You should get the following simple texteditor appearing on your display:</p>

<p><a href="https://i.stack.imgur.com/MRcdJ.png" rel="nofollow"><img src="https://i.stack.imgur.com/MRcdJ.png" alt="enter image description here"></a></p>

<p>Notice how perfect the syntax highlighting is! QScintilla certainly did some parsing on the background to achieve that.<br>
Is it possible to make <em>clickable functions &amp; variables</em> for this texteditor? Every self-respecting IDE has it. You click on a function, and the IDE jumps to the function definition. The same for variables. I would like to know:<br></p>

<ul>
<li>Does QScintilla support <em>clickable functions &amp; variables</em>?</li>
<li>If not, is it possible to import another python module that implements this feature in the QScintilla texteditor?</li>
</ul>

<hr>

<p><strong>EDIT :</strong><br>
Î»user noted the following:</p>

<blockquote>
  <p>Clickable function names require full parsing with a much deeper knowledge of a programming language [..]<br>This is way beyond the scope of Scintilla/QScintilla. Scintilla provides a way to react when the mouse clicks somewhere on the text, but the logic of "where is the definition of a function" is not in Scintilla and probably never will be.<br>However, some projects are dedicated to this task, like <strong>ctags</strong>. You could simply write a wrapper around this kind of tool.</p>
</blockquote>

<p>I guess that writing such wrapper for <strong>ctags</strong> is now on my TODO list. The very first step is to get a reaction (Qt signal) when the user clicks on a function or variable. And perhaps the function/variable should turn a bit blueish when you hover with the mouse over it, to notify the user that it is clickable. I already tried to achieve this, but am held back by the shortage of QScintilla documentation.</p>

<p>So let us trim down the question to: <em>How do you make a function or variable in the QScintilla texteditor clickable (with clickable defined as 'something happens')</em></p>

<p>I got a helpful answer from Matic Kukovec through mail, that I would like to share here. Matic Kukovec made an incredible IDE based on QScintilla: <a href="https://github.com/matkuki/ExCo" rel="nofollow">https://github.com/matkuki/ExCo</a>. Maybe it will inspire more people to dig deeper into QScintilla (and clickable variables and functions).</p>

<hr>

<p>Hotspots make text clickable. You have to style it manualy using the <code>QScintilla.SendScintilla</code> function.
Example function I used in my editor Ex.Co. ( <a href="https://github.com/matkuki/ExCo" rel="nofollow">https://github.com/matkuki/ExCo</a> ):</p>

<pre><code>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)
</code></pre>

<p>This makes text in the QScintilla editor clickable when you hover the mouse over it.
The number 2 in the above functions is the hotspot style number.
To catch the event that fires when you click the hotspot, connect to these signals:</p>

<pre><code>QScintilla.SCN_HOTSPOTCLICK
QScintilla.SCN_HOTSPOTDOUBLECLICK
QScintilla.SCN_HOTSPOTRELEASECLICK
</code></pre>

<p>For more details look at Scintilla hotspot documentation:
<a href="http://www.scintilla.org/ScintillaDoc.html#SCI_STYLESETHOTSPOT" rel="nofollow">http://www.scintilla.org/ScintillaDoc.html#SCI_STYLESETHOTSPOT</a>
and QScintilla hotspot events:
<a href="http://pyqt.sourceforge.net/Docs/QScintilla2/classQsciScintillaBase.html#a5eff383e6fa96cbbaba6a2558b076c0b" rel="nofollow">http://pyqt.sourceforge.net/Docs/QScintilla2/classQsciScintillaBase.html#a5eff383e6fa96cbbaba6a2558b076c0b</a></p>

<hr>

<p>First of all, a big thank you to Mr. Kukovec! I have a few questions regarding your answer:</p>

<p><strong>(1)</strong> There are a couple of things I don't understand in your example function.</p>

<pre><code>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(..)
</code></pre>

<p><strong>(2)</strong> You say "To catch the event that fires when you click the hotspot, connect to these signals:"</p>

<pre><code>QScintilla.SCN_HOTSPOTCLICK
QScintilla.SCN_HOTSPOTDOUBLECLICK
QScintilla.SCN_HOTSPOTRELEASECLICK
</code></pre>

<p>How do you actually connect to those signals? Could you give one example? I'm used to the PyQt signal-slot mechanism, but I never used it on QScintilla. It would be a big help to see an example :-)</p>

<p><strong>(3)</strong> Maybe I missed something, but I don't see where you define in QScintilla that functions and variables (and not other things) are clickable in the source code?</p>

<p>Thank you so much for your kind help :-)</p>

python python-3.x pyqt pyqt5 qscintilla