Skip to content

Commit 40aad33

Browse files
committed
Merged main into live
2 parents 6a8d3ee + 8d5bf80 commit 40aad33

File tree

7 files changed

+317
-35
lines changed

7 files changed

+317
-35
lines changed

docs/extensibility/visualstudio.extensibility/editor/editor.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ Extensions can contribute new CodeLenses to the Visual Studio editor. A CodeLens
4343

4444
For detailed walkthrough of how to provide your own CodeLens with your extension, refer to [Extending Visual Studio editor with a new CodeLens](./walkthroughs/codelens.md)
4545

46+
### Taggers
47+
Extensions can contribute new taggers to the Visual Studio editor. Taggers are used to associate data with spans of text, such data is consumed by other Visual Studio features (E.g., CodeLens).
48+
49+
For detailed walkthrough of how to provide your own taggers with your extension, refer to [Extending Visual Studio editor with a new tagger](./walkthroughs/taggers.md)
50+
4651
## Related content
4752

4853
Learn about the editor interfaces and types at [Editor concepts](editor-concepts.md).

docs/extensibility/visualstudio.extensibility/editor/walkthroughs/codelens.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@ ms.subservice: extensibility-integration
1414
There are different types of CodeLenses that could be created. Each type offers its own unique set of functionalities. The following section details how to provide each of these types of CodeLenses.
1515

1616
## Text view CodeLens
17-
Text view CodeLens provide text-based information to segments of code. This is the simplest forms of CodeLens. The following concepts illustrate how to create a text view CodeLens:
18-
- [`ICodeLensProvider`](/dotnet/api/microsoft.visualstudio.extensibility.editor.icodelensprovider) interface is the main interface to implement. Your implementation of this interface will define when your CodeLens will be activated, and which segments of code your CodeLens will be applicable to, and how it will be displayed.
19-
- Within your implementation of [`ICodeLensProvider`](/dotnet/api/microsoft.visualstudio.extensibility.editor.icodelensprovider), you will need to define when the CodeLens should be activated by implementing [`CodeLensProviderConfiguration`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelensproviderconfiguration).
20-
- You will also need to implement the [`TryCreateCodeLensAsync`](/dotnet/api/microsoft.visualstudio.extensibility.editor.icodelensprovider.trycreatecodelensasync) method. This method will be invoked when your CodeLens is activated. In this method, you can define how you want your CodeLens to be displayed and when it should be displayed. This method returns an instance of [`CodeLens`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelens).
21-
- You will need to create your own sub-class of [`CodeLens`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelens), where you get to define how you want your display text to appear through the [`GetLabelAsync`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelens.getlabelasync) method. This method returns an instance of [`CodeLensLabel`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelenslabel), which you can use to configure the text, tooltip, and an optional icon.
17+
Text view CodeLens provide text-based information to segments of code and are the simplest forms of CodeLens. The following concepts illustrate how to create a text view CodeLens:
18+
- [`ICodeLensProvider`](/dotnet/api/microsoft.visualstudio.extensibility.editor.icodelensprovider) interface is the main interface to implement. Your implementation of this interface defines when your CodeLens will activate, and which segments of code your CodeLens applies to, and how it will display.
19+
- Within your implementation of [`ICodeLensProvider`](/dotnet/api/microsoft.visualstudio.extensibility.editor.icodelensprovider), you need to define when the CodeLens should be activated by implementing [`CodeLensProviderConfiguration`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelensproviderconfiguration).
20+
- You also need to implement the [`TryCreateCodeLensAsync`](/dotnet/api/microsoft.visualstudio.extensibility.editor.icodelensprovider.trycreatecodelensasync) method. This method will execute when your CodeLens is activated. In this method, you can define how you want your CodeLens to be displayed and when it should be displayed. This method returns an instance of [`CodeLens`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelens).
21+
- You need to create your own sub-class of [`CodeLens`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelens), where you get to define how you want your display text to appear through the [`GetLabelAsync`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelens.getlabelasync) method. This method returns an instance of [`CodeLensLabel`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelenslabel), which you can use to configure the text, tooltip, and an optional icon.
2222

2323
## Invokable CodeLens
24-
Invokable CodeLens allows extensions to perform some action (e.g. run a unit test) when user clicks on the CodeLens. Extensions can contribute invokable CodeLens by implementing [`InvokableCodeLens`](/dotnet/api/microsoft.visualstudio.extensibility.editor.invokablecodelens), which derives from [`CodeLens`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelens).
24+
Invokable CodeLens allows extensions to perform some action (for example, run a unit test) when user clicks on the CodeLens. Extensions can contribute invokable CodeLens by implementing [`InvokableCodeLens`](/dotnet/api/microsoft.visualstudio.extensibility.editor.invokablecodelens), which derives from [`CodeLens`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelens).
2525

2626
## Visual CodeLens
27-
Visual CodeLens allows extensions to provide custom UI, like a list of references to a method, to be displayed in a popup above the CodeLens when user clicks on the CodeLens. Extensions can contribute visual CodeLens by implementing [`VisualCodeLens`](/dotnet/api/microsoft.visualstudio.extensibility.editor.visualcodelens), which derives from [`CodeLens`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelens). Similar to text view margins, because extensions in VisualStudio.Extensibility might be out-of-process from the Visual Studio, visual CodeLenses provide UI by creating a [`RemoteUserControl`](./../../inside-the-sdk/remote-ui.md) and the corresponding data template for that control. While there are some simple examples below, we recommend reading the [Remote UI documentation](./../../inside-the-sdk/remote-ui.md) when creating visual CodeLens' UI content.
27+
Visual CodeLens allows extensions to provide custom UI, like a list of references to a method, to be displayed in a popup above the CodeLens when user clicks on the CodeLens. Extensions can contribute visual CodeLens by implementing [`VisualCodeLens`](/dotnet/api/microsoft.visualstudio.extensibility.editor.visualcodelens), which derives from [`CodeLens`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelens). Similar to text view margins, because extensions in VisualStudio.Extensibility might be out-of-process from the Visual Studio, visual CodeLenses provide UI by creating a [`RemoteUserControl`](./../../inside-the-sdk/remote-ui.md) and the corresponding data template for that control. While there are some simple examples in the following sections, we recommend reading the [Remote UI documentation](./../../inside-the-sdk/remote-ui.md) when creating visual CodeLens' UI content.
2828

29-
The sample code below demonstrates how to create a text view CodeLens and an invokable CodeLens:
29+
The following sample code demonstrates how to create a text view CodeLens and an invokable CodeLens:
3030

3131
```csharp
3232
public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
@@ -90,12 +90,12 @@ public class WordCountCodeLens : VisualCodeLens
9090
}
9191
```
9292

93-
In addition to configuring CodeLens provider display name, CodeLens providers can also configure priority of their CodeLens. The priority value is used to determine the relative ordering of your CodeLens respective to other CodeLenses. This is done through the [`Priority`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelensproviderconfiguration.priority) property in the [`CodeLensProviderConfiguration`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelensproviderconfiguration) object.
93+
In addition to configuring CodeLens provider display name, CodeLens providers can also configure priority of their CodeLens. The priority value is used to determine the relative ordering of your CodeLens respective to other CodeLenses, which can be set through the [`Priority`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelensproviderconfiguration.priority) property in the [`CodeLensProviderConfiguration`](/dotnet/api/microsoft.visualstudio.extensibility.editor.codelensproviderconfiguration) object.
9494

9595
CodeLenses typically visualize some data related to the text view. For example, they might want to display the number of references to a method. To do so, your CodeLens provider should also [listen to text view events](working-with-text.md) to react to opening, closing of text views and user typing.
9696

9797
Visual Studio only creates one instance of your CodeLens provider regardless of how many applicable text views a user opens. This means that if your CodeLens needs to maintain state, you need to ensure your CodeLens provider has a way to keep the state of currently open text views.
9898

9999
For more information, see [CodeLens Sample](https://github.com/Microsoft/VSExtensibility/tree/main/New_Extensibility_Model/Samples/CodeLensSample/).
100100

101-
*Contributing CodeLenses to new documents types (or existing document types not supporting CodeLens) is not yet supported.*
101+
If a Code Lens provider has to reference code elements that are not already being tagged by an existing Visual Studio feature, you can create a new [tagger](./taggers.md) implementing `ITextViewTaggerProvider<CodeLensTag>`.
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
---
2+
title: Customizing taggers in the editor
3+
description: A walkthrough of how to provide your own taggers in the Visual Studio editor using extensions
4+
ms.date: 2/5/2025
5+
ms.topic: conceptual
6+
ms.author: maprospe
7+
monikerRange: ">=vs-2022"
8+
author: matteo-prosperi
9+
manager: tinaschrepfer
10+
ms.subservice: extensibility-integration
11+
---
12+
13+
# Extending Visual Studio editor with a new tagger
14+
Extensions can contribute new taggers to Visual Studio. Taggers are used to associate data with spans of text. The data provided by taggers are consumed by other Visual Studio features (for example, [CodeLens](./codelens.md)).
15+
16+
VisualStudio.Extensibility only supports tag types that are provided by the [Microsoft.VisualStudio.Extensibility](https://www.nuget.org/packages/Microsoft.VisualStudio.Extensibility) package and implement the `Microsoft.VisualStudio.Extensibility.Editor.ITag` interface:
17+
18+
- `CodeLensTag` can be used together with an [ICodeLensProvider](./codelens.md) to add Code Lenses to documents
19+
- `TextMarkerTag` can be used to highlight portions of documents. VisualStudio.Extensibility doesn't support defining new Text Marker styles yet, so only styles that are built into Visual Studio or provided by a VSSDK extension can be used for now (a [VisualStudio.Extensibility in-proc extension](../../get-started/in-proc-extensions.md) can create Text Marker styles with an `[Export(typeof(EditorFormatDefinition))]`).
20+
21+
To generate tags, the extension must contribute an extension part that implements `ITextViewTaggerProvider<>` for the type (or types) of tags provided. The extension part also needs to implement `ITextViewChangedListener` to react to document changes:
22+
23+
```csharp
24+
[VisualStudioContribution]
25+
internal class MarkdownCodeLensTaggerProvider : ExtensionPart, ITextViewTaggerProvider<CodeLensTag>, ITextViewChangedListener
26+
{
27+
public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
28+
{
29+
AppliesTo = [DocumentFilter.FromDocumentType("vs-markdown")],
30+
};
31+
32+
public async Task TextViewChangedAsync(TextViewChangedArgs args, CancellationToken cancellationToken)
33+
{
34+
...
35+
}
36+
37+
public Task<TextViewTagger<CodeLensTag>> CreateTaggerAsync(ITextViewSnapshot textView, CancellationToken cancellationToken)
38+
{
39+
...
40+
}
41+
}
42+
```
43+
44+
The tagger provider has to keep track of the active taggers in order to dispatch the `TextViewChangedAsync` notifications to them. The following code snippet is a full implementation:
45+
46+
```csharp
47+
[VisualStudioContribution]
48+
internal class MarkdownCodeLensTaggerProvider : ExtensionPart, ITextViewTaggerProvider<CodeLensTag>, ITextViewChangedListener
49+
{
50+
private readonly object lockObject = new();
51+
private readonly Dictionary<Uri, List<MarkdownCodeLensTagger>> taggers = new();
52+
53+
public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
54+
{
55+
AppliesTo = [DocumentFilter.FromDocumentType("vs-markdown")],
56+
};
57+
58+
public async Task TextViewChangedAsync(TextViewChangedArgs args, CancellationToken cancellationToken)
59+
{
60+
List<Task> tasks = new();
61+
lock (this.lockObject)
62+
{
63+
if (this.taggers.TryGetValue(args.AfterTextView.Uri, out var textMarkerTaggers))
64+
{
65+
foreach (var textMarkerTagger in textMarkerTaggers)
66+
{
67+
tasks.Add(textMarkerTagger.TextViewChangedAsync(args.AfterTextView, args.Edits, cancellationToken));
68+
}
69+
}
70+
}
71+
72+
await Task.WhenAll(tasks);
73+
}
74+
75+
public Task<TextViewTagger<CodeLensTag>> CreateTaggerAsync(ITextViewSnapshot textView, CancellationToken cancellationToken)
76+
{
77+
var tagger = new MarkdownCodeLensTagger(this, textView.Document.Uri);
78+
lock (this.lockObject)
79+
{
80+
if (!this.taggers.TryGetValue(textView.Document.Uri, out var taggers))
81+
{
82+
taggers = new();
83+
this.taggers[textView.Document.Uri] = taggers;
84+
}
85+
86+
taggers.Add(tagger);
87+
}
88+
89+
return Task.FromResult<TextViewTagger<CodeLensTag>>(tagger);
90+
}
91+
92+
internal void RemoveTagger(Uri documentUri, MarkdownCodeLensTagger toBeRemoved)
93+
{
94+
lock (this.lockObject)
95+
{
96+
if (this.taggers.TryGetValue(documentUri, out var taggers))
97+
{
98+
taggers.Remove(toBeRemoved);
99+
if (taggers.Count == 0)
100+
{
101+
this.taggers.Remove(documentUri);
102+
}
103+
}
104+
}
105+
}
106+
}
107+
```
108+
109+
The tagger itself, is a class implementing `TextViewTagger<>`:
110+
111+
```csharp
112+
internal class MarkdownCodeLensTagger : TextViewTagger<CodeLensTag>
113+
{
114+
private readonly MarkdownCodeLensTaggerProvider provider;
115+
private readonly Uri documentUri;
116+
117+
public MarkdownCodeLensTagger(MarkdownCodeLensTaggerProvider provider, Uri documentUri)
118+
{
119+
this.provider = provider;
120+
this.documentUri = documentUri;
121+
}
122+
123+
public override void Dispose()
124+
{
125+
this.provider.RemoveTagger(this.documentUri, this);
126+
base.Dispose();
127+
}
128+
129+
public async Task TextViewChangedAsync(ITextViewSnapshot textView, IReadOnlyList<TextEdit> edits, CancellationToken cancellationToken)
130+
{
131+
...
132+
await this.UpdateTagsAsync(ranges, tags, cancellationToken);
133+
}
134+
135+
protected override async Task RequestTagsAsync(NormalizedTextRangeCollection requestedRanges, bool recalculateAll, CancellationToken cancellationToken)
136+
{
137+
...
138+
await this.UpdateTagsAsync(ranges, tags, cancellationToken);
139+
}
140+
}
141+
```
142+
143+
Both the `TextViewChangedAsync` and `RequestTagsAsync` methods, should call `UpdateTagsAsync` providing the ranges that tags are being updated for and the new tags themselves. The `TextViewTagger<>` base class holds a cache of previously generated tags, calling `UpdateTagsAsync` invalidates all existing tags for the provided ranges and replaces them with the newly provided ones.
144+
145+
While generating tags for the entire document is a possible strategy, it is preferrable to only generate tags for the requested ranges (in `RequestTagsAsync`) and the edited ranges (in `TextViewChangedAsync`). It is also common to have to extend such ranges to cover meaningful spans of the document syntax (for example, entire statements, entire lines of code, etc.).
146+
147+
Handling text view changes in particular requires some additional code. The following code snippet is an example:
148+
149+
```csharp
150+
public async Task TextViewChangedAsync(ITextViewSnapshot textView, IReadOnlyList<TextEdit> edits, CancellationToken cancellationToken)
151+
{
152+
// GetAllRequestedRangesAsync returns all ranges that Visual Studio has requested
153+
// tags for so far.
154+
var allRequestedRanges = await this.GetAllRequestedRangesAsync(textView.Document, cancellationToken);
155+
156+
// Translate edited ranges to the current document snapshot
157+
var editedRanges = edits.Select(e => e.Range.TranslateTo(textView.Document, TextRangeTrackingMode.EdgeInclusive));
158+
159+
// Extend 0-length ranges to be at least 1 character so that they are not ignored
160+
// when passed to `Intersect`
161+
var fixedEditedRanges = editedRanges.Select(e => EnsureNotEmpty(editedRanges));
162+
163+
// Intersect the edited ranges with the requested ranges to avoid generating tags
164+
// for ranges that Visual Studio is not interested in (for example, non-visible portions
165+
// of the document)
166+
var rangesOfInterest = allRequestedRanges.Intersect(fixedEditedRanges);
167+
168+
// Extend ranges to match meaningful portions of the document's syntax
169+
var rangesToCalculateTagsFor = ExtendToMatchSyntax(rangesOfInterest);
170+
171+
// Calculate tags
172+
await this.CreateTagsAsync(textView.Document, rangesToCalculateTagsFor);
173+
}
174+
175+
private static TextRange EnsureNotEmpty(TextRange range)
176+
{
177+
...
178+
}
179+
180+
private static IEnumerable<TextRange> ExtendToMatchSyntax(IEnumerable<TextRange> range)
181+
{
182+
...
183+
}
184+
185+
private async Task CreateTagsAsync(ITextDocumentSnapshot document, IEnumerable<TextRange> requestedRanges)
186+
{
187+
...
188+
await this.UpdateTagsAsync(ranges, tags, cancellationToken);
189+
}
190+
```
191+
192+
If generating tags requires significant computation (for example, it's necessary to parse the entire document, or large portions of it, in order to generate tags), the tagger should have additional synchronization logic to avoid calculating tags for every text view change or call to `RequestTagsAsync`. Instead, `RequestTagsAsync` and `TextViewChangedAsync` should quickly return a completed task, multiple requests should be batched together, and `UpdateTagsAsync` should be called when the batched tag generation is completed. The [tagger sample extension](https://github.com/Microsoft/VSExtensibility/tree/main/New_Extensibility_Model/Samples/TaggersSample/README.md) contains a complete example of this approach.

0 commit comments

Comments
 (0)