Fast Colored TextBox for syntax highlighting

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 RichTextBoх, but while using it for a large amount of text I found out that RichTextBoх 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.
Therefore I created my own text component which uses neither Windows TextBox nor RichTextBoх.
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.
Some properties and methods
FastColoredTextBox
class:
-
ClearStyle()
– Clears styles of the text -
CollapseBlock(int fromLine, int toLine)
,ExpandBlock(int fromLine, int toLine)
,ExpandBlock(int iLine)
- Collapse/Expand text block -
CollapseFoldingBlock(int iLine)
,ExpandFoldedBlock(int iLine)
- Collapse/Expand folding block -
Copy()
,Cut()
,Paste()
,ClearSelected()
,SelectAll()
– Standard text operations with selected text -
ClearUndo()
– Clears undo/redo stack -
DoCaretVisible()
– Scrolls control for display caret -
DoSelectionVisible()
- Scrolls control for display selection area -
- Style of folded blockFoldedBlockStyle
-
FoldingIndicatorColor
- Color of folding indicator (left vertical line between folding bounds) -
GetLine(int iLine)
– Return Range for given line index -
GetLineLength(int iLine)
– Returns chars count of given line index -
GetLineText(int iLine)
– Returns text for given line index -
GetRanges(string regexPattern)
,GetRanges(int fromLine, int toLine, string regexPattern)
– Returns Range of text for given regular expression. -
GoEnd()
,GoHome()
– Moves caret to first/last position. -
HighlightFoldingIndicator
- Enables folding indicator (left vertical line between folding bounds) -
IncreaseIndent()
,DecreaseIndent()
- Shifts selected block to left/right -
IndentBackColor
- Color of indent area -
InsertText(string text)
– Inserts text into current position. -
IsChanged
– Text was changed (user may set this property into false, after saving text into file) -
LeftIndent
- Left indent (in pixels) -
LinesCount
– Count of lines (without regard for wordwrap effect) -
Point
PlaceToPoint(Place place)
- Gets point for given line and char position -
int
PlaceToPosition(Place point)
- Gets absolute text position from line and char position -
Place
PointToPlace(Point point)
- Gets nearest line and char position from point -
int
PointToPosition(Point point)
- Gets nearest absolute text position for given point -
Place
PositionToPlace(int pos)
- Gets line and char position from absolute text position -
Undo()
,Redo()
,RedoEnabled
,UndoEnabled
– Standard undo/redo functions -
SelectedText
- Text of current selected range -
Selection
– Current selected range of text -
SelectionColor
– Background color for selection area. Alpha component of color must be less than 200. Otherwise alpha will be assigned to 50 forcedly. -
SelectionLength
,SelectionStart
– Standard (like RichTextBoх) properties of current selected text -
ServiceLinesColor
- Color of auxiliary lines -
ShowLineNumber
- Shows line number in left indent -
ShowFindDialog()
- Shows Find dialog form -
ShowReplaceDialog()
- Shows Replace dialog form -
Text
– All text (asstring
). This property includes Environment.NewLine chars as line separator -
TextVersion
- This counter is incremented each time changes the text -
WordWrap
– Enables/Disables WordWrap mode. -
WordWrapLinesCount
- Count of lines (include wordwrap effect) -
event
TextChanged
,TextChangedDelayed
- It occurs after insert, delete, clear, undo and redo operations -
event
VisibleRangeChanged
,VisibleRangeChangedDelayed
- It occurs after change of visible range of the text -
event
TextChanging
- It occurs before insert, delete, clear, undo and redo operations -
event
SelectionChanged
,SelectionChangedDelayed
- It occurs after change of selection
Range
class:
-
CharAfterStart
,CharBeforeStart
– char before/after start of selected area (include \n char) -
ClearStyle()
- Clears styles of range -
ClearFoldingMarkers()
- Clears folding markers of all lines of range -
Clone()
- Clones range -
Contains(Place place)
– Is range contains given char and line position? -
GetRanges(string regexPattern)
– Returns fragments of this Range object matched to given regular expression pattern. -
GoLeft()
,GoRight()
– Moves Start and End positions left/right (after this methods positions of Start and End will be equal). Returns false, if moving is impossible -
Normalize()
- Exchanges Start and End if End appears before Start -
Start
,End
– Start and End of range object. These properties are of type Place, which is represents line index and index of the character in the line -
SelectAll()
- Selects all chars of text and assign range to this object -
SetFoldingMarkers(string startFoldingPattern, string finishFoldingPattern)
- Sets folding markers -
SetStyle(Style style)
– Sets style for all chars of this Range object. -
SetStyle(Style style, string regexPattern)
– Sets style for fragments of this Range object matched to given regular expression. -
Text
– Returns text of this Range object
Implementation
For storage of characters of text, structure Char
is used:
public struct Char
{
public char 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:
public readonly 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:
public class TextStyle : Style
{
public Brush ForeBrush { get; set; }
public Brush BackgroundBrush { get; set; }
public FontStyle FontStyle { get; set; }
public override void Draw(Graphics gr, Point position, Range range)
{
//draw background
if (BackgroundBrush != null)
gr.FillRectangle(BackgroundBrush, position.X, position.Y, (range.End.iChar -
range.Start.iChar) * range.tb.CharWidth, range.tb.CharHeight);
//draw chars
Font f = new Font(range.tb.Font, FontStyle);
Line line = range.tb[range.Start.iLine];
float dx = range.tb.CharWidth;
float y = position.Y - 2f;
float x = position.X - 2f;
Brush foreBrush = this.ForeBrush ?? new SolidBrush(range.tb.ForeColor);
for (int i = range.Start.iChar; i < range.End.iChar; i++)
{
//draw char
gr.DrawString(line[i].c.ToString(), f, foreBrush, x, y);
x += dx;
}
}
}
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, was used the class Range
, representing a continuous block of text, given the initial and final positions:
public class Range
{
Place start;
Place end;
}
public struct Place
{
int iLine;
int iChar;
}
Using the code
Syntax highlighting
Unlike RichTextBoх, 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):
Style GreenStyle = new TextStyle(Brushes.Green, null, FontStyle.Italic);
...
private void fastColoredTextBox1_TextChanged(object sender, TextChangedEventArgs e)
{
//clear style of changed range
e.ChangedRange.ClearStyle(GreenStyle);
//comment highlighting
e.ChangedRange.SetStyle(GreenStyle, @"//.*$", RegexOptions.Multiline);
}
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:
e.ChangedRange.SetStyle(BoldStyle, @"\b(class|struct|enum)\s+(?<range>[\w_]+?)\b");
The event handler TextChanged
utilized for coloring C#, VB and HTML 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()
:
fastColoredTextBox1.CollapseBlock(fastColoredTextBox1.Selection.Start.iLine,
fastColoredTextBox1.Selection.End.iLine);
The result is shown in the picture:
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:
private void fastColoredTextBox1_TextChanged(object sender, TextChangedEventArgs e)
{
//clear style of changed range
e.ChangedRange.ClearFoldingMarkers();
//set folding markers
e.ChangedRange.SetFoldingMarkers("{", "}");
e.ChangedRange.SetFoldingMarkers(@"#region\b", @"#endregion\b");
}
The result is shown in the picture:
Folding blocks can be nested into each other.
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...#endregion
blocks 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.
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
work 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.
Samples
Demo application has many samples. Below is a brief description:
-
Powerful sample. Contains many features: syntax highlighting, code folding, autocomplete, export, same words highlighting and other.
- Simplest syntax highlighting sample. Shows how to make simplest syntax highlighting.
- Marker sample. Shows how to make marker tool. Sample uses class
ShortcutStyle
for create clickable markers on text area:
-
Custom style sample. This example shows how to create own custom style. Next custom style draws frame around of the words:
class EllipseStyle : Style { public override void Draw(Graphics gr, Point position, Range range) { //get size of rectangle Size size = GetSizeOfRange(range); //create rectangle Rectangle rect = new Rectangle(position, size); //inflate it rect.Inflate(2, 2); //get rounded rectangle var path = GetRoundedRectangle(rect, 7); //draw rounded rectangle gr.DrawPath(Pens.Red, path); } }
- VisibleRangeChangedDelayed usage sample. This example shows how to highlight syntax for extremally large text by
VisibleRangeChangedDelayed
event.
- Simplest code folding sample. This example shows how to make simplest code folding.
- Autocomplete sample. This example shows how to create autocomplete functionality. Also implemented highlighting of same words:
- Brackets highlighting sample. This example shows how to highlight brackets.
- Joke sample :) Some additional features. Implemented custom
TextStyle
:
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 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, automatical Drag&Drop and uses only monospaced fonts.
History
-
26 Feb 2011 - Added Find/Replace functionality, indent stuffs, showing of line numbers. Also added VB syntax highlighting.
-
28 Feb 2011 - Added code folding functionality (include current block highlighting). Added some features (caret blinking, increase/decrease indent of selected text). Optimized ReplaceAll functionality. Added HTML syntax highlighting. Increased general performance of control.
-
2 Mar 2011 - Style logic was revised. Added Html export. Added many samples. Added many, many features... :)