Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make QScintilla auto-indent like SublimeText?

Consider the below mcve:

import sys
import textwrap

from PyQt5.Qsci import QsciScintilla
from PyQt5.Qt import *


if __name__ == '__main__':

    app = QApplication(sys.argv)
    view = QsciScintilla()

    view.SendScintilla(view.SCI_SETMULTIPLESELECTION, True)
    view.SendScintilla(view.SCI_SETMULTIPASTE, 1)
    view.SendScintilla(view.SCI_SETADDITIONALSELECTIONTYPING, True)

    view.setAutoIndent(True)
    view.setTabWidth(4)
    view.setIndentationGuides(True)
    view.setIndentationsUseTabs(False)
    view.setBackspaceUnindents(True)

    view.setText(textwrap.dedent("""\
        def foo(a,b):
            print('hello')
    """))

    view.show()
    app.exec_()

The behaviour of the auto-indent of the above snippet is really bad when comparing it with editors such as SublimeText or CodeMirror. First let's see how nice will behave the autoindent feature in SublimeText with single or multiple selections.

enter image description here

And now let's see how the auto-indent works in with the above snippet:

enter image description here

In comparison to SublimeText the way QScintilla works when auto-indentation is enabled with both single/multi selections is corky and really bad/unusable.

The first step to make the widget more like SublimeText/Codemirror would be disconnecting the current slot that makes autoindentation to behave badly, we can achieve that by doing:

print(view.receivers(view.SCN_CHARADDED))
view.SCN_CHARADDED.disconnect()
print(view.receivers(view.SCN_CHARADDED))

At this point you'd be ready to connect SCN_CHARADDED with your custom slot doing all the magic :)

QUESTION: How would you modify the above snippet so all selections will be preserved and the auto-indentation will behave exactly like SublimeText, Codemirror or any serious text editor out there?

REFERENCES:

  • https://www.riverbankcomputing.com/static/Docs/QScintilla/classQsciScintillaBase.html#signals

  • QScintilla source code, below you can see what the private slot we've disconnected by using disconnect would look like:

qsciscintilla.h

class QSCINTILLA_EXPORT QsciScintilla : public QsciScintillaBase
{
    Q_OBJECT

public:
    ...
    private slots:
        void handleCharAdded(int charadded);
    ...
    private:
        void autoIndentation(char ch, long pos);

qsciscintilla.cpp

connect(this,SIGNAL(SCN_CHARADDED(int)),
         SLOT(handleCharAdded(int)));

...

// Handle the addition of a character.
void QsciScintilla::handleCharAdded(int ch)
{
    // Ignore if there is a selection.
    long pos = SendScintilla(SCI_GETSELECTIONSTART);

    if (pos != SendScintilla(SCI_GETSELECTIONEND) || pos == 0)
        return;

    // If auto-completion is already active then see if this character is a
    // start character.  If it is then create a new list which will be a subset
    // of the current one.  The case where it isn't a start character seems to
    // be handled correctly elsewhere.
    if (isListActive() && isStartChar(ch))
    {
        cancelList();
        startAutoCompletion(acSource, false, use_single == AcusAlways);

        return;
    }

    // Handle call tips.
    if (call_tips_style != CallTipsNone && !lex.isNull() && strchr("(),", ch) != NULL)
        callTip();

    // Handle auto-indentation.
    if (autoInd)
    {
        if (lex.isNull() || (lex->autoIndentStyle() & AiMaintain))
            maintainIndentation(ch, pos);
        else
            autoIndentation(ch, pos);
    }

    // See if we might want to start auto-completion.
    if (!isCallTipActive() && acSource != AcsNone)
    {
        if (isStartChar(ch))
            startAutoCompletion(acSource, false, use_single == AcusAlways);
        else if (acThresh >= 1 && isWordCharacter(ch))
            startAutoCompletion(acSource, true, use_single == AcusAlways);
    }
}

IMPORTANT: I've decided to post the relevant c++ bits so you'll got more background about how the indentation is achieved internally to give more clues about a possible replacement... The goal of this thread is to try to find a pure python solution though. I'd like to avoid modifying the QScintilla source code (if possible) so maintenance/upgrading will remain as simple as possible and QScintilla dep can still be seen as a black box.

like image 575
BPL Avatar asked Apr 24 '19 09:04

BPL


1 Answers

It looks like you have to code your own version, the documentation mentions the most important point's about it already in the chapter Installation:

As supplied QScintilla will be built as a shared library/DLL and installed in the same directories as the Qt libraries and include files.

If you wish to build a static version of the library then pass CONFIG+=staticlib on the qmake command line.

If you want to make more significant changes to the configuration then edit the file qscintilla.pro in the Qt4Qt5 directory.

If you do make changes, specifically to the names of the installation directories or the name of the library, then you may also need to update the Qt4Qt5/features/qscintilla2.prf file.*

Further steps are explained there too.

like image 112
David Avatar answered Nov 05 '22 16:11

David