Как я могу сделать автоматический отступ QScintilla, подобный SublimeText?

#python #c #pyqt5 #sublimetext #qscintilla

#python #c #pyqt5 #sublimetext #qscintilla

Вопрос:

Рассмотрим приведенный ниже 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_()
  

Поведение автоматического отступа в приведенном выше фрагменте действительно плохо при сравнении его с такими редакторами, как SublimeText или CodeMirror. Сначала давайте посмотрим, как хорошо будет вести себя функция автоматического отступа в SublimeText с одним или несколькими вариантами выбора.

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

А теперь давайте посмотрим, как работает автоматический отступ в приведенном выше фрагменте:

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

По сравнению с SublimeText способ работы QScintilla, когда автоотвод включен как для одиночного, так и для множественного выбора, затруднен и действительно плох / непригоден для использования.

Первым шагом, чтобы сделать виджет более похожим на SublimeText / Codemirror, было бы отключение текущего слота, из-за которого автоиндентирование ведет себя плохо, мы можем добиться этого, выполнив:

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

На этом этапе вы будете готовы подключиться SCN_CHARADDED к своему пользовательскому слоту, творящему все волшебство 🙂

ВОПРОС: Как бы вы изменили приведенный выше фрагмент, чтобы все выделенные фрагменты были сохранены, а автоматический отступ вел себя точно так же, как SublimeText, Codemirror или любой другой серьезный текстовый редактор?

ССЫЛКИ:

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() amp;amp; isStartChar(ch))
    {
        cancelList();
        startAutoCompletion(acSource, false, use_single == AcusAlways);

        return;
    }

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

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

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

ВАЖНО: я решил опубликовать соответствующие фрагменты c , чтобы вы получили больше информации о том, как отступ достигается внутри, чтобы дать больше подсказок о возможной замене… Цель этого потока — попытаться найти решение на чистом python. Я бы хотел избежать изменения исходного кода QScintilla (если это возможно), чтобы обслуживание / обновление оставались максимально простыми, а QScintilla dep по-прежнему можно рассматривать как черный ящик.

Комментарии:

1. Это C вопрос, а также Python вопрос, возможно, это помогло бы добавить C тег.

2. @LogicalBranch Мммм, ты в чем-то прав … дело в том, что сначала я хотел бы узнать, существует ли решение на чистом python, которое решает данную проблему. Почему? Что ж, на данный момент мы решили изменить внутренний исходный код QScintilla (принадлежащий riverbank), мы больше не сможем легко обновлять версию через pypi … кроме того, было бы не так просто перекомпилировать для всех основных платформ. Причина, по которой я опубликовал внутренние биты c , заключалась в том, чтобы предоставить соответствующую информацию потенциальным участникам. Имеет ли это смысл?

3. В качестве решения на python вам понадобится наблюдатель, не знаю, сможете ли вы как-то его подключить. Другим решением было бы создание макро для QScintilla — просто мозговой штурм, это интересный вопрос.

4. Я знаю, что это своего рода хакерство, но поскольку вы используете несколько курсоров, когда выбрано более одного курсора, разве вы не можете сохранить местоположение курсоров во временной переменной? Затем, когда вставлен символ пробела, восстановите курсоры в их первоначальных положениях, позволяя пользователю продолжать печатать в этих позициях.

5. Эй, ребята, я играл с notepad , и он работает вполне нормально, этот редактор основан на Scintilla… Для тех, кто не знает, QScintilla также использует Scintilla за кулисами. Хотя notepad не поддерживает множественный выбор (по крайней мере, по умолчанию)… В любом случае, мозговой штурм, который вы проводите, классный, продолжайте в том же духе… Не уверен, что есть какой-то способ каким-то образом отключить частный слот и подключить нашу собственную функциональность каким-то хакерским способом :/ …

Ответ №1:

Похоже, вам нужно написать свою собственную версию, в документации упоминается самый важный момент об этом уже в главе Установка:

Как указано, QScintilla будет собрана как разделяемая библиотека / DLL и установлена в те же каталоги, что и библиотеки Qt и включаемые файлы.

