Skip to content

Commit b330c5e

Browse files
2 parents 91ba9b0 + 85f21b9 commit b330c5e

File tree

13 files changed

+469
-4
lines changed

13 files changed

+469
-4
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ This package attempts to solve multiple issues with the current extensibility mo
8585
Base classes, helper methods, and extension methods encapsulate the complexity so you don't have to.
8686

8787
### It's difficult to find what services and components to use
88-
Now the most commmon services are all easy to get to from the main `VS` object. For instance, to write to the status bar, you can now write the following:
88+
Now the most commmon services are all easy to get to from the main `VS` object.
8989

9090

9191
### Best practices change with each version of VS. I can't keep up
@@ -105,4 +105,6 @@ This is a living project where the whole community can contribute helpers on top
105105
This project works around those changes in the implementation of its public contracts and interfaces. This means that what was a breaking change to the VS SDK, becomes an implementation detail of this project and no user will be affected.
106106

107107
## Templates
108-
For both project- and item templates that utilizes this NuGet packages, download the [Extensibility Template Pack](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ExtensibilityItemTemplates).
108+
For both project- and item templates that utilizes this NuGet packages, download the Extensibility Template Pack:
109+
* [Extensibility Template Pack 2022](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ExtensibilityItemTemplates2022) (Visual Studio 2022)
110+
* [Extensibility Template Pack 2019](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ExtensibilityItemTemplates) (Visual Studio 2019)

demo/VSSDK.TestExtension/TestExtensionPackage.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ private void SolutionEvents_OnBeforeOpenProject(string obj)
6868
private void SolutionEvents_OnAfterOpenProject(Project obj)
6969
{
7070
if (obj != null)
71+
{
7172
VS.StatusBar.ShowMessageAsync("Opened project " + obj.Name).FireAndForget();
73+
}
7274
}
7375

7476
private void ProjectItemsEvents_AfterRemoveProjectItems(AfterRemoveProjectItemEventArgs obj)

src/toolkit/Community.VisualStudio.Toolkit.Shared/ErrorList/ErrorListItem.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using Microsoft.VisualStudio.Imaging.Interop;
23
using Microsoft.VisualStudio.Shell.Interop;
34

45
namespace Community.VisualStudio.Toolkit
@@ -63,6 +64,11 @@ public class ErrorListItem
6364
/// </summary>
6465
public string? BuildTool { get; set; }
6566

67+
/// <summary>
68+
/// The image icon moniker to use for the error list item.
69+
/// </summary>
70+
public ImageMoniker Icon { get; set; }
71+
6672
/// <inheritdoc/>
6773
public override bool Equals(object obj)
6874
{

src/toolkit/Community.VisualStudio.Toolkit.Shared/ErrorList/TableEntriesSnapshot.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using Microsoft.VisualStudio.Shell.TableManager;
45

@@ -39,6 +40,15 @@ public override bool TryGetValue(int index, string columnName, out object? conte
3940
return false;
4041
}
4142

43+
if (columnName == StandardTableKeyNames.PriorityImage || columnName == StandardTableKeyNames.ErrorSeverityImage)
44+
{
45+
if (_errors[index].Icon.Guid != Guid.Empty)
46+
{
47+
content = _errors[index].Icon;
48+
return true;
49+
}
50+
}
51+
4252
switch (columnName)
4353
{
4454
case StandardTableKeyNames.ProjectName:

src/toolkit/Community.VisualStudio.Toolkit.Shared/LanguageService/LanguageBase.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ public virtual int MapLogicalView(ref Guid logicalView, out string? physicalView
179179
if (VSConstants.LOGVIEWID_Primary == logicalView ||
180180
VSConstants.LOGVIEWID_Debugging == logicalView ||
181181
VSConstants.LOGVIEWID_Code == logicalView ||
182+
VSConstants.LOGVIEWID_UserChooseView == logicalView ||
182183
VSConstants.LOGVIEWID_TextView == logicalView)
183184
{
184185
// primary view uses NULL as pbstrPhysicalView
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.Collections.Generic;
2+
using System.ComponentModel.Composition;
3+
using Microsoft.VisualStudio.Text;
4+
using Microsoft.VisualStudio.Text.Classification;
5+
using Microsoft.VisualStudio.Text.Tagging;
6+
7+
namespace Community.VisualStudio.Toolkit
8+
{
9+
/// <summary>
10+
/// A base class to provide classification (syntax highlighting) based on a Token Tagger.
11+
/// </summary>
12+
public abstract class TokenClassificationTaggerBase : ITaggerProvider
13+
{
14+
[Import] internal IClassificationTypeRegistryService? _classificationRegistry = null;
15+
[Import] internal IBufferTagAggregatorFactoryService? _bufferTagAggregator = null;
16+
17+
/// <summary>
18+
/// A map of a token value and which classification name it cooresponds with.
19+
/// </summary>
20+
public abstract Dictionary<object, string> ClassificationMap { get; }
21+
22+
/// <inheritdoc/>
23+
public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
24+
{
25+
ITagAggregator<TokenTag>? tags = _bufferTagAggregator?.CreateTagAggregator<TokenTag>(buffer);
26+
return (ITagger<T>)buffer.Properties.GetOrCreateSingletonProperty(() => new TokenClassifier(_classificationRegistry, tags, ClassificationMap));
27+
}
28+
}
29+
30+
internal class TokenClassifier : TokenTaggerBase<IClassificationTag>
31+
{
32+
private static readonly Dictionary<object, ClassificationTag> _classificationMap = new();
33+
34+
internal TokenClassifier(IClassificationTypeRegistryService? registry, ITagAggregator<TokenTag>? tags, Dictionary<object, string> map) : base(tags)
35+
{
36+
foreach (object key in map.Keys)
37+
{
38+
_classificationMap[key] = new ClassificationTag(registry?.GetClassificationType(map[key]));
39+
}
40+
}
41+
42+
public override IEnumerable<ITagSpan<IClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans, bool isFullParse)
43+
{
44+
foreach (IMappingTagSpan<TokenTag> tag in Tags!.GetTags(spans))
45+
{
46+
if (_classificationMap.TryGetValue(tag.Tag.TokenType ?? "", out ClassificationTag classificationTag))
47+
{
48+
NormalizedSnapshotSpanCollection tagSpans = tag.Span.GetSpans(tag.Span.AnchorBuffer.CurrentSnapshot);
49+
50+
foreach (SnapshotSpan tagSpan in tagSpans)
51+
{
52+
yield return new TagSpan<ClassificationTag>(tagSpan, classificationTag);
53+
}
54+
}
55+
}
56+
}
57+
}
58+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel.Composition;
4+
using System.Linq;
5+
using Microsoft.VisualStudio.Text;
6+
using Microsoft.VisualStudio.Text.Adornments;
7+
using Microsoft.VisualStudio.Text.Tagging;
8+
9+
namespace Community.VisualStudio.Toolkit
10+
{
11+
/// <summary>
12+
/// An error tagger based on TokenTag.
13+
/// </summary>
14+
public abstract class TokenErrorTaggerBase : ITaggerProvider
15+
{
16+
[Import] internal IBufferTagAggregatorFactoryService? _bufferTagAggregator = null;
17+
18+
/// <inheritdoc/>
19+
public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
20+
{
21+
ITagAggregator<TokenTag>? tags = _bufferTagAggregator?.CreateTagAggregator<TokenTag>(buffer);
22+
return (ITagger<T>)buffer.Properties.GetOrCreateSingletonProperty(() => new ErrorTagger(tags));
23+
}
24+
}
25+
26+
internal class ErrorTagger : TokenTaggerBase<IErrorTag>
27+
{
28+
private readonly TableDataSource _dataSource;
29+
30+
public ErrorTagger(ITagAggregator<TokenTag>? tags) : base(tags)
31+
{
32+
if (tags == null)
33+
{
34+
throw new ArgumentNullException(nameof(tags));
35+
}
36+
37+
_dataSource = new TableDataSource(tags.BufferGraph.TopBuffer.ContentType.DisplayName);
38+
}
39+
40+
public override IEnumerable<ITagSpan<IErrorTag>> GetTags(NormalizedSnapshotSpanCollection spans, bool isFullParse)
41+
{
42+
IEnumerable<IMappingTagSpan<TokenTag>> tags = Tags!.GetTags(spans).Where(t => !t.Tag.IsValid);
43+
44+
foreach (IMappingTagSpan<TokenTag> tag in tags)
45+
{
46+
NormalizedSnapshotSpanCollection tagSpans = tag.Span.GetSpans(tag.Span.AnchorBuffer.CurrentSnapshot);
47+
string tooltip = string.Join(Environment.NewLine, tag.Tag.Errors);
48+
ErrorTag errorTag = new(GetErrorType(tag.Tag.Errors), tooltip);
49+
50+
foreach (SnapshotSpan span in tagSpans)
51+
{
52+
yield return new TagSpan<IErrorTag>(span, errorTag);
53+
}
54+
}
55+
56+
if (isFullParse)
57+
{
58+
PopulateErrorList(tags);
59+
}
60+
}
61+
62+
private static string GetErrorType(IList<ErrorListItem> errors)
63+
{
64+
return errors.FirstOrDefault()?.ErrorCategory ?? PredefinedErrorTypeNames.SyntaxError;
65+
}
66+
67+
private void PopulateErrorList(IEnumerable<IMappingTagSpan<TokenTag>> tags)
68+
{
69+
IEnumerable<ErrorListItem> errors = tags.SelectMany(t => t.Tag.Errors);
70+
71+
if (!errors.Any())
72+
{
73+
_dataSource?.CleanAllErrors();
74+
}
75+
else
76+
{
77+
_dataSource?.AddErrors(errors);
78+
}
79+
}
80+
81+
public override void Dispose(bool disposing)
82+
{
83+
if (disposing)
84+
{
85+
_dataSource?.CleanAllErrors();
86+
}
87+
88+
base.Dispose(disposing);
89+
}
90+
}
91+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#if VS16 || VS17
2+
using System.Collections.Generic;
3+
using System.ComponentModel.Composition;
4+
using System.Linq;
5+
using Microsoft.VisualStudio.Text;
6+
using Microsoft.VisualStudio.Text.Adornments;
7+
using Microsoft.VisualStudio.Text.Tagging;
8+
9+
namespace Community.VisualStudio.Toolkit
10+
{
11+
/// <summary>
12+
/// A base class for providing outlining based on Token Tags.
13+
/// </summary>
14+
public abstract class TokenOutliningTaggerBase : ITaggerProvider
15+
{
16+
[Import] internal IBufferTagAggregatorFactoryService? _bufferTagAggregator = null;
17+
18+
/// <inheritdoc/>
19+
public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
20+
{
21+
ITagAggregator<TokenTag>? tags = _bufferTagAggregator?.CreateTagAggregator<TokenTag>(buffer);
22+
return (ITagger<T>)buffer.Properties.GetOrCreateSingletonProperty(() => new StructureTagger(tags));
23+
}
24+
}
25+
26+
internal class StructureTagger : TokenTaggerBase<IStructureTag>
27+
{
28+
public StructureTagger(ITagAggregator<TokenTag>? tags) : base(tags)
29+
{ }
30+
31+
public override IEnumerable<ITagSpan<IStructureTag>> GetTags(NormalizedSnapshotSpanCollection spans, bool isFullParse)
32+
{
33+
foreach (IMappingTagSpan<TokenTag> tag in Tags!.GetTags(spans).Where(t => t.Tag.SupportOutlining))
34+
{
35+
NormalizedSnapshotSpanCollection tagSpans = tag.Span.GetSpans(tag.Span.AnchorBuffer.CurrentSnapshot);
36+
37+
foreach (SnapshotSpan tagSpan in tagSpans)
38+
{
39+
string text = tagSpan.GetText().TrimEnd();
40+
SnapshotSpan span = new(tagSpan.Snapshot, tagSpan.Start, text.Length);
41+
yield return CreateTag(span, text, tag.Tag);
42+
}
43+
}
44+
}
45+
46+
private static TagSpan<IStructureTag> CreateTag(SnapshotSpan span, string text, TokenTag tag)
47+
{
48+
StructureTag structureTag = new(
49+
span.Snapshot,
50+
outliningSpan: span,
51+
guideLineSpan: span,
52+
guideLineHorizontalAnchor: span.Start,
53+
type: PredefinedStructureTagTypes.Structural,
54+
isCollapsible: true,
55+
collapsedForm: tag.GetOutliningText(text),
56+
collapsedHintForm: null);
57+
58+
return new TagSpan<IStructureTag>(span, structureTag);
59+
}
60+
}
61+
}
62+
#endif
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#if VS16 || VS17
2+
using System;
3+
using System.ComponentModel.Composition;
4+
using System.Linq;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Microsoft.VisualStudio.Language.Intellisense;
8+
using Microsoft.VisualStudio.Text;
9+
using Microsoft.VisualStudio.Text.Adornments;
10+
using Microsoft.VisualStudio.Text.Tagging;
11+
12+
namespace Community.VisualStudio.Toolkit
13+
{
14+
/// <summary>
15+
/// Shows hover tooltips (QuickInfo) based on TokenTags.
16+
/// </summary>
17+
public abstract class TokenQuickInfoBase : IAsyncQuickInfoSourceProvider
18+
{
19+
[Import] internal IBufferTagAggregatorFactoryService? _bufferTagAggregator = null;
20+
21+
/// <inheritdoc/>
22+
public IAsyncQuickInfoSource TryCreateQuickInfoSource(ITextBuffer buffer)
23+
{
24+
ITagAggregator<TokenTag>? tags = _bufferTagAggregator?.CreateTagAggregator<TokenTag>(buffer);
25+
return buffer.Properties.GetOrCreateSingletonProperty(() => new TokenQuickInfo(tags));
26+
}
27+
}
28+
29+
internal sealed class TokenQuickInfo : IAsyncQuickInfoSource
30+
{
31+
private readonly ITextBuffer? _buffer;
32+
private readonly ITagAggregator<TokenTag> _tags;
33+
34+
public TokenQuickInfo(ITagAggregator<TokenTag>? tags)
35+
{
36+
_tags = tags ?? throw new ArgumentNullException(nameof(tags));
37+
_buffer = tags.BufferGraph.TopBuffer;
38+
}
39+
40+
public async Task<QuickInfoItem?> GetQuickInfoItemAsync(IAsyncQuickInfoSession session, CancellationToken cancellationToken)
41+
{
42+
SnapshotPoint? triggerPoint = session.GetTriggerPoint(_buffer?.CurrentSnapshot);
43+
44+
if (triggerPoint.HasValue)
45+
{
46+
SnapshotSpan span = new(triggerPoint.Value.Snapshot, triggerPoint.Value.Position, 0);
47+
IMappingTagSpan<TokenTag> tag = _tags.GetTags(span).FirstOrDefault(t => t.Tag.GetTooltipAsync != null);
48+
49+
if (tag != null && tag.Tag.GetTooltipAsync != null)
50+
{
51+
object? tooltip = await tag.Tag.GetTooltipAsync(triggerPoint.Value);
52+
53+
if (tooltip == null)
54+
{
55+
return null;
56+
}
57+
58+
ContainerElement container = new(ContainerElementStyle.Stacked, tooltip);
59+
ITrackingSpan applicapleTo = _buffer!.CurrentSnapshot.CreateTrackingSpan(tag.Span.GetSpans(_buffer)[0], SpanTrackingMode.EdgeExclusive);
60+
61+
return new QuickInfoItem(applicapleTo, container);
62+
}
63+
}
64+
65+
return null;
66+
}
67+
68+
public void Dispose()
69+
{
70+
// This provider does not perform any cleanup.
71+
}
72+
}
73+
}
74+
#endif

0 commit comments

Comments
 (0)