In This Article

Taggers and Tagger Providers

Taggers are objects that can return tagged snapshot ranges for any requested snapshot range. They are generally created by tagger providers, which are language service. Tagger results are then consumed by tag aggregators.

The ITagger Interface

Taggers must implement the ITagger interface. The type parameter indicates the ITag-based type that is provided by the tagger. For instance, a tagger that returns IClassificationTag would be an ITagger<IClassificationTag>.

Returning Tagged Snapshot Ranges

The main focus of this interface is its GetTags method. That method is called when some other object, such as a tag aggregator, needs to know the tagged snapshot ranges provided by the tagger for one or more snapshot ranges. The return value of the method is an IEnumerable<TagSnapshotRange<T>>. This allows the C# yield keyword to be used when implementing a GetTags method.

Lifecycle

Since taggers generally attach to various events on a document or other objects, they have the notion of a lifecycle. The lifecycle starts when the tagger is created and ends when the tagger is closed.

The ITagger.Close method can be used to close a tagger. When a tagger is closed, its Closed event is raised.

Notification of Tag Changes

Sometimes the data that a tagger monitors is updated in such a way that its tag results are affected. The tagger's TagsChanged event can be raised to notify any watchers, such as tag aggregators, that the tag data that can be retrieved has been updated.

The event arguments should indicate the snapshot range that was updated. If the entire document was updated, pass a snapshot range that starts at offset 0 and goes through the snapshot length.

Ordering Taggers

Most of the time, the order of taggers isn't important. However, when working in an editor and layering IClassificationTag taggers, order is important since that governs how the highlighting style data related to one classification type is merged into the highlighting style data from another classification type.

Taggers must implement the IOrderable interface, which allows them to be ordered as desired.

Token Taggers

Token taggers are a special built-in implementation of taggers that return both ITokenTag and IClassificationTag objects. The tokens used are generated by the language's lexer. These tokens are returned in token tags and are also used to generate related classification tags as appropriate.

Since in an editor, the syntax highlighting is based off of classifications, token taggers provide the mechanism by which a lexer indirectly provides the base syntax highlighting layer for code. Because token tags also report tokens for a given range, they are used to enable the functionality of scanning a snapshot by tokens.

Important

Only one token tagger should ever be associated with a document at any given time. Therefore, only one tagger provider service should ever be registered on a language.

The TokenTagger Class

The TokenTagger class wraps all this functionality up into a handy package. For languages that have a mergable lexer, the TokenTagger class is all you need, and can be used by your language by adding a TokenTaggerProvider service to your language with the type parameter of TokenTagger.

Note

If your language is loaded from a language definition file, a token tagger provider service will be automatically registered with your language.

If your language is created in code-behind and all the code was generated by the Language Designer tool, it will make a custom token tagger provider class that can be used.

The standard TokenTagger works for languages with mergable lexers since the tokens created by those lexers already include references to the classification type that should be used for each individual token.

For languages with non-mergable lexers, tokens are smaller and more simplistic. They do not know the related classification type to use. Therefore for languages that use these types of lexers, you must create a class that inherits the TokenTagger class and overrides the ClassifyToken method. This method is passed a token and you return the IClassificationType, if any, to use for that token. The code in the base TokenTagger class takes care of the rest.

The TokenTagger class has a built-in mechanism to support incremental lexing from any point in a document, which is why it is considered a special case tagger type. It completely virtualizes the caching of token data for a document and allows token data to be generated incrementally on-demand when requested. Whereas most parsing frameworks store all tokens for a document in memory (which can be really bad for large documents), this class instead uses a minimal amount of memory by only caching data at certain intervals and regenerates token data as needed. The net result is that a miniscule amount of memory is used to persist token data for incremental parsing vs. what most other products use, and performance is still good.

Sample: Adding a Token Tagger Provider Service

Assume that we are making an ECMAScript language and have an EcmaScriptTokenTagger class inherits TokenTagger and has a ClassifyToken override. This is how a provider for it could be installed:

language.RegisterService(new TokenTaggerProvider<EcmaScriptTokenTagger>());
Note

The example above only works for token taggers. For all other taggers, see the documentation on tagger providers below.

Collection Taggers

The ICollectionTagger interface (and related CollectionTagger implementation class) is a ITagger that is ideal for situations where there is a set of tagged ranges that should be maintained like a collection. If you would rather virtualize your tagger results, you should make a tagger that inherits one of the tagger base classes instead.

Collection Items

