setAutoIndentStyle() в лексере QScintilla не работает

Автоматический отступ — очень удобная функция, предлагаемая QScintilla. При вставке новой строки автоматический отступ перемещает курсор на тот же уровень отступа, что и предыдущий:

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

<сильный>1. Без лексера
Если вы НЕ устанавливали лексер, вы можете легко включить эту функцию следующим образом:

self.__editor.setAutoIndent(True)

self.__editor в этой кодовой строке является экземпляром QsciScintilla(), поэтому он представляет собой фактический редактор в моем коде.


2. С лексером
Даже если вы активируете лексер, предыдущий подход все равно будет работать. Но это не очень хорошая практика, потому что лексер может переопределить этот параметр. Таким образом, лучшим подходом было бы удалить предыдущую кодовую строку и включить автоматический отступ в лексере:

class MyLexer(QsciLexerCustom):

    def __init__(self, parent):
        super(MyLexer, self).__init__(parent)
        [...]
        self.setAutoIndentStyle(QsciScintilla.AiMaintain)
    ''''''

    def language(self):
        [...]
    ''''''

    def description(self, style):
        [...]
    ''''''

    def styleText(self, start, end):
        [...]
    ''''''

'''--- end class ---'''

Еще одним преимуществом этого подхода является большая гибкость. Включение автоматического отступа в лексере позволяет выбирать между (или даже комбинировать):

  • QsciScintilla.AiOpening
  • QsciScintilla.AiClosing
  • QsciScintilla.AiMaintain


3. Проблема
Описанный в первом абзаце подход без лексера работает. Подход, описанный во втором абзаце с лексером, не работает. Независимо от того, какую настройку я выберу в качестве параметра для setAutoIndentStyle(..), ничего не изменится.
Почему?

<сильный>4. Полный пример для экспериментов
Вот пример для экспериментов. Просто скопируйте и вставьте его в файл .py и запустите. Вы должны получить работающий редактор с простой подсветкой синтаксиса. Вы можете использовать его для экспериментов с автоматическим отступом:

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.Qsci import *
import re


