Introduction

For one of my projects, I have felt the need of a text editor with syntax highlighting. At first, I used a component inherited from RichTextBox, but while using it for a large amount of text I found out that RichTextBox highlights very slowly a large number of colored fragments (from 200 and more). When such highlighting has to be made in a dynamic way, it causes a serious problem.

The rendering of a text was made completely only by the means of GDI+.

The component works fast enough with a large amount of text and also possesses tools to make comfortably dynamic syntax highlighting.

It has such settings as foreground color, font style, background color which can be adjusted for arbitrarily selected text symbols. One can easily gain access to a text with the use of regular expressions. WordWrap, Find/Replace, Code folding and multilevel Undo/Redo are supported as well.

Implementation

For storage of characters of text, structure Char is used:

publicstruct Char
{
publicchar c;
public StyleIndex style;
}

The structure keeps the symbol (char, 2 bytes) and style index mask (StyleIndex, 2 bytes). Thus, on each character of text consumes 4 bytes of memory. Symbols are grouped into lines, which are implemented using List<Char>.

StyleIndex is mask of styles indices, applied to this character. Each bit of StyleIndex means that this symbol will be drawn by appropriate style. Because StyleIndex has 16 bits, control supports no more than 16 different styles.

Styles are stored in a separate list:

publicreadonly Style[] Styles = new Style[sizeof(ushort)*8];

In fact, Style is renderer of chars, backgrounds, borders and other design elements of the text.
Below is a typical implementation of one of the styles for rendering text characters:

TextStyle contains foreground color, background color and font style of the text. When creating a new style, component checks style on its list, and if there is no style, it creates a new style, with its index.

You can create custom styles, inherited from Style class.

To work with fragments of text, the class Range was used, representing a continuous block of text, given the initial and final positions:

Using the Code

Syntax highlighting

Unlike RichTextBox, the component does not use RTF. The information about the color and type of symbols is kept only in the component. It means that the coloring of the component has to be redone every time when entering text. In this case, the event TextChanged is applied.

A Range object which contains the information about modified text range pass into the event TextChanged. It permits the highlighting of the altered text fragment only.

For the search of fragments of text which need to be colored, it is possible to employ overloaded method Range.SetStyle() which accepts search pattern (regular expression). For example, the following code can be used for the search and coloring of the comments of C# code (the part of the line starting from two forward slashes):

Before beginning the coloring call, the method Range.ClearStyle() is used to clean out and delete the previous style.

The method SetStyle() highlights the text fragment corresponding to a regular expression. However, if the expression includes the named group "range", the group with a name "range" is highlighted. The name of the class which comes after the key words "class", "struct" and "enum" was bolded in the following example:

The event handler TextChanged utilized for coloring C#, VB, HTML and other languages syntax was implemented in demo application.

Apart from the event TextChanged the events TextChanging, VisibleRangeChanged and SelectionChanged may happen to be useful. The event TextChanging appears before the text starts to be modified. The event SelectionChanged occurs after the change of the cursor position in the component or while a selected fragment of text is being modified.

Code Folding

Control allows to hide blocks of text. To hide the selected text, use method CollapseBlock():

The component supports automatic search for fragments of collapse (folding area). To set the pattern (Regex) to find the beginning and end of folding block, use method Range.SetFoldingMarkers() in TextChanged handler.

For example, to search of blocks {..} and #region .. #endregion, use next handler:

Collapsed block can be opened by doubleclick on it, or click on marker '+'. Single click on folded area selects hidden block. Also, you can open hidden block programmatically by ExpandBlock() method.

Demo application contains sample for collapse all #region...#endregionblocks of the text.

In addition to hiding the text, folding blocks help visually define the boundaries of the block where the caret is located. For this purpose, the left side of the control draws a vertical line (folding indicator). It shows the beginning and end of the current folding block, in which the caret is located.

Custom Code Folding

Code folding using regex does not always bring the desired result.

For more specific cases - you can directly set the start and end markers blocks: Line.FoldingStartMarker and Line.FoldingEndMarker.

FoldingStartMarker and FoldingEndMarker must be identical for folding block.

The FCTB supports nested blocks with same FoldingMarker name.

Also it supports two strategies of finding of blocks, use property FindEndOfFoldingBlockStrategy.

More complex example of custom code folding see in CustomFoldingSample.

Delayed Handlers