Если вы хотите создать статическую версию библиотеки, передайте CONFIG =staticlib в командной строке qmake.

Если вы хотите внести более существенные изменения в конфигурацию, отредактируйте файл qscintilla.pro в каталоге Qt4Qt5.

Если вы вносите изменения, в частности, в имена каталогов установки или название библиотеки, то вам также может потребоваться обновить файл Qt4Qt5/features/qscintilla2.prf.*

Дальнейшие шаги также объясняются там.

Ответ №2:

В QScintilla (особенно в SublimeText том виде, в каком он есть) нет интегрированного способа заставить работать автоматический отступ. Это поведение зависит от языка и пользователя. Собственная документация по Scintilla содержит примеры того, как запускать автоматический отступ. Извините, но это написано на C #. Я не нашел его написанным на Python.

Вот код (я знаю, что QScintilla — это порт для Qt, этот ориентированный на Scintilla код должен работать и с QScintilla, или, на худой конец, вы можете адаптировать его для C ):

 private void Scintilla_InsertCheck(object sender, InsertCheckEventArgs e) {

    if ((e.Text.EndsWith(""   Constants.vbCr) || e.Text.EndsWith(""   Constants.vbLf))) {
        int startPos = Scintilla.Lines(Scintilla.LineFromPosition(Scintilla.CurrentPosition)).Position;
        int endPos = e.Position;
        string curLineText = Scintilla.GetTextRange(startPos, (endPos - startPos)); 
        // Text until the caret so that the whitespace is always
        // equal in every line.

        Match indent = Regex.Match(curLineText, "^[ \t]*");
        e.Text = (e.Text   indent.Value);
        if (Regex.IsMatch(curLineText, "{\s*$")) {
            e.Text = (e.Text   Constants.vbTab);
        }
    }
}

private void Scintilla_CharAdded(object sender, CharAddedEventArgs e) {

    //The '}' char.
    if (e.Char == 125) {
        int curLine = Scintilla.LineFromPosition(Scintilla.CurrentPosition);

        if (Scintilla.Lines(curLine).Text.Trim() == "}") { 
        //Check whether the bracket is the only thing on the line. 
        //For cases like "if() { }".
            SetIndent(Scintilla, curLine, GetIndent(Scintilla, curLine) - 4);
        }
    }
}

//Codes for the handling the Indention of the lines.
//They are manually added here until they get officially 
//added to the Scintilla control.

#region "CodeIndent Handlers"
    const int SCI_SETLINEINDENTATION = 2126;
    const int SCI_GETLINEINDENTATION = 2127;
    private void SetIndent(ScintillaNET.Scintilla scin, int line, int indent) {
        scin.DirectMessage(SCI_SETLINEINDENTATION, new IntPtr(line), new IntPtr(indent));
    }
    private int GetIndent(ScintillaNET.Scintilla scin, int line) {
        return (scin.DirectMessage(SCI_GETLINEINDENTATION, new IntPtr(line), null).ToInt32);
    }
#endregion
  

Надеюсь, это поможет.

Комментарии:

1. Привет, прежде всего, спасибо за попытку, не уверен, что у меня будет достаточно времени, чтобы протестировать этот код до истечения срока действия вознаграждения… Но позвольте мне спросить вас, если я смогу перенести это на python, автоматическое указание будет вести себя как SublimeText (даже при работе с несколькими вариантами выбора)? Кроме того, вы сказали в своем ответе there is no integrated way to make auto-indentation , убедитесь, что вы снова прочитали мой вопрос, чтобы понять, что происходит, поскольку вся моя точка зрения заключалась в том, что setAutoIndent не будет вести себя как Sublime. В любом случае, где используется этот код (любой Windows exe, с которым я могу поиграть?)

2. Мой коллега сказал, что это код ScintillaNET, и он протестировал его в Windows. Честно говоря, я не на 100% уверен, что он работает с несколькими вариантами выбора. Но мой коллега сказал, что это так.