Kate Internals: Smart Cursors and Smart Ranges

SmartCursors and SmartRanges in KDE 4.0 – KDE 4.4

Since KDE 4.0 the KTextEditor interfaces have so called SmartCursors and SmartRanges. A SmartCursor is a text cursor (i.e. a line/column tuple) , which is bound to a text document. When editing the text document, the cursor is automatically moved such that it maintains its position. You need this for the displayed text cursor in a view for instance. If you type text, the cursor automatically moves.
A SmartRange consists of two SmartCursors: start and end. We use that for instance for the text selection or the inline spell checking, or KDevelop uses it to add arbitrary highlighting to parsed C/C++ code. Again, if you modify text in the document, the text range automatically moves, expands or shrinks.

The concept of SmartCursors and SmartRanges is doubtless very useful. However, for KDE 4.5 the Kate developers came up with an alternative implementation that will even deprecate the SmartCursors and SmartRanges in KDE 4.6. The reason for this is that the current implementation has several problems, which we will discuss in the following.

API Usage

The SmartRanges API can be used in very wrong ways. For instance, the SmartInterface has the two functions deleteCursors() and deleteRanges(). Besides that, a document reload also deletes all SmartCursors and SmartRanges. Both cases lead to a dangling pointer if you use SmartCursors or SmartRanges. Hence, whenever you use them, you always need a so-called SmartCursorWatcher / SmartCursorNotifier and SmartRangeWatcher / SmartRangeNotifier, which tell you that your SmartCursor or SmartRange is about to be deleted. Watcher and Notifier basically do the same thing. Besides deletion notification, they also tell you more e.g. that the SmartCursor position changed. If you use a notifier, those notifications are sent via signals. If you use a watcher, you have to overwrite virtuals. That is, we have two different concepts that basically do the same thing. If you use thousands of SmartRanges e.g. for arbitrary highlighting, we have possibly thousands of those emits, which does not really scale.

The API also allows to have parent/child relations, i.e. a SmartRange can have children and a parent. It is possible to traverse this hierarchy, and again since you get the pointers you can delete arbitrary SmartRanges or SmartCursors. Another reason why you always need a notifier or watcher. And if you have a notifier/watcher, you always have the signals emitted when e.g. a cursor changes, as explained above.

Further, we have lots of places where we use reference object KTextEditor::Cursor& as parameter. This const reference-object signals that it does not change. But due to SmartRange deriving from KTextEditor::Cursor, this object might also be a SmartCursor. So despite of being declared as a const object, it may change its position behind your back. This may lead to unexpected behaviours in e.g. loops where the line or column of the cursor is assumed to be constant. This is a problem in almost all functions in KatePart, so passing SmartCursors as Cursors is a very bad idea, but of course allowed.

Scaleability Problems

As mentioned above, you always need a Notifier or Watcher for all SmartCursors and SmartRanges. This will emit lots of signals you probably rarely need. Still, there is no way around it. This is a huge overhead, it simply does not scale.

Implementation

The implementation is really complex and rather undocumented in most areas. Unfortunately, only very few (or maybe even no one) really understand the code. Besides that, it is very fragile. If you change something, you might break the code. Hence, we often heard similar comments like “don’t touch the code, otherwise it will break”. Such comments alone already indicate that the current implementation is not maintainable. Unmaintainable code is bad, especially in open source projects (this is not a good way to gain new developers at all). There are other downsides of the current implementation: SmartCursors need a lot of memory for each instance; there are lots of features like animations for SmartRanges, which make the code even more complex; redundancy of watchers and notifiers bloat the code and influence the runtime behavior.

Threading

It seems there was the idea of making the KTextEditor interfaces thread safe. The SmartRanges interact with the content of the document, e.g. querying lines count and line length of lines in the text buffer. As this is done by other threads we need correct locking in all places: in document content changes, in smart cursors, in smart ranges, in the painting routines, etc. The current state in KatePart is that not all functions do the right locking. That’s why we have lots of asserts/crashs. KatePart already has more than 150 lockings at the moment, but they still do not voer problems. And very few developers (or no one?) really know when to lock and when not to. This is especially complex since we want to ensure that the locks are not hold while emitting signals or calling functions provided from the outside as callbacks, this is still not done completely right, too.

If you think about Qt, the GUI widgets are also not thread-safe, they all live in the main thread. And if you need data in other threads, you always use queued signals. This is pretty much what we experience in KatePart now. Not enough locking? Too much locking? In other words: It’s far too complex to make the KTextEditor interfaces thread-safe…

Threading and Revisions