class MyLexer(QsciLexerCustom):

    def __init__(self, parent):
        super(MyLexer, self).__init__(parent)

        # Default text settings
        # ----------------------
        self.setDefaultColor(QColor("#ff000000"))
        self.setDefaultPaper(QColor("#ffffffff"))
        self.setDefaultFont(QFont("Consolas", 14))

        # Initialize colors per style
        # ----------------------------
        self.setColor(QColor("#ff000000"), 0)   # Style 0: black
        self.setColor(QColor("#ff7f0000"), 1)   # Style 1: red
        self.setColor(QColor("#ff0000bf"), 2)   # Style 2: blue
        self.setColor(QColor("#ff007f00"), 3)   # Style 3: green

        # Initialize paper colors per style
        # ----------------------------------
        self.setPaper(QColor("#ffffffff"), 0)   # Style 0: white
        self.setPaper(QColor("#ffffffff"), 1)   # Style 1: white
        self.setPaper(QColor("#ffffffff"), 2)   # Style 2: white
        self.setPaper(QColor("#ffffffff"), 3)   # Style 3: white

        # Initialize fonts per style
        # ---------------------------
        self.setFont(QFont("Consolas", 14, weight=QFont.Bold), 0)   # Style 0: Consolas 14pt
        self.setFont(QFont("Consolas", 14, weight=QFont.Bold), 1)   # Style 1: Consolas 14pt
        self.setFont(QFont("Consolas", 14, weight=QFont.Bold), 2)   # Style 2: Consolas 14pt
        self.setFont(QFont("Consolas", 14, weight=QFont.Bold), 3)   # Style 3: Consolas 14pt

        # Auto indent
        # ------------
        #self.setAutoIndentStyle(QsciScintilla.AiOpening | QsciScintilla.AiClosing)
        self.setAutoIndentStyle(QsciScintilla.AiMaintain)
        print(self.autoIndentStyle())

    ''''''

    def language(self):
        return "SimpleLanguage"
    ''''''

    def description(self, style):
        if style == 0:
            return "myStyle_0"
        elif style == 1:
            return "myStyle_1"
        elif style == 2:
            return "myStyle_2"
        elif style == 3:
            return "myStyle_3"
        ###
        return ""
    ''''''

    def styleText(self, start, end):
        # 1. Initialize the styling procedure
        # ------------------------------------
        self.startStyling(start)

        # 2. Slice out a part from the text
        # ----------------------------------
        text = self.parent().text()[start:end]

        # 3. Tokenize the text
        # ---------------------
        p = re.compile(r"[*]\/|\/[*]|\s+|\w+|\W")

        # 'token_list' is a list of tuples: (token_name, token_len)
        token_list = [ (token, len(bytearray(token, "utf-8"))) for token in p.findall(text)]

        # 4. Style the text
        # ------------------
        # 4.1 Check if multiline comment
        multiline_comm_flag = False
        editor = self.parent()
        if start > 0:
            previous_style_nr = editor.SendScintilla(editor.SCI_GETSTYLEAT, start - 1)
            if previous_style_nr == 3:
                multiline_comm_flag = True
            ###
        ###
        # 4.2 Style the text in a loop
        for i, token in enumerate(token_list):
            if multiline_comm_flag:
                self.setStyling(token[1], 3)
                if token[0] == "*/":
                    multiline_comm_flag = False
                ###
            ###
            else:
                if token[0] in ["for", "while", "return", "int", "include"]:
                    # Red style
                    self.setStyling(token[1], 1)

                elif token[0] in ["(", ")", "{", "}", "[", "]", "#"]:
                    # Blue style
                    self.setStyling(token[1], 2)

                elif token[0] == "/*":
                    multiline_comm_flag = True
                    self.setStyling(token[1], 3)

                else:
                    # Default style
                    self.setStyling(token[1], 0)
                ###
            ###
        ###

    ''''''

''' end Class '''




myCodeSample = r"""#include <stdio.h>
/*
 * This is a
 * multiline
 * comment */
int main()
{
    char arr[5] = {'h', 'e', 'l', 'l', 'o'};

    int i;
    for(i = 0; i < 5; i++) {
        printf(arr[i]);
    }
    return 0;
}""".replace("\n","\r\n")




class CustomMainWindow(QMainWindow):
    def __init__(self):
        super(CustomMainWindow, self).__init__()

        # -------------------------------- #
        #           Window setup           #
        # -------------------------------- #

        # 1. Define the geometry of the main window
        # ------------------------------------------
        self.setGeometry(300, 300, 800, 400)
        self.setWindowTitle("QScintilla Test")

        # 2. Create frame and layout
        # ---------------------------
        self.__frm = QFrame(self)
        self.__frm.setStyleSheet("QWidget { background-color: #ffeaeaea }")
        self.__lyt = QVBoxLayout()
        self.__frm.setLayout(self.__lyt)
        self.setCentralWidget(self.__frm)
        self.__myFont = QFont()
        self.__myFont.setPointSize(14)

        # 3. Place a button
        # ------------------
        self.__btn = QPushButton("Qsci")
        self.__btn.setFixedWidth(50)
        self.__btn.setFixedHeight(50)
        self.__btn.clicked.connect(self.__btn_action)
        self.__btn.setFont(self.__myFont)
        self.__lyt.addWidget(self.__btn)

        # -------------------------------- #
        #     QScintilla editor setup      #
        # -------------------------------- #

        # ! Make instance of QSciScintilla class!
        # ----------------------------------------
        self.__editor = QsciScintilla()
        self.__editor.setText(myCodeSample)     # 'myCodeSample' is a string containing some C-code
        self.__editor.setLexer(None)            # We install lexer later
        self.__editor.setUtf8(True)             # Set encoding to UTF-8
        self.__editor.setFont(self.__myFont)    # Gets overridden by lexer later on

        # 1. Text wrapping
        # -----------------
        self.__editor.setWrapMode(QsciScintilla.WrapWord)
        self.__editor.setWrapVisualFlags(QsciScintilla.WrapFlagByText)
        self.__editor.setWrapIndentMode(QsciScintilla.WrapIndentIndented)

        # 2. End-of-line mode
        # --------------------
        self.__editor.setEolMode(QsciScintilla.EolWindows)
        self.__editor.setEolVisibility(False)

        # 3. Indentation
        # ---------------
        self.__editor.setIndentationsUseTabs(False)
        self.__editor.setTabWidth(4)
        self.__editor.setIndentationGuides(True)
        self.__editor.setTabIndents(True)
        #self.__editor.setAutoIndent(True)   <- This is set in the lexer!!!

        # 4. Caret
        # ---------
        self.__editor.setCaretForegroundColor(QColor("#ff0000ff"))
        self.__editor.setCaretLineVisible(True)
        self.__editor.setCaretLineBackgroundColor(QColor("#1f0000ff"))
        self.__editor.setCaretWidth(2)

        # 5. Margins
        # -----------
        # Margin 0 = Line nr margin
        self.__editor.setMarginType(0, QsciScintilla.NumberMargin)
        self.__editor.setMarginWidth(0, "0000")
        self.__editor.setMarginsForegroundColor(QColor("#ff888888"))

        # -------------------------------- #
        #          Install lexer           #
        # -------------------------------- #
        self.__lexer = MyLexer(self.__editor)
        self.__editor.setLexer(self.__lexer)

        # ! Add editor to layout !
        # -------------------------
        self.__lyt.addWidget(self.__editor)
        self.show()

    ''''''

    def __btn_action(self):
        print("Hello World!")
    ''''''

''' End Class '''

if __name__ == '__main__':
    app = QApplication(sys.argv)
    QApplication.setStyle(QStyleFactory.create('Fusion'))
    myGUI = CustomMainWindow()

    sys.exit(app.exec_())

''''''

Я взял этот пример (и немного адаптировал его к потребностям этого вопроса) с моего веб-сайта на QScintilla: https://qscintilla.com/custom-lexer-example/

<сильный>5. Примечания
Для полноты картины моя система:

  • Windows 10
  • Питон 3.6
  • QScintilla 2.10 или выше

person K.Mulier    schedule 14.05.2017    source источник


Ответы (1)


В лексере scintilla есть 2 части: 1-я предназначена для раскрашивания синтаксиса, 2-я — для отступов.

Пример, который вы используете, предназначен только для окраски синтаксиса.

Я не использовал реализацию QScintilla на python, поэтому не уверен, доступны ли функции в python, однако в C++ вы можете использовать indentLine в своем лексере, чтобы установить его, или установить ключевые слова begin и endblock лексера, чтобы позволить qscintilla сделать это для вас.

person lostbard    schedule 14.05.2017
comment
Большое Вам спасибо. +1 за интересный ответ! - person K.Mulier; 14.05.2017
comment
Не могли бы вы уточнить, что вы подразумеваете под двумя частями лексера? Пожалуйста, не стесняйтесь приводить любые примеры на C++. Я могу перевести большую часть этого на Python ???? - person K.Mulier; 14.05.2017
comment
Я даже не уверен, что это возможно реализовать в python таким образом, но для лексера C++ одним из наиболее полных, пожалуй, является LexerCPP (Qt4Qt5/qscilexercpp.cpp в исходном коде qscintilla), который содержит QsciLexerCPP::blockStart и QsciLexerCPP: :blockEnd функции - person lostbard; 15.05.2017
comment
2 части просто означают, что основной функцией scintilla является синтаксическая раскраска кода. Во-вторых, вы также можете установить уровни отступов. Например, лексер CPP в qscintilla использует слова начала/конца блока для отступа, лексер Matlab явно устанавливает отступ - person lostbard; 15.05.2017