styleText() – the highlighting engine

The styleText() method gets called automatically everytime the editors text has changed. It is your task to make the styleText() method perform the required syntax highlighting! We will discover together how you can do that, and what your tools are.
 

A minimalistic styleText() implementation

While styling the text, the syntax highlighting engine keeps an internal counter indicating which character is currently getting styled. Each character in the text equals one count.

Now imagine you want to highlight the following text:

myCodeSample = r"""#include 

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")

myCodeSample is a raw string (hence the ‘r’ prefix). The triple quotes """ allow to define a string in Python over several lines. Python automatically stitches them together – with an EOL character \n in between. Before actually using the string, we convert the Unix-type EOL character \n to the windows type \r\n. That’s because our editor is used to the windows-style EOL:

self.__editor.setEolMode(QsciScintilla.EolWindows)

Because we know that QScintilla keeps an internal counter, we will count the nr of characters in our string. Don’t forget that whitespaces are also characters, and don’t forget the newline character too. We’re using the Windows-style EOL, such that a newline is actually two characters: \r and \n.

 

 

This sums up to 177 chars. So after highlighting all this text, the internal counter will be i = 177!

We will now go through a very simple example to highlight the text. We’ll make the first line black (style 0), the second red (style 1) and the third blue (style 2). The fourth is again black, and so forth.

  • When entering the styleText() function, we reset the internal counter to 0:

    self.startStyling(0)
     

  • The first line is 20 characters wide. So we style the first 20 characters in black (style 0).

    self.setStyling(20, 0)
     

  • Characters 0 – 19 are now black. The internal counter is now i = 20. We style the following line red (style 1) – even though it is just an empty line with the two EOL characters:

    self.setStyling(2, 1)
     

  • Characters 20 – 21 are now red. The internal counter is now i = 22. We style the following line blue (style 2):

    self.setStyling(12, 2)
     

  • Characters 22 – 33 are now blue. The internal counter is now i = 34. We style the following line again black (style 0):

    self.setStyling(3, 0)
     

  • And so forth…

Below you can find the complete styleText() code for this little experiment:

class MyLexer(QsciLexerCustom):

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

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

    def description(self, style_nr):
        [...]
    ''''''
	
    def styleText(self, start, end):
        # 1. Reset the internal counter
        # ------------------------------
        self.startStyling(0)
        
        # 2. Highlight the text
        # ----------------------
        self.setStyling(20, 0)
        self.setStyling(2,  1)
        self.setStyling(12, 2)
        self.setStyling(3,  0)
        self.setStyling(46, 1)
        self.setStyling(2,  2)
        self.setStyling(12, 0)
        self.setStyling(30, 1)
        self.setStyling(25, 2)
        self.setStyling(7,  0)
        self.setStyling(15, 1)
        self.setStyling(3,  2)
    ''''''

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

 
The experiment works! This is what you get:
 

 

I made a little movie about this experiment. Enjoy:

 

 

Of course you’re smart enough to realize that this implementation makes no sense. The lenght of each line is hard-coded. To get this working for any text, you could use regexes to search for the EOL characters \r and \n, calculate the length of each line dynamically and rewrite the implementation. But that would only distract you right now from the syntax highlighting mechanisms that I’m trying to show here. So I kept it simple and hard-coded.

 
 

The start and end parameters

Did you notice the start and end parameters in the styleText() method? Everytime QScintilla calls the styleText() method, it passes a start- and stop point for the syntax highlighting. In fact, QScintilla says: “Hey, I think you should restyle the text between the character at position start up to the character at position end. You are completely free to ignore this suggestion. It’s only there to help. How can you use those two numbers to your advantage?
 
In general you want to start highlighting from the suggested start position. So you want to initialize the internal counter to that point:

self.startStyling(start)

 
You also need to access the text between the suggested start and end positions:

text = self.parent().text()[start:end]

The parent of your lexer-object is the editor. Get the entire text in the editor, and slice a piece out.