The collection tagger stores tagged version ranges (TagVersionRange) items. This class represents an ITag and its associated ITextVersionRange in a document. The version range can be translated to any snapshot.

A TagVersionRange can be created by passing it a lightweight ITextVersionRange (see the Offsets, Ranges, and Positions topic for details on how to create a version range), along with a tag.

Modifying the Collection

The tagger works just like a collection, where tagged version ranges (TagVersionRange objects) can be added to it with the Add method. Similar Remove and Clear methods are also available.

Two additional overloads for the Add method allows for adding a TagVersionRange indirectly by specifying a TextSnapshotRange and ITag, or an ITextSnapshotLine and ITag. In the latter case, the tagged region will track with a single line. Both of these overloads set the created TagVersionRange to be deleted when it becomes zero-length. If more find-grained control over tracking modes (how the range adjusts due to text changes) is required, use the lower-level Add method that takes a TagVersionRange directly.

An additional overload of the Remove method allows for indirectly removing a TagVersionRange by finding the instance that has a specified ITag.

Two Toggle method overloads toggle whether a tagged range appears over a certain text range. Just like the additional Add method overloads, one accepts a TextSnapshotRange and ITag, and the other an ITextSnapshotLine and ITag. If any tags (within the same tagger) are located in the covered range, they will be removed. If there are no tags within that range, the result is the same as calling the related Add method.

Batch Updates

The tagger will raise a TagsChanged event any time the collection is modified. When adding multiple tagged ranges, this is not efficient. Therefore, batches are supported. A batch can be created by calling the CreateBatch method. This returns an IDisposable object. When the object gets disposed, the batch is ended and the TagsChanged event is raised. Therefore, this disposible object is ideal for use within a C# using block.

This code shows how to wrap tagger modification code in a batch update:

using (IDisposable batch = tagger.CreateBatch()) {
	// Make batch updates here
}

Finding Tags

The indexer of the ICollectionTagger type allows you to specify an ITag and the related TagVersionRange item is returned, if one is found.

ICollectionTagger also supports more complex search operations via its FindNext method overloads. The FindNext method will find the next TagVersionRange from the starting point that matches the options that are specified. This method is very useful in scenarios such as trying to find the next bookmark tagged range from the current caret offset in an editor.

One overload is line-based and accepts a start ITextSnapshotLine argument. The other is offset-based and accepts a start TextSnapshotOffset argument. Both overloads also take an optional ITagSearchOptions argument.

The options have properties for setting whether the search should search upwards instead of downward (the default), if the search can wrap if it reaches the start/end of the document with no match, and a filter predicate that can allow for a lambda to decide whether a potential match is valid.

Parse Error Taggers

A ParseErrorTagger class may be available, which is a tagger for ISquiggleTag. When this tagger is provided to a document, it will watch for updates to the document's ParseData property. It will see if the parse data implements the IParseErrorProvider interface. If it does, this tagger will automatically provide related squiggle tags for all parse errors, and when in an editor, will automatically render squiggles under the appropriate text ranges.

For performance reasons, the tagger limits the number of parse errors it can tag. This limit defaults to 5000, but can be adjusted by changing the ParseErrorTagger.MaximumParseErrorCount property to another value.

This tagger can be attached to a document via a document-oriented tagger provider, described below.

Tagger Base Classes

There are several classes that can be used as base classes for tagger implementations.

Type Description

TaggerBase

This is an abstract base class for a tagger that implements nearly all of the ITagger interface. The only method that you need to implement in an inheriting class is the GetTags method.

In most scenarios, this is the ideal base class to use when writing a tagger.

The TaggerBase constructor takes a string key that identifies the tagger, an optional collection of IOrderable objects (if ordering the tagger), and the ICodeDocument that the tagger is for. There also is an optional flag indicating whether or not the tagger is for a language. In most cases this should be true since it will automatically disconnect the tagger if the document's language is changed.

TokenTagger

This class is used for the special token tagger functionality that is described above. For languages with mergable lexers, an instance of this class can be installed as-is. However, for languages with non-mergable lexers, you can make class that inherits TokenTagger and overrides its ClassifyToken method, which returns the IClassificationType to use for an IToken.

Sample: ITagger Implementation

In this sample we have implemented a tagger that uses the .NET regular expression engine to search for a text pattern. When a match is found, it is tagged with an ISquiggleTag. Assuming this is being used with an editor, a squiggle line will appear under the related text.

/// <summary>
/// Provides <see cref="ISquiggleTag"/> objects over text ranges that contain the specified regex pattern text.
/// </summary>
public class CustomSquiggleTagger : TaggerBase<ISquiggleTag> {

