For projects where I need some form of syntax highlighting, I tend to use the open source DigitalRune Text Editor Control which is a modified version of the text editor used in SharpDevelop. While it has a number of syntax definitions built in, the one it didn't have was for CSS formatting.
After doing a quick search on the internet and finding pretty much nothing, I created my own. This article describes that process, along with how to embed the definition directly in a custom version of the control, or loading it into the vendor supplied control.
Creating the Rule Set
Each definition is an XML document which contains various sections describing how to syntax highlight a document. An XSD schema is available, named Mode.xsd and located in the /Resources directory in the control's source code.
Here's an example of an (almost) empty definition - I've filled in the definition name and the list of file extensions it will support:
="1.0" ="utf-8"
<SyntaxDefinition name="CSS" extensions="*.css">
<RuleSets>
</RuleSets>
</SyntaxDefinition>
The RuleSets
element contains one of more RuleSet
elements which in turn describe formatting. I'm not sure how the control decides to process these, but in my example I started with an unnamed ruleset which references a named ruleset
, and in turn that references another - seems to work fine.
There are two key constructs we'll be using for highlighting - first is span
highlighting, where a block of text which starts and ends with given symbols is highlighted. The second is keywords
, where distinct words are highlighted. From having a quick look through the source code to figure out problems, there appears to be one or two other constructs available, but I'll ignore these for now.
First, I need to add a rule for comments, which should be quite straight forward - look for a /* and end with /*:
<RuleSet ignorecase="false">
<Span name="Comment" bold="false"
italic="false" color="Green" stopateol="false">
<Begin>/*</Begin>
<End>*/</End>
</Span>
</RuleSet>
The Span
tag creates a span
highlighting construct. The Begin
and End
tags describe the phrase that marks the beginning and end of the text to match. The stopateol
attribute determines if the line breaks should stop at the end of a line. The formatting properties should be evident!
Next, I added another span
rule to process the highlighting of the actual CSS rules - so anything between {
and }
.
<Span name="CssClass" rule="CssClass" bold="false" italic="false" color="Black" stopateol="false">
<Begin>{</Begin>
<End>}</End>
</Span>
Note this time the rule
attribute - this is pointing to a new ruleset (more on that below). Without this attribute, I found that I was unable to style keywords and values inside the CSS rule, as the span
above always took precedence. The new ruleset looks similar to this, although in this example I have stripped out most of the CSS property names. (The list of which came from w3schools.)
<RuleSet name="CssClass" ignorecase="true">
<Span name="Value" rule="ValueRules"
bold="false" italic="false" color="Blue" stopateol="false">
<Begin color="Black">:</Begin>
<End color="Black">;</End>
</Span>
<KeyWords name="CSSLevel1PropertyNames"
bold="false" italic="false" color="Red">
<Key word="background" />
<Key word="background-attachment" />
(snip)
</KeyWords>
<KeyWords name="CSSLevel2PropertyNames"
bold="false" italic="false" color="Red">
<Key word="border-collapse" />
<Key word="border-spacing" />
(snip)
</KeyWords>
<KeyWords name="CSSLevel3PropertyNames"
bold="false" italic="false" color="Red">
<Key word="@font-face" />
<Key word="@keyframes" />
(snip)
</KeyWords>
</RuleSet>
First is a new span
to highlight attribute values (found between the :
and ;
characters in blue, and then 3 sets of a new construct - KeyWords
. This basically matches a given word and formats it appropriately. In this example, I have split each of the 3 major CSS versions into separate sections, on the off chance you want to reconfigure the file to only support a subset, for example CSS1 and CSS2. Also note that I haven't included any vendor prefixes.
One thing to note, in the Value span
above, the begin
and end
tags have color
attributes. This overrides the overall span
color (blue
) and colors those individual colors with the override (black
). Again, from checking the scheme, it looks like this can be done for most elements, and supports the color
, bold
and italic
attributes, plus a bgcolor
attribute that I haven't used yet.
The span
in the above ruleset
references a final ruleset
, as follows:
<RuleSet name="ValueRules" ignorecase="false">
<Span name="Comment" bold="false"
italic="false" color="Green" stopateol="false">
<Begin>/*</Begin>
<End>*/</End>
</Span>
<<pan name="String" bold="false"
italic="false" color="BlueViolet" stopateol="true">
<Begin>"</Begin>
<End>"</End>
</Span>
<Span name="Char" bold="false"
italic="false" color="BlueViolet" stopateol="true">
<Begin>'</Begin>
<End>'</End>
</Span>
<KeyWords name="Flags" bold="true"
italic="false" color="BlueViolet">
<Key word="!important" />
</KeyWords>
</RuleSet>
This ruleset
has 3 span
s, and one keyword. I had to duplicate the comment span
from the first ruleset
, I couldn't comment highlighting to work inside { }
blocks otherwise - probably some subtlety of the definition format that I'm missing. This is followed by two span
s which highlight string
s (depending on whether single or double quoted). Finally, we have a keyword rule for formatting !important
. (Of course, ideally, you wouldn't be using this keyword at all, but you never know!)
Put together, this definition nicely highlights CSS. Except for one thing - everything outside a comment or style block is black
. And I want it to be something else! Initially, I tried just setting the ForeColor
property of the control itself, but this was blatantly ignored when it drew itself. Fortunately a scan of the schema gave the answer - you can add an Environment
tag and set up a large bunch of colors. Or one, in this case.
<Environment>
<Default color="Maroon" bgcolor="White" />
</Environment>
Now, save the file somewhere with the .xshd extension - in keeping with the convention of the existing definitions, I named it CSS-Mode.xshd.
Loading the Definition into the Text Editor Control
This is where I was a little bit stumped - as I didn't have a clue how to get the definition in. Fortunately, DigitalRune's technical support were able to help.
If you are using a custom version of the source code, you can add the definition directly into the source and have it available with the compiled assembly. However, if you are using the vendor supplied assembly, you'll need to include the definition with your application in order to load it in.
Compiling the Definition into the Assembly
This is quite straight forward, and easily recommended if you have a custom version.
- Copy the definition file into the Resources folder of the control's project
- Set the Build Action to be Embedded Resource
- Open SyntaxModes.xml located in the same folder and add a
mode
tag which points to your definition, for example:
<Mode file="CSS-Mode.xshd" name="CSS" extensions=".css"/>
While I haven't checked to see if it is enforced, common sense would suggest you ensure the name
and extensions
attributes match in both the syntax definition and the ruleset definition.
- Compile the solution.
With that done, your definition is now available for use!
Loading the Definitions Externally
You don't need to compile the definitions into the control assembly, but can load them externally. To do this, you need to have the definition file and the syntax mode file available for loading.
- Add a new folder to your project and copy into this folder your .xshd file and set the Copy to Output Directory property to Copy always.
- Create a file named SyntaxMode.xml in the folder, and paste in the definition below. You'll also need to set the copy to output directory attribute.
="1.0" ="utf-8"
<SyntaxModes version="1.0">
<Mode file="CSS-Mode.xshd" name="CSS" extensions=".css" />
</SyntaxModes>
- The following line of code will load the definition file into the text editor control:
HighlightingManager.Manager.AddSyntaxModeFileProvider(new FileSyntaxModeProvider(definitionsFolder));
Setting up the Text Editor Control
To instruct instances of the Text Editor control to use CSS syntax highlighting, add the following line of code to your application (replacing CSS with the name of your definition if you called it something different):
textEditorControl.Document.HighlightingStrategy =
HighlightingManager.Manager.FindHighlighter("CSS");
Syntax Highlighting Isn't Appearing, What Went Wrong?
Rather frustratingly, the control doesn't raise an error if a definition file is invalid, it just silently ignores it and uses a default highlighting scheme. Use the source code for the control so you can catch the exceptions being raised by the HighlightingDefinitionParser
class in order to determine any problems. Remember the definition you create is implicitly linked to the schema and so must conform to it.
SharpDevelop?
As the DigitalRune control is derived from the original SharpDevelop editing component, I believe this article and sample code will work in exactly the same way for the SharpDevelop control. However, I don't have this installed and so this remains untested - let me know if it works for you!
Sample Application
The download available above includes the CSS definition file and a sample application which will load in the definition files. Note that no binaries are included in the archive, you'll need to add a reference to a copy of the DigitalRune Text Editor control installed on your own system.