Subscribe (RSS)

Quick Links

About Actipro

Actipro Software has been creating .NET user interface control products for Windows Forms since its inception. More recently, Actipro has become a pioneer in the .NET 3.0 WPF control development arena.
Thursday, 26 March 2009 01:56 by Bill Henning (Actipro)

SyntaxEditor for WPF - Highlighting style configuration part 2

Thanks for your comments on this post and via email on yesterday’s post about getting input on highlighting style configuration.  After reviewing your comments and talking things over internally here, I believe we have a simpler yet very robust way of both registering classification types and how they map to highlighting styles.

In today’s blog post I’d like to follow up on the conversation yesterday. 

Quick summary

Let me start off by summarizing the process of how text gets highlighted for display in a SyntaxEditor.

When a SyntaxEditor needs to draw text, it asks one or more active IClassifier instances to return logical classified spans of text.  The IClassifier defined by a syntax language essentially does some lexical parsing to create tokens, then takes those tokens and returns a IClassificationRange objects for the requested text range.  Again, a sample:

   1: int foo;

If a SyntaxEditor was drawing text for this line, it would first ask for the classification of the text on the line.  The current language’s IClassifier would be called, would tokenize the line, and return a classification result that says text range 0-3 is an IClassificationType with a Key of Keyword.  Then it would do the same thing for the tokens that follow, classifying them properly.

As an interesting side note, SyntaxEditor allows other layers of classifiers to be added in here.  This means you can classify a range as something else on the fly before SyntaxEditor gets the classification results.  Neat stuff!  :)  This feature is described in this blog post from January.

Next, the classification results use an IHighlightingStyleRegistry to map an IClassificationType to an IHighlightingStyle.  Thus each classified range gets a certain style applied to it and it renders with various colors, etc.  SyntaxEditor renders the text in the appropriate style, and voila, we have syntax highlighting.

Solution to yesterday’s post

Ok so that’s the summary, let’s dive into what we changed after yesterday’s post.  In our samples, we are making a getting-started series of QuickStarts that show the implementation of a programmatic lexing Simple language.  Full source included, of course. 

For our Simple language’s IClassifier implementation, we know it needs to classify text as Keyword, Identifier, Comment, and Number.  What we do is define a type called SimpleClassificationTypes like this:

   1: /// <summary>
   2: /// Represents a provider of <see cref="IClassificationType"/> objects for 
   3: /// the <c>Simple</c> language.
   4: /// </summary>
   5: public class SimpleClassificationTypes {
   6:  
   7:     private IHighlightingStyleRegistry registry;
   8:  
   9:     private IClassificationType comment;
  10:     private IClassificationType identifier;
  11:     private IClassificationType keyword;
  12:     private IClassificationType number;
  13:  
  14:     /// <summary>
  15:     /// Initializes a new instance of the <c>SimpleClassificationTypes</c> class.
  16:     /// </summary>
  17:     public SimpleClassificationTypes() : this(null) {}
  18:  
  19:     /// <summary>
  20:     /// Initializes a new instance of the <c>SimpleClassificationTypes</c> class.
  21:     /// </summary>
  22:     /// <param name="registry">The <see cref="IHighlightingStyleRegistry"/> 
  23:     /// to use when registering classification types and highlighting styles.</param>
  24:     public SimpleClassificationTypes(IHighlightingStyleRegistry registry) {
  25:         this.registry = registry ?? AmbientHighlightingStyleRegistry.Instance;
  26:     }
  27:  
  28:     /// <summary>
  29:     /// Gets the <see cref="IClassificationType"/> to use for comments.
  30:     /// </summary>
  31:     /// <value>The <see cref="IClassificationType"/> to use for comments.</value>
  32:     public IClassificationType Comment {
  33:         get {
  34:             if (comment == null) {
  35:                 comment = registry.GetClassificationType(ClassificationTypes.Comment.Key);
  36:                 if (comment == null) {
  37:                     comment = ClassificationTypes.Comment;
  38:                     registry.Register(comment, new HighlightingStyle(
  39:                         ClassificationTypes.Comment.Key, 
  40:                         Colors.Green, Colors.Transparent));
  41:                 }
  42:             }
  43:             return comment;
  44:         }
  45:     }
  46:  
  47:     // NOTE: Identifier, Keyword, Number props defined here similar to Comment
  48:  
  49:     /// <summary>
  50:     /// Registers all classification types and highlighting styles with 
  51:     /// the <see cref="IHighlightingStyleRegistry"/> used by this class.
  52:     /// </summary>
  53:     /// <returns>The collection of <see cref="IClassificationType"/> objects
  54:     /// that were registered.</returns>
  55:     public IEnumerable<IClassificationType> RegisterAll() {
  56:         return new IClassificationType[] {
  57:             this.Comment,
  58:             this.Identifier,
  59:             this.Keyword,
  60:             this.Number
  61:         };
  62:     }
  63:     
  64: }
  65:     