	/// <summary>
	/// Initializes a new instance of the <c>CustomSquiggleTagger</c> class.
	/// </summary>
	/// <param name="document">The document to which this manager is attached.</param>
	public CustomSquiggleTagger(ICodeDocument document) :
		base("ActiproPatternBasedSquiggle", null, document, true) {}

	/// <summary>
	/// Returns the tag ranges that intersect with the specified normalized snapshot ranges.
	/// </summary>
	/// <param name="snapshotRanges">The collection of normalized snapshot ranges.</param>
	/// <param name="parameter">An optional parameter that provides contextual information about the tag request.</param>
	/// <returns>The tag ranges that intersect with the specified normalized snapshot ranges.</returns>
	public override IEnumerable<TagSnapshotRange<ISquiggleTag>> GetTags(
		NormalizedTextSnapshotRangeCollection snapshotRanges, object parameter) {

		if (snapshotRanges != null) {
			// Loop through the snapshot ranges
			foreach (TextSnapshotRange snapshotRange in snapshotRanges) {
				// Get the text of the snapshot range
				string text = snapshotRange.Text;

				// Look for a regex pattern match
				MatchCollection matches = Regex.Matches(text, @"\bActipro\b", RegexOptions.IgnoreCase);
				if (matches.Count > 0) {
					// Loop through the matches
					foreach (Match match in matches) {
						// Create a tag
						SquiggleTag tag = new SquiggleTag();

						// Yield the tag
						yield return new TagSnapshotRange<ISquiggleTag>(
							TextSnapshotRange.FromSpan(snapshotRange.Snapshot,
								snapshotRange.StartOffset + match.Index, match.Length), tag);
					}
				}
			}
		}
	}
}

Tagger Providers

Tagger providers are objects that can create/retrieve taggers for a particular document or view. Taggers can be created for either a document or a view and their providers are called on-demand when a related tagger is needed.

The general rule is that if a tagger is going to provide tags for that document that should show up in any view that uses the document, a document-oriented tagger provider should be used. Most taggers fall into this category. An example of this would be a tagger that provides syntax error tags. Document-oriented tagger providers are represented by the ICodeDocumentTaggerProvider interface.

For other cases where a tagger should be for a specific view, a view-oriented tagger provider should be used. An example of this would be a tagger that relies on the current selection in a view. View-oriented tagger providers are represented by the ITextViewTaggerProvider interface.

Both tagger provider interfaces have two members: a TagTypes property that returns an enumerable of the Type of tags that are returned by provided taggers, and a GetTagger method that returns a tagger instance to use for the specified document/view.

Built-In Implementation Classes

These types are built-in classes that can be registered as services on syntax languages and used to provide taggers.

Type Description

CodeDocumentTaggerProvider

Creates a tagger is associated with an ICodeDocument and any view that uses it.

A constructor overload accepts a "singleton" object key that can be used to persist any created tagger into the ICodeDocument.Properties dictionary using the key. In this case, if the document's language changes, the tagger is automatically closed and removed from the properties dictionary. The tagger associated with the document can be retrieved via the properties dictionary while it is active.

If no "singleton" object is passed to the tagger provider, a new tagger class instance will be created each time a tag aggregator requests one.

TextViewTaggerProvider

Creates a tagger is associated with a specific text view. This type is only available when an editor is used.

A constructor overload accepts a "singleton" object key that can be used to persist any created tagger into the ITextView.Properties dictionary using the key. In this case, if the view's document or the document's language changes, the tagger is automatically closed and removed from the properties dictionary. The tagger associated with the view can be retrieved via the properties dictionary while it is active.

If no "singleton" object is passed to the tagger provider, a new tagger class instance will be created each time a tag aggregator requests one.

TokenTaggerProvider

Creates a token tagger (see above for more info) that is associated with an ICodeDocument and any views that use it.

Only one token tagger provider should ever be registered as a service on the same language at a time.

Registering with a Syntax Language

As described above, CodeDocumentTaggerProvider, TextViewTaggerProvider, and TokenTaggerProvider objects can be used as language services to provide taggers to any tag aggregator that request them.

This code shows how to register a document-oriented tagger provider language service that provides ParseErrorTagger objects for documents that use the language. Note that we are also passing a "singleton" key so that the tagger that is created for any document using the language is persisted in the document's Properties dictionary while it is active.

language.RegisterService(new CodeDocumentTaggerProvider<ParseErrorTagger>(typeof(ParseErrorTagger)));