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