Skip to content

Code highlighting in SlideViewer

A couple of months ago, Jesper Pedersen introduced our presentation tool for trainings, called SlideViewer. As SlideViewer is mainly used for our Qt and Squish trainings, we naturally have many code examples on the slides, ranging from C++ and QML to Python and XML. In this blog post, we will take a closer look at how we use WebKit to power the syntax highlighting of these code examples in the training slides.

In the spirit of not re-inventing the wheel, we looked at existing solutions ranging from the Qt Creator highlighting to what is used on the web. Eventually, we decided upon using the JavaScript based highlighting application SHJS, as that turned out to have the most extensive highlighting out-of-the-box among the solutions we looked at.

The SHJS highlighter does not have a function that you call with a piece of code and get a highlighted version back. Instead, when told to, it traverses the DOM tree of a web page. When it finds a piece of code to highlight, it modifies the DOM tree and inserts <span> elements around the different code fragments. Combining these <span> elements with a stylesheet, we get the desired highlighting of the code.

Of course, in order to provide SHJS with the DOM tree so it can perform the highlighting, we need a web page. The natural solution for that, since we’re in Qt land, is to simply use QWebView and friends. And as we do not need any of the QWidget attributes, our starting point is QWebPage.

To make this work for our code slides, we have a number of steps to take:

  1. Create a web page
  2. Feed the HTML with the code to highlight into the web page
  3. Run SHJS’s highlighting function
  4. Parse the HTML code once SHJS is done

Let’s look at these, step-by-step.

Creating a QWebPage is easy:

m_webPage = new QWebPage(this);

SHJS is modular in its implementation, so for the web page containing the code we want to highlight, we only need to use e.g. the C++ specific parts of SHJS. Looking at the code below, we include both the main script as well as a language-specific script from SHJS, then we wrap our, in this case, C++ code in a <pre class="sh_cpp">...</pre> tag. This tells SHJS that what we want to highlight is C++ code. If we wanted to highlight Python code instead, we would include the “sh_python.js” script and wrap the code in <pre class="sh_python">...</pre> instead.

const QString preHighlightHtml = QStringLiteral("<html>"
                        "<head>"
                        "<script src=\"qrc:///highlight/sh_main.js\"></script>"
                        "<script src=\"qrc:///highlight/sh_cpp.js\"></script>"
                        "</head>"
                        "<body onLoad=\"sh_highlightDocument();\">"
                        "<pre class=\"sh_cpp\">"
                        "%1" // code
                        "</pre></body></html>").arg(code.toHtmlEscaped());

m_webPage->currentFrame()->setHtml(preHighlightHtml);

The code above also takes care of the third item on our list. Notice the onLoad attribute of the <body> tag, telling SHJS to run the highlighting once the page is finished loading.

Now, in order to be notified when the highlighting is completed, we cannot use the loadFinished() signal from neither QWebPage nor QWebFrame. That signal is emitted when the page is done loading, and hence before the highlighting is completed. Remember the highlighting doesn’t run until the page is done loading.

To realize when the highlighting is done, we have a number of options. We could add a timer that checks every 100 ms or so, whether we have any <span> tags present. This might lead to a race though, as some parts of the code might have been highlighted by then, but not all of it.

Thanks to the JavaScript integration, we can make our C++ objects available to the JavaScript context in our web page, and this is the technique we use to get notified when the highlighting is done. So to get notified, we add a callback for the SHJS script to call once the highlighting is completed. This is done by calling QWebFrame::addToJavaScriptWindowObject(..), which makes a C++ QObject available to the frame’s JavaScript context, in this case under the name highlighter.

m_webPage->currentFrame()->addToJavaScriptWindowObject("highlighter", this);

This needs to be done each time after loading a new URL. Thankfully, QWebFrame has a signal telling us when it’s needed: QWebFrame::javaScriptWindowObjectCleared().

connect(m_webPage->currentFrame(),
        &QWebFrame::javaScriptWindowObjectCleared,
        this, &Highlighter::addJSCallback);

The implementation of the callback is straightforward:

// Header file
Q_INVOKABLE void setHighlightCompleted(bool success);

// Source file
void Highlighter::setHighlightCompleted(bool success)
{
    emit highlightCompleted(success ? parseHighlightedHtml() : plainTextCode());
}

As a last step, we modify the SHJS code slightly, to call this function when it’s completed:

highlighter.setHighlightCompleted(true);

The parseHighlightedHtml() function used above, simply parses and returns the HTML code between <pre class="sh_cpp"> and </pre>, which is now full of <span> tags generated by SHJS. Combine this with the SHJS stylesheet and we have our highlighted C++ code!

The above described method is used for highlighting C++, Python, XML, JavaScript and Java code. For QML code, we had to take a different approach, as SHJS has no built-in support for QML. Instead, we use a Python script that, similarly to SHJS above, gives us HTML code with <span> tags to combine with a stylesheet.

Categories: KDAB Blogs / KDAB on Qt / SlideViewer

Tags:

2 thoughts on “Code highlighting in SlideViewer”

Leave a Reply

Your email address will not be published. Required fields are marked *