Now to another issue in the implementation. KDevelop uses KTextEditor::Ranges in the parsing thread. Think of the following use case: KDevelop gets all the document text in the current revision (or version, if you prefer). Now it takes some time until the parsing is finished. When it’s finished, KDevelop uses the Ranges (which initially belong to the old revision of the document), and then translates those ranges to the current version of the text document (this is simple: Just track all edits inside KatePart (delete, insert, wrap, unwrap) and apply those transformations to the ranges). Now we transformed ranges from an old text revision to the current text revision. This means KDevelop does not have to parse everything again, as it knows exactly which parts changed. Awesome indeed :) However, now comes the problem: To transform Cursors and Ranges between revisions, you have to tell the SmartInterface about the revision you are working with. This is done via SmartInterface::useRevision(int). The API documentation says: Tell the smart interface to work against the given revision when creating cursors and ranges. That is, if you call useRevision() once, all succeeding calls like newSmartRange() etc are working in the past (at that revision). Also kind of cool, since useRevision() works locally in each thread. That is different threads don’t influence each other. But there is huge problem with this: Think of a KTextEditor user (KDevelop, Kile, …) uses useRevision() in the main thread. Then all succeding calls of e.g. newSmartRange() use an old revision instead of the current one. Hence, KatePart itself is completely broken in that case. (This case is now catched via a qFatal() in KatePart). But again this shows that multi-threading simply complicates the matter a lot. It would have been much easier to say transformRange(int fromRevision, int toRevision) instead of just one translateFromRevision() that translates the given range against the revision specified through useRevision(). Hence, the current API is unfortunately pretty much broken by design.

A necessary Step?

Often it’s hard to get things done right in the first try. So maybe the issues above are a necessary step to a much better implementation. And that is exactly what happened in the last two months: We have new interfaces called MovingInterface, MovingCursor and MovingRange. For feedback there is MovingRangeFeedback, which uses callbacks for notification (no signals by intention). All those classes have unit tests. KatePart in KDE 4.6 will not implement any Smart* interface anymore. Hence, KatePart will be completely single threaded again. We already started to remove several thousands of lines Smart* code that was never used/finished. Much more will follow immediately after the KDE 4.5 release. This means all developers using Smart* from the KTextEditor interfaces should migrate to the Moving* pendents for KDE 4.5 already, if possible.

I’ll provide very detailed information about the Moving* implementation in further blogs during the next month.

Kate XML Completion Plugin

A while ago I’ve blogged about the Kate’s most powerful plugins: the XML completion plugin. Thanks to Tomáš Trnka this plugin is back alive with all its features since today and compiled by default in trunk: It will be included in KDE 4.5. Yay! :) And if you need it already now for KDE 4.4, you can build Kate yourself very easily according to this howto, without touching your global KDE installation (probably interesting for KDE documentation writers!). Have fun!

Quick Compiling Kate in a stable KDE Environment

Since all of the Kate code is now co-hosted on gitorious, it became very easy to build Kate in your stable KDE >= 4.4 environment. This means you can run the newest version of Kate with very few effort. Just give it a try and do the following steps:

  1. make sure you have the following packages installed: git, cmake and kdelibs development package (on openSUSE this is git, cmake and libkde4-devel)
  2. create and change into a KDE development directory:
    mkdir ~/kde; cd ~/kde
  3. get a copy of the Kate code:
    git clone git://gitorious.org/kate/kate.git
  4. create and change into a build directory for compilation:
    mkdir build; cd build
  5. run the configure process with cmake:
    cmake ../kate -DCMAKE_BUILD_TYPE=debugfull \
    -DCMAKE_INSTALL_PREFIX=~/kde/usr
  6. compile Kate:
    make
  7. finally install Kate:
    make install

That’s all! This installs Kate locally into the separate directory ~/kde/usr, so that your global KDE installation will not be touched at all.

Now on to starting the newly compiled Kate. Create a file ~/kde/run.sh with the following content:

#!/bin/bashexport KATE_DIR=~/kde/usr
export PATH=$KATE_DIR/bin:$PATH
export LD_LIBRARY_PATH=$KATE_DIR/lib:$LD_LIBRARY_PATH
export KDEDIR=$KATE_DIR
export KDEDIRS=$KDEDIRexport XDG_DATA_DIRS=$XDG_DATA_DIRS:$KATE_DIR/share
# update KDE's system configuration cache
kbuildsycoca4
# start app
$@

Now you can run the compiled Kate version with ~/kde/run.sh kate. Just calling kate directly will start the system version of Kate.

Your copy of Kate contains all of the Kate code, i.e.: the KTextEditor interfaces, Kate Part, KWrite and the Kate Application. Feel free to send patches to our mailing list kwrite-devel@kde.org. And join #kate on irc.kde.org :-)

Note for KDE developers: All the changes in git are merged back to KDE’s subversion repository in a timely manner. So don’t worry about Kate moving away from KDE; this is not the case.

(Updated on 2010-04-17: Allow installation into local directory.)

Kate Internals: Text Buffer

Right now, in Kate’s gitorious repository we, or rather mainly Christoph, is rewriting the heart of Kate: The text buffer. In this blog I’ll explain how the text buffer works in detail. After reading, you’ll be able to understand the code. And be warned: Probably you will be desperately amazed about its awesomeness! So this is a must read :^)

Storage. Kate Part internally stores the content of a text document as a QVector of text lines. Each text line holds the text in a QString object as well as additional data like color attributes for highlighting. Inserting and removing text lines implies inserting or removing items in the QVector of the text lines. This is can get slow when there are thousands of text lines in a document, because QVector has to move the memory every time. Thus, to keep text operations fast, Kate splits all text lines into several text blocks: Each text block contains a certain amount of lines (e.g. 256). The expense of memory movement is then always limited. When a text block grows too much, it is automatically split. When the amount of lines in a text block shrinks, it is merged with the previous text block. In other words, Kate’s text buffer automatically balances the text blocks. The basic idea of Kate’s text buffer, the text blocks and text lines looks like this:

Text Cursors and Ranges. Text manipulation always takes place at certain positions in a text. Such a position is called a text cursor. Each text cursor is defined by a line and a column. Further, to specify e.g. text selections, we need text ranges. A text range has a start cursor and an end cursor. For instance, if you have two views of the same document, you want the cursor in the 2nd view to keep its correct position in the text if you insert text in the 1st view. Thus, text cursors have to be kind of intelligent. Whenever text changes, each text cursor needs to be moved. As a text range just consists of two text cursors, we will focus only on text cursors for now. The question is how to implement this efficiently? If we store all text cursors in a list in the text buffer, we have to iterate over all text cursors on every editing operation. This obviously does not scale for thousands of text cursors (KDevelop for instance uses thousands of text cursors for the highlighting). The solution is the same as with the text content itself: Let’s just put a list of all text cursors in a text block to the text block itself. During text editing, we only have to adapt all text cursors of a single text block instead of all text cursors in a document. This looks as follows:

Editing Operations. When editing text, you usually have only four different types of text manipulation:

  1. insert text at a text cursor inside one line
  2. remove text at a text cursor inside one line
  3. wrap line at a text cursor (e.g. by hitting <return/enter>)
  4. unwrap line (e.g. by hitting <backspace> at the beginning of a line)

Those editing primitives are implemented in the Kate::TextBuffer and Kate::TextBlock and take care of balancing the text blocks. For each one the set of text cursors in a text block has to be adapted. Of course, corner cases need to be taken into account: For instance, unwrapping the first line in a text block means that cursors in the first line need to be moved to the previous text block. All editing primitives emit a signal, such that additional layers later can track what happens. For instance, the undo/redo system needs to know all editing operations. Or on top of that we could implement vim like swap file support, i.e., track all changes from the beginning, and if Kate crashes, replay all the editing primitives on the original files.

Transactions. A transaction consists of several of the four editing operations. For instance, if you select text and then move it to another location in the document with drag & drop you want this to be grouped together to a single text operation on undo/redo. (The text operations in this case are unwrap lines and remove text, then insert text and wrap line). To be able to specify which parts of text operations belong together, the text buffer provides two functions: startEditing() starts a transaction and finishEditing() closes it. Those functions use reference counting, so you can call startEditing() multiple times and only the last finishEditing() completes a transaction. Again, signals are emitted e.g. in finishEditing() such that other layers (undo/redo system) are notified about this.

Revisions. As an easy way to check whether a document changed you can get the current revision of the text buffer. The revision is simply an int64 starting with 0 after loading a document. The revision is incremented in every of the 4 editing primitives. This way you don’t have to listen to multiple signals like textInserted() and textRemoved(). This could be useful for e.g. KDevelop’s background parser: Parse a file. When done, check whether the revision is the same. If yes, parsing was successful. If not, parse again. This is how QtCreator does it. Easy and straight forward.

Unit Tests. The text buffer, text block, text line, text cursors are all designed such that unit tests can be written. Hence, each of the above implementation details will be covered by unit tests.

Further ideas. As mentioned previously, the design of the text buffer leaves room for further features. For now, let’s face two ideas:

  • Vim like swap files: If Kate or the application using Kate Part crashes, the unsaved data in the text document is lost. Vim has a backup mechanism called ‘swap files’ to recover from crashes to avoid data loss since years, and it works as follows: Each of the 4 editing primitives explained above are streamed into a separate file. When you save a file successfully, the swap file is cleared. If a crash happens, vim loads the original text document and then replays all editing operations in the swap file. This way the current unsaved editing state is recovered; no data is lost. Since the text buffer emits all necessary signals, implementing this feature is kind of trivial. Any takers?
  • Right now, the highlighting works in an extra class, accessing the text buffer’s text lines and setting the text attributes such as the color. As idea, we could also derive a class Kate::HighlightingBuffer from Kate::TextBuffer and do the additional modifications in the derived class instead of an extra class. We’ll have to see whether that makes sense.

Please Contribute.