Effectively what we have now is a class that has properties which return the IClassificationType instances that should be used by the Simple language’s IClassifier.  The neat thing is that assuming you don’t do any pre-registration of classification types, it will register everything on-the-fly as needed.

If you wish to have a highlighting styles options page in your app, you can also call this at your app startup to pre-register everything, thereby ensuring that the dialog shows all classification types for the languages you support in your app.

   1: new SimpleClassificationTypes().RegisterAll();

Another change from yesterday is that we now only have a single registry instead of two.  The single IHighlightingStyleRegistry can provide the IHighlightingStyle for a specific IClassificationType, can enumerate the registered IClassificationType objects, can return the IClassificationType registered for a give classification type key, and can enumerate IHighlightingStyles that are registered.

Altogether it seems like a solid solution since you can be lazy and let it handle registering things as needed, or you can set everything up yourself at app startup.

We welcome your comments on this implementation.

IClassificationType inheritance

As Jesper posted in the comments to yesterday’s post, it would be cool to support logical inheritance of IClassificationType objects.  This way you could designate a tree of IClassificationType objects, like XML Comment Tag inherits XML Comment which inherits Comment and that inherits Text.  You could assign a IHighlightingStyle to any of those levels and during style resolution, it would take the lowest non-default property values for things like foreground color, etc. 

This feature is not implemented at this time, however it’s something we’ve thought about and have placeholders in our code where it can be added later.  What are your thoughts on this, and if we do it, should we allow single inheritance or multiple inheritance?

Tags:   ,
Categories:   Actipro | In development | WPF
Actions:   Submit to DotNetKicks | E-mail | Permalink | Comments (2) | Comment RSS




Comments

Jesper

I started writing this comment to push multiple inheritance as important, but then I realized that it's not just the classification type's inheritance that plays a role in styling; the context in the code does, too.

What if the XML tag is in a documentation comment? If it's also #if-ed out? If it's, on top of that, and this probably wouldn't be a documentation comment, is in a breakpoint highlight? What if it's in all these things and you've chosen to use other layer of custom classification on top. The list goes on and I'm starting to get brainfreeze just thinking about it. You could actually use something like CSS selectors to style this, with the classification being the class and the context being the elements. (That's clearly an exercise for the reader though.)

There's inherited behavior "from within", and overridden behavior "on top", in essence. I actually think the stacking factor of these is more what the example I used in my last comment (TextMate) does. (What's interesting is that as far as I know, that's its entire token concept; SyntaxEditor really solves this problem in an exhaustive way, but that's a nifty alternative approach for "just" getting the syntax highlighting.)

March 24, 2009 at 17:32  

Bill Henning (Actipro)

True, yet here in this case, we're only concerned with how the lexical parser for a language is classifying the text.

If something is #if'ed-out, it would then probably need to use some sort of pre-processor directive classification.  Alternatively, if you cared about highlighting the code in the section but maybe a bit faded out, you'd probably need a separate classification type for each thing like Keyword, etc. that was something like PreProcessorKeyword.

So do you vote single or multiple inheritance?

March 24, 2009 at 22:40  

Add comment



  Country flag

biuquote
  • Comment
  • Preview
Loading





We moderate all comments to block comment spam