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.

A sample application demonstrating CSS syntax highlighting
A sample application demonstrating CSS syntax highlighting

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:

xml
<?xml version="1.0" encoding="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 rule which references a named rule, 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 an 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 */:

xml
<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 }.

xml
<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)

xml
<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:

xml
<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 spans, 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 spans which highlight strings (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.

xml
<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.

  1. Copy the definition file into the Resources folder of the control's project

  2. Set the Build Action to be Embedded Resource

  3. Open SyntaxModes.xml located in the same folder and add a mode tag which points to your definition, for example

    xml
    <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.

  4. 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.

  1. 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.

  2. 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.

    xml
    <?xml version="1.0" encoding="utf-8"?>
    <SyntaxModes version="1.0">
     <Mode file="CSS-Mode.xshd" name="CSS" extensions=".css" />
    </SyntaxModes>
    
  3. The following line of code will load the definition file into the text editor control:

    csharp
    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):

csharp
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 below 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.

Update History

  • 2011-07-08 - First published
  • 2020-11-21 - Updated formatting

Like what you're reading? Perhaps you like to buy us a coffee?

Donate via Buy Me a Coffee

Donate via PayPal


Files


Comments