Many events (TextChanged, SelectionChanged, VisibleRangeChanged) have a pending version of the event. A deferred event is triggered after a certain time after the occurrence of major events.

What does this mean? If the user enters text quickly, then the TextChanged is triggered when you enter each character. And event TextChangedDelayed works only after the user has stopped typing. And only once.

It is useful for lazy highlighting of large text.

Control supports next delayed events: TextChangedDelayed, SelectionChangedDelayed, VisibleRangeChangedDelayed. Properties DelayedEventsInterval and DelayedTextChangedInterval contain time of pending.

Export to HTML

Control has property Html. It returns HTML version of colored text. Also you can use ExportToHTML class for more flexibility of export to HTML. You can use export to HTML for printing of the text, or for coloring of the code of your web-site.

Clipboard

Control copies the text in two formats - Plain text and HTML.
If the target application supports inserting HTML (e.g. Microsoft Word) then will be inserted colored text. Otherwise (such as Notepad) will be inserted plain text.

Note: You can change hotkey mapping. Use Hotkeys property in design mode and HotkeysMapping in runtime.

Brackets Highlighting

Control has built-in brackets highlighting. Simply set properties LeftBracket and RightBracket. If you want to disable brackets highlighting, set it to '\x0'. For adjust color of highlighting, use property BracketsStyle. For adjusting of time of pending of highlighting, change DelayedEventsInterval.

Interactive Styles

You can create own interactive(clickable) styles. To do this, derive your class from the Style, and call AddVisualMarker() from your overridden Draw() method. To handle a click on the marker, use eventsFastColoredTextBox.VisualMarkerClick or Style.VisualMarkerClick or override method Style.OnVisualMarkerClick().
Also you can use built-in style ShortcutStyle. This class draws little clickable rectangle under last char of range.

Styles Priority

Each char can contain up to 16 different styles. Therefore, the matter in which order these styles will be drawn. To explicitly specify the order of drawing, use the method FastColoredTextBox.AddStyle():

This methods must be called before any calls of Range.SetStyle(). Otherwise, the draw order will be determined by the order of calls of methods Range.SetStyle().

Note: By default, control draws only one TextStyle(or inherited) style - undermost from all. However, you can enable the drawing of the symbol in many TextStyle, using the property FastColoredTextBox.AllowSeveralTextStyleDrawing. This applies only to TextStyle(or inherited) styles, other styles(inherited from Style) are drawn in any case.

If char has not any TextStyle, it will drawing by FastColoredTextBox.DefaultStyle. DefaultStyle draws over all other styles.

Call method ClearStyleBuffer() if you need reset order of drawing.

Also, to adjust the look of text, you can apply a semitransparent color in your styles.

Built-in highlighter

Property HighlightingRangeType specifies which part of the text will be highlighted as you type (by built-in highlighter). Value ChangedRange provides better performance. Values VisibleRange and AllTextRange - provides a more accurate highlighting (including multiline comments), but with the loss of performance.

Multiline Comments Highlighting

If your custom language supports multiline comments or other multiline operators, you may encounter a problem highlighting such operators. Property ChangedRange from TextChanged event contains only changed range of the text. But multiline comments is larger than ChangedRange, from what follows incorrect highlighting. You can solve this problem in the following way: In TextChanged handler use VisibleRange or Range property of FastColoredTextBox instead of ChangedRange. For example:

<style name="..." color="..." backColor="..." fontStyle="..." /> - sets the style called name. Tag <style> creates only styles of type TextStyle. color and backColor determine foreground and background color. Allowed as a string color name or hex representation of the form #RGB or #ARGB. fontStyle - enumeration of FontStyle parameters.
The sequence tags <style> determines the order of rendering these styles.

<folding start="..." finish="..." options="..."> - specifies the rules for folding. start and finish set regular expressions to the beginning and end of the block. options - enumeration of RegexOptions parameters.

Backward/Forward Navigation

Control remembers lines in which the user has visited. You can return to the previous location by pressing [Ctrl + -] or call NavigateBackward(). Similarly, [Ctrl + Shift + -] and the method NavigateForward().
You can also use the property Line.LastVisit for information on the last visit to this line. In the example PowerfulCSharpEditor this property is used for navigation in multitab mode, when you have multiple documents open.

Hieroglyphs and Wide Characters

Control supports input and display hieroglyphs and other wide characters (CJK languages, Arabic and other). Also Input Method Editor (IME) is supported.
To enable this feature to switch the property ImeMode to the state On.
Demo contains a sample of IME features usage.Note: For a normal display wide characters may require a larger font size.Note: Enabled IME mode can decrease performance of control.

Autocomplete

The library has class AutocompleteMenu to implement autocompleting functionality (like IntelliSense popup menu).AutocompleteMenu contains a list of AutocompleteItem. It supports displaying, filtering and inserting of items.
You can use AutocompleteMenu for code snippets, keywords, methods and properties hints.

Notice to such properties and methods as Fragment, SearchPattern, MinFragmentLength, Items.ImageList, Items.SetAutocompleteItems().

You can override class AutocompleteItem for more flexibility of functionality.

Also, you can use more advanced control AutocompleteMenu[^]. AutocompleteMenu is fully compatible with FastColoredTextBox.

See AutocompleteSample and AutocompleteSample2 for more information.

AutoIndent

When the user is typing, and enabled AutoIndent, control automatically determines the left indent for the input string.

By default, the indentation made on the markers of Code Folding. But, if you select a specific Language, then padding makes integrated highlighter for appropriate language.

You can set their own rules for indentation.
Use the event AutoIndentNeeded.
The handler must return two values: Shift and ShiftNextLines.

Shift value indicates indention of this line relative to the previous line (in characters, can be negative). ShiftNextLines value indicates a indent will be applied to the subsequent lines after this.

Also, you can manually make AutoIndent of selected text. Simply call DoAutoIndent() method. The below picture shows text before calling:

And this is picture after DoAutoIndent() calling:

AutoIndentChars

The control can align the position of the individual characters in adjacent lines. This functionality is called AutoIndentChars.

An example is shown below:

The property AutoIndentCharsPatterns contains a set of patterns (in each line - one pattern).

When the user types a character, line is checked against each pattern. If the string suitable for some of the patterns are checked near lines (the left indent these lines must match the indentation of the current line). And then these symbols of found lines are aligned by inserting whitespaces. Which symbols will be aligned is defined by named groups of regex, name of group must be "range". See example:

In this example will be aligned symbol "=" and text after its.

To deactivate AutoIndentChars set property AutoIndentChars to false (by default it is true). Built-in syntax highlighter already contains needed patterns for AutoIndentChars, so you shouldn't to define it.

You can make AutoIndentChars programmatically by method DoAutoIndentChars(). Also, user can call AutoIndentChars by hotkey Ctrl+I (It will worked even if AutoIndentChars = false).

Custom AutoIndentChars see in example AutoIndentCharsSample.

Printing

The component has built-in printing functionality. For print all text or selected range, call method Print(). You can specify which dialog boxes to show, by passing the object PrintDialogSettings. By default, the method makes printing of all text, without dialog boxes.

Lazy file loading

To work with files of extremally large size (100k lines and more) can be a useful "lazy" mode. In this mode, the control opens the file and reads its parts, as needed. Unused pieces of text are deleted from memory. This mode is supported by three methods:OpenBindingFile() - Opens file for reading (in exclusive mode).CloseBindingFile() - Closes opened file. After call the control returns to simple (non lazy) mode.SaveToFile() - Saves text to the file. After this method, control will be binded to new file. This method can be used in simple (non lazy) mode too.

Split-screen mode

The control has property SourceTextBox of type FastColoredTextBox. If you set this property to another textbox, both the controls will display the same text. This mode can be used to split the screen, where you can edit the same text in different places.

Column selection mode

The component supports column selection mode. When its activated, user can work with vertical fragment of the text:

To enable column selection mode, press Alt key and select area by mouse. Another way - press Alt+Shift and select area by arrow keys. To switch off this mode simply click on the control or press any arrow key.

ToolTips

The component supports tooltips. For this you need to handle event ToolTipNeeded and pass parameters for tooltip. ToolTipNeeded is fired when user moves mouse over text. After the mouse is stopped, after ToolTipDelay ms ToolTipNeeded will be called and will be showed popup tooltip.

Hints

The component supports built-in hints. This is powerful feature allows you to insert hints into text.

The hint can contain simple text or arbitrary control. The hint is linked to range and move with it when user scrolls textbox. If user presses Esc or changes text all hints are removing.

Class Hint has several modes of displaying. Property Dock allows to place hint on whole line. If property Inline is True, hint will be built in the text, otherwise the hint will be located over text.

Also you can adjust colors of background, borders, fonts etc. If user clicks on the hint, event HintClick will be called. To create and show hint use collection Hints. Also the FastColoredTextBox has auxiliary methods to add and remove hints: AddHint() and ClearHints().

Call method Hint.DoVisible() to scroll textbox to the hint.

Macro recording

The FCTB supports recording of macros. When recording is activated, FCTB remembers all pressed keys. Later you can execute recorded sequence.

Macros remember any entered symbols and control keys (include Ctrl-C, Ctrl-V, Home, End, etc). But macros do not support selection by mouse.

Dynamic highlighting sample. Shows how to make dynamic syntax highlighting. This example finds the functions declared in the program and dynamically highlights all of their entry into the code of LISP.

Performance

For storing one megabyte of text requires approximately 6 MB of RAM (include undo/redo stack objects). The coloring does not consume significant resources.

The use of regular expressions and saving memory usage, allow to reach high performance component. I tested the file of 50,000 lines (about 1.6 MB) of C# code. The total time of insertion, and the syntax coloring was about 3 seconds. Further work with the text passed without significant delays.

Restrictions

The component does not support center or right alignment and uses only monospaced fonts. Also tabs are always replaced by spaces.

03 Nov 2012 - Added properties: AutoIndentExistingLines (You can disable autoindenting for existing lines), VirtualSpace (In VirtualSpace mode user can set cursor into any position of textbox), FindEndOfFoldingBlockStrategy (You can adjust strategy of code folding). Added line selection mode(When user drags mouse over left margin of textbox, corresponding lines will be selected). Fixed some bugs with scrolling. Added HyperlinkSample.

23 Jan 2013 - The control supports built-in Bookmarks, Tooltips and Hints. Event OnPaste was added. Fixed bugs with localization and binding to the Text property. AutocompleteMenu appearing was improved. Also behaviour of methods IncreaseIndent and DecreaseIndent was improved. Now you can change color scheme of built-in syntax highlighter (see properties KeywordStyle, StringStyle, etc.). Useful method GetStylesOfChar() was added. SQL highlighting was extended. Some samples was improved. Sample HintSample was added. Drag&drop is available now.

14 Feb 2013 - The control supports readonly blocks, zooming. Samples ReadOnlyBlocksSample and PredefinedStylesSample were added. Source codes of the component are available on GitHub now.

I will be brief:
using FTCB with autocomplete menu I would like to eliminate these behaviors:

1) When you enter a menu item and then press CTRL + Z shows the formula of the original menu with (^).
2) When you're just after a character from the menu as expected pattern (ex .: "#") and press space even if it follows an instruction, the menu item is inserted into the same.

Print Preview... this is a standard feature of many applications to allow you to see how the content will appear when printed. Taking into account margins, headers and footers of the print layout etc. Not sure how else to describe it. Adrian

1. I would like to understand the LGPL licence:
- does it mean that I can use only your version in my app? or can I make my own modified version (e.g. supporting tabs because makefile requieres them)?

2. Do you plan to support tabs in the future?
- I have created my own branch while trying to be non-invasive and instead of rewriting the core, I have replaced all tabs with "___\t" (spaces and one tab). That won't mess the painting and syntax regex, but Range.GoRight() and such needed modification to mimic the moving around tabs and SaveToFile must replace all "___\t" back with single tabs.

Hi Pavel, thanks.
The problem is that, in the text, I would like to find the start point and end of the word (or sequence of words) to be able to draw the rectangle.
That is: if the word part in paragraph 50 and goes up to the point 123, I would find this location within the row and use it for the automatic design of the rectangle in question on multiline text.

Example: 'My name is Francis.'
I find 'nime' instead of 'name' and 'Fracens' instead of 'Francis' and would like to make them visible.

The problem is not the design, but the coordinates on the X axis of the word inside the line.
Thanks 1000.

Hello Pavel,
I appreciated the speed of response.
Actually I have to search only a few reserved keywords (less than 10) and, having written all the validation engine and the calculation function of the distance, I'd like to use them as they are, with something more simply (since the characters are monospaced ) can perform calculations directly on the position in the line.
Obviously I can only thank you for your time ... do this for you but if you can spend more time, I would do an even greater pleasure.