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. Проблема
Подход, описанный в первом абзаце без лексера, работает. Подход, описанный во втором абзаце с помощью lexer, отсутствует. Независимо от того, какую настройку я выберу в качестве параметра 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
  • Python 3.6
  • QScintilla 2.10 или выше

1 ответ

Лексер сцинтиллы состоит из 2 частей, первая из которых предназначена для окраски синтаксиса, а вторая - для отступа.

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

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

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