提交 a9a85de3 编写于 作者: H HeeJae Chang

ported typescript syntax over LSP classification to Roslyn for experiment

tweaked a bit more so that we dont run anything on UI thread for syntactic classification

add latency tracker to the new tagger
上级 a0a99fa4
......@@ -33,7 +33,7 @@ public static void ReturnClassifiedSpanList(List<ClassifiedSpan> list)
s_spanCache.Enqueue(list);
}
public static void Convert(ClassificationTypeMap typeMap, ITextSnapshot snapshot, List<ClassifiedSpan> list, Action<ITagSpan<IClassificationTag>> addTag)
public static void Convert(ClassificationTypeMap typeMap, ITextSnapshot snapshot, IEnumerable<ClassifiedSpan> list, Action<ITagSpan<IClassificationTag>> addTag)
{
foreach (var classifiedSpan in list)
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.CodeAnalysis.Internal.Log;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Classification
{
internal sealed class RequestLatencyTracker : IDisposable
{
private readonly int _tick;
private readonly SyntacticLspLogger.RequestType _requestType;
public RequestLatencyTracker(SyntacticLspLogger.RequestType requestType)
{
_tick = Environment.TickCount;
_requestType = requestType;
}
public void Dispose()
{
var delta = Environment.TickCount - _tick;
SyntacticLspLogger.LogRequestLatency(_requestType, delta);
}
}
}
......@@ -4,8 +4,11 @@
using System.Collections.Generic;
using System.Threading;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServices.LiveShare;
using Task = System.Threading.Tasks.Task;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Classification
......@@ -16,7 +19,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Classification
/// So for the liveshare case, call into the <see cref="RoslynSyntaxClassificationService"/> to handle lexical classifications.
/// Otherwise forward to the original <see cref="IClassificationService"/> which will call into <see cref="ISyntaxClassificationService"/>
/// </summary>
internal class RoslynClassificationService : IClassificationService
internal class RoslynClassificationService : IClassificationService, IRemoteClassificationService
{
private readonly IClassificationService _originalService;
private readonly ISyntaxClassificationService _liveshareSyntaxClassificationService;
......@@ -49,5 +52,26 @@ public ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan
{
return _originalService.AdjustStaleClassification(text, classifiedSpan);
}
public async Task AddRemoteSyntacticClassificationsAsync(Document document, TextSpan textSpan, List<ClassifiedSpan> result, CancellationToken cancellationToken)
{
using (new RequestLatencyTracker(SyntacticLspLogger.RequestType.SyntacticTagger))
{
var internalService = (RoslynSyntaxClassificationService)_liveshareSyntaxClassificationService;
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
await internalService.AddRemoteClassificationsAsync(SyntaxClassificationsHandler.SyntaxClassificationsMethodName, document.FilePath, sourceText, textSpan, result.Add, cancellationToken).ConfigureAwait(false);
}
}
}
/// <summary>
/// special interface only used for <see cref="WellKnownExperimentNames.SyntacticExp_LiveShareTagger_Remote"/>
///
/// this let syntactic classification to run on remote side in bulk
/// </summary>
internal interface IRemoteClassificationService
{
Task AddRemoteSyntacticClassificationsAsync(Document document, TextSpan textSpan, List<ClassifiedSpan> result, CancellationToken cancellationToken);
}
}
......@@ -62,10 +62,16 @@ public void AddLexicalClassifications(SourceText text, TextSpan textSpan, ArrayB
{
_threadingContext.JoinableTaskFactory.Run(async () =>
{
await AddRemoteClassificationsAsync(LexicalClassificationsHandler.LexicalClassificationsMethodName, document.FilePath, text, textSpan, result, cancellationToken).ConfigureAwait(false);
await AddRemoteClassificationsAsync(LexicalClassificationsHandler.LexicalClassificationsMethodName, document.FilePath, text, textSpan, result.Add, cancellationToken).ConfigureAwait(false);
});
}
}
else if (ShouldRunExperiment(WellKnownExperimentNames.SyntacticExp_LiveShareTagger_Remote) ||
ShouldRunExperiment(WellKnownExperimentNames.SyntacticExp_LiveShareTagger_TextMate))
{
// do nothing here so that existing RoslynSyntacticTagger return nothing in this mode
return;
}
else
{
// Some other invalid flight. Just fallback to the regular service. Don't want to block the user based on an experimentation failure.
......@@ -78,7 +84,7 @@ public void AddSemanticClassifications(SemanticModel semanticModel, TextSpan tex
_threadingContext.JoinableTaskFactory.Run(async () =>
{
var sourceText = await semanticModel.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false);
await AddRemoteClassificationsAsync(RoslynMethods.ClassificationsName, semanticModel.SyntaxTree.FilePath, sourceText, textSpan, result, cancellationToken).ConfigureAwait(false);
await AddRemoteClassificationsAsync(RoslynMethods.ClassificationsName, semanticModel.SyntaxTree.FilePath, sourceText, textSpan, result.Add, cancellationToken).ConfigureAwait(false);
});
}
......@@ -92,7 +98,7 @@ public async Task AddSemanticClassificationsAsync(Document document, TextSpan te
}
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
await AddRemoteClassificationsAsync(RoslynMethods.ClassificationsName, document.FilePath, sourceText, textSpan, result, cancellationToken).ConfigureAwait(false);
await AddRemoteClassificationsAsync(RoslynMethods.ClassificationsName, document.FilePath, sourceText, textSpan, result.Add, cancellationToken).ConfigureAwait(false);
}
public void AddSyntacticClassifications(SyntaxTree syntaxTree, TextSpan textSpan, ArrayBuilder<ClassifiedSpan> result, CancellationToken cancellationToken)
......@@ -111,10 +117,16 @@ public void AddSyntacticClassifications(SyntaxTree syntaxTree, TextSpan textSpan
_threadingContext.JoinableTaskFactory.Run(async () =>
{
var sourceText = await syntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false);
await AddRemoteClassificationsAsync(SyntaxClassificationsHandler.SyntaxClassificationsMethodName, syntaxTree.FilePath, sourceText, textSpan, result, cancellationToken).ConfigureAwait(false);
await AddRemoteClassificationsAsync(SyntaxClassificationsHandler.SyntaxClassificationsMethodName, syntaxTree.FilePath, sourceText, textSpan, result.Add, cancellationToken).ConfigureAwait(false);
});
}
}
else if (ShouldRunExperiment(WellKnownExperimentNames.SyntacticExp_LiveShareTagger_Remote) ||
ShouldRunExperiment(WellKnownExperimentNames.SyntacticExp_LiveShareTagger_TextMate))
{
// do nothing here so that existing RoslynSyntacticTagger return nothing in this mode
return;
}
else
{
// Invalid experiment flight or older client. Since this is an experiment, just fallback.
......@@ -137,17 +149,25 @@ public ImmutableArray<ISyntaxClassifier> GetDefaultSyntaxClassifiers()
/// Only runs the experiment if the server provides the capability
/// and the experiment flight is enabled.
/// </summary>
private bool ShouldRunExperiment(string experimentName)
public bool ShouldRunExperiment(string experimentName)
{
if (_roslynLspClientServiceFactory.ServerCapabilities?.Experimental is RoslynExperimentalCapabilities experimentalCapabilities)
return ShouldRunExperiment(_roslynLspClientServiceFactory, _experimentationService, experimentName);
}
public static bool ShouldRunExperiment(
AbstractLspClientServiceFactory lspClientServiceFactory,
IExperimentationService experimentationService,
string experimentName)
{
if (lspClientServiceFactory.ServerCapabilities?.Experimental is RoslynExperimentalCapabilities experimentalCapabilities)
{
return experimentalCapabilities.SyntacticLspProvider && _experimentationService.IsExperimentEnabled(experimentName);
return experimentalCapabilities.SyntacticLspProvider && experimentationService.IsExperimentEnabled(experimentName);
}
return false;
}
private async Task AddRemoteClassificationsAsync(string classificationsType, string filePath, SourceText sourceText, TextSpan textSpan, ArrayBuilder<ClassifiedSpan> result, CancellationToken cancellationToken)
public async Task AddRemoteClassificationsAsync(string classificationsServiceName, string filePath, SourceText sourceText, TextSpan textSpan, Action<ClassifiedSpan> tagAdder, CancellationToken cancellationToken)
{
var lspClient = _roslynLspClientServiceFactory.ActiveLanguageServerClient;
if (lspClient == null)
......@@ -155,9 +175,7 @@ private async Task AddRemoteClassificationsAsync(string classificationsType, str
return;
}
// TODO - Move to roslyn client initialization once liveshare initialization is fixed.
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/964288
await _roslynLspClientServiceFactory.EnsureInitialized(cancellationToken).ConfigureAwait(false);
await EnsureInitializationAsync(cancellationToken).ConfigureAwait(false);
var classificationParams = new ClassificationParams
{
......@@ -165,7 +183,7 @@ private async Task AddRemoteClassificationsAsync(string classificationsType, str
Range = ProtocolConversions.TextSpanToRange(textSpan, sourceText)
};
var request = new LS.LspRequest<ClassificationParams, ClassificationSpan[]>(classificationsType);
var request = new LS.LspRequest<ClassificationParams, ClassificationSpan[]>(classificationsServiceName);
var classificationSpans = await lspClient.RequestAsync(request, classificationParams, cancellationToken).ConfigureAwait(false);
if (classificationSpans == null)
{
......@@ -185,27 +203,21 @@ private async Task AddRemoteClassificationsAsync(string classificationsType, str
var span = ProtocolConversions.RangeToTextSpan(classificationSpan.Range, sourceText);
if (span.End <= sourceText.Length)
{
result.Add(new ClassifiedSpan(classification, span));
tagAdder(new ClassifiedSpan(classification, span));
}
}
}
private class RequestLatencyTracker : IDisposable
public Task EnsureInitializationAsync(CancellationToken cancellationToken)
{
private readonly int _tick;
private readonly SyntacticLspLogger.RequestType _requestType;
public RequestLatencyTracker(SyntacticLspLogger.RequestType requestType)
{
_tick = Environment.TickCount;
_requestType = requestType;
}
return EnsureInitializationAsync(_roslynLspClientServiceFactory, cancellationToken);
}
public void Dispose()
{
var delta = Environment.TickCount - _tick;
SyntacticLspLogger.LogRequestLatency(_requestType, delta);
}
public static async Task EnsureInitializationAsync(AbstractLspClientServiceFactory lspClientServiceFactory, CancellationToken cancellationToken)
{
// TODO - Move to roslyn client initialization once liveshare initialization is fixed.
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/964288
await lspClientServiceFactory.EnsureInitializedAsync(cancellationToken).ConfigureAwait(false);
}
}
}
......@@ -46,6 +46,7 @@
<PackageReference Include="Microsoft.VisualStudio.Shell.15.0" Version="$(MicrosoftVisualStudioShell150Version)" />
<PackageReference Include="Microsoft.VisualStudio.Shell.Framework" Version="$(MicrosoftVisualStudioShellFrameworkVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Telemetry" Version="$(MicrosoftVisualStudioTelemetryVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Utilities" Version="$(MicrosoftVisualStudioUtilitiesVersion)" />
<PackageReference Include="StreamJsonRpc" Version="$(StreamJsonRpcVersion)" />
</ItemGroup>
......@@ -53,5 +54,4 @@
<PublicAPI Include="PublicAPI.Shipped.txt" />
<PublicAPI Include="PublicAPI.Unshipped.txt" />
</ItemGroup>
</Project>
......@@ -137,7 +137,7 @@ public void Dispose()
/// and made private once LiveShare fixes the race in client creation.
/// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/964288
/// </summary>
public async Task EnsureInitialized(CancellationToken cancellationToken)
public async Task EnsureInitializedAsync(CancellationToken cancellationToken)
{
if (ActiveLanguageServerClient == null)
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.Tagging;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Tagger
{
/// <summary>
/// this is almost straight copy from typescript for syntatic LSP experiement.
/// we won't attemp to change code to follow Roslyn style until we have result of the experiement
/// </summary>
internal abstract partial class AbstractAsyncClassificationTaggerProvider :
AsynchronousViewTaggerProvider<IClassificationTag>
{
protected override SpanTrackingMode SpanTrackingMode => SpanTrackingMode.EdgeInclusive;
protected AbstractAsyncClassificationTaggerProvider(
IForegroundNotificationService notificationService,
IAsynchronousOperationListenerProvider asyncListenerProvider,
IThreadingContext threadingContext)
: base(threadingContext, asyncListenerProvider.GetListener(FeatureAttribute.Classification), notificationService)
{
}
protected override async Task ProduceTagsAsync(
TaggerContext<IClassificationTag> context,
DocumentSnapshotSpan snapshotSpan,
int? caretPosition)
{
// Asked to classify when the document is no longer part of a workspace,
// this can happen when the document/project is being closed.
if (snapshotSpan.Document == null)
{
return;
}
// Classify documents in chunks of 50k. This allows more important work (like
// completion) to pre-empt classification on our semantic thread. It also keeps
// the size of the responses that we need to marshal from the script side smaller.
var document = snapshotSpan.Document;
var snapshot = snapshotSpan.SnapshotSpan.Snapshot;
var start = snapshotSpan.SnapshotSpan.Start.Position;
var end = snapshotSpan.SnapshotSpan.End.Position;
const int chunkSize = 50 * 1024;
for (var i = start; i < end; i += chunkSize)
{
var subSpan = Span.FromBounds(i, Math.Min(i + chunkSize, end));
await this.ProduceTagsAsync(
context,
new DocumentSnapshotSpan(document, new SnapshotSpan(snapshot, subSpan))).ConfigureAwait(false);
}
}
protected abstract Task ProduceTagsAsync(TaggerContext<IClassificationTag> context, DocumentSnapshotSpan snapshotSpan);
protected override IEnumerable<SnapshotSpan> GetSpansToTag(ITextView textView, ITextBuffer subjectBuffer)
{
if (textView == null)
{
var currentSnapshot = subjectBuffer.CurrentSnapshot;
return new[] { new SnapshotSpan(currentSnapshot, Span.FromBounds(0, currentSnapshot.Length)) };
}
// Determine the range of text that is visible in the view. Then map this down to our
// specific buffer. From that, determine the start/end line for our buffer that is in
// view. Then grow that a bit on either end (so the user can scroll up/down without
// noticing any classification) and return as the span we want to tag.
var visibleSpan = textView.TextViewLines.FormattedSpan;
var visibleSpansInBuffer = textView.BufferGraph.MapDownToBuffer(visibleSpan, SpanTrackingMode.EdgeInclusive, subjectBuffer);
var snapshot = subjectBuffer.CurrentSnapshot;
if (visibleSpansInBuffer.Count == 0)
{
// Roslyn expects a non-empty list of snapshot spans so if there
// are no visible spans in the buffer return the full snapshot.
// This can occur in an HTML file with script blocks when the user
// scrolls down and the script blocks are no longer visible in view.
return new[] { new SnapshotSpan(snapshot, 0, snapshot.Length) };
}
var visibleStart = visibleSpansInBuffer.First().Start;
var visibleEnd = visibleSpansInBuffer.Last().End;
var startLine = snapshot.GetLineNumberFromPosition(visibleStart);
var endLine = snapshot.GetLineNumberFromPosition(visibleEnd);
// Actually classify the region +/- about 50 lines. Make sure we stay within bounds
// of the file.
startLine = Math.Max(startLine - 50, 0);
endLine = Math.Min(endLine + 50, snapshot.LineCount - 1);
var start = snapshot.GetLineFromLineNumber(startLine).Start;
var end = snapshot.GetLineFromLineNumber(endLine).EndIncludingLineBreak;
var span = new SnapshotSpan(snapshot, Span.FromBounds(start, end));
return new[] { span };
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel.Composition;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Tagger
{
/// <summary>
/// this is almost straight copy from typescript for syntatic LSP experiement.
/// we won't attemp to change code to follow Roslyn style until we have result of the experiement
/// </summary>
[Export(typeof(ITaggerProvider))]
[PartCreationPolicy(CreationPolicy.Shared)]
[TagType(typeof(IClassificationTag))]
[ContentType(StringConstants.CSharpLspContentTypeName)]
[TextViewRole(PredefinedTextViewRoles.Document)]
internal sealed class CSharpModeAwareSyntacticClassificationTaggerProvider : ModeAwareSyntacticClassificationTaggerProvider
{
[ImportingConstructor]
public CSharpModeAwareSyntacticClassificationTaggerProvider(
Lazy<TextMateClassificationTaggerProvider> textMateProvider,
Lazy<SyntacticClassificationTaggerProvider> serverProvider,
CSharpLspClientServiceFactory lspClientServiceFactory)
: base(textMateProvider, serverProvider, lspClientServiceFactory)
{
}
}
[Export(typeof(ITaggerProvider))]
[PartCreationPolicy(CreationPolicy.Shared)]
[TagType(typeof(IClassificationTag))]
[ContentType(StringConstants.VBLspContentTypeName)]
[TextViewRole(PredefinedTextViewRoles.Document)]
internal sealed class VBModeAwareSyntacticClassificationTaggerProvider : ModeAwareSyntacticClassificationTaggerProvider
{
[ImportingConstructor]
public VBModeAwareSyntacticClassificationTaggerProvider(
Lazy<TextMateClassificationTaggerProvider> textMateProvider,
Lazy<SyntacticClassificationTaggerProvider> serverProvider,
VisualBasicLspClientServiceFactory lspClientServiceFactory)
: base(textMateProvider, serverProvider, lspClientServiceFactory)
{
}
}
internal class ModeAwareSyntacticClassificationTaggerProvider : ITaggerProvider
{
private readonly Lazy<TextMateClassificationTaggerProvider> _textMateProvider;
private readonly Lazy<SyntacticClassificationTaggerProvider> _serverProvider;
private readonly AbstractLspClientServiceFactory _lspClientServiceFactory;
public ModeAwareSyntacticClassificationTaggerProvider(
Lazy<TextMateClassificationTaggerProvider> textMateProvider,
Lazy<SyntacticClassificationTaggerProvider> serverProvider,
AbstractLspClientServiceFactory lspClientServiceFactory)
{
_textMateProvider = textMateProvider;
_serverProvider = serverProvider;
_lspClientServiceFactory = lspClientServiceFactory;
}
ITagger<TTag> ITaggerProvider.CreateTagger<TTag>(ITextBuffer buffer)
{
Debug.Assert(typeof(TTag).IsAssignableFrom(typeof(IClassificationTag)));
return CreateTagger(buffer) as ITagger<TTag>;
}
private ITagger<IClassificationTag> CreateTagger(ITextBuffer buffer)
{
return new ModeAwareTagger(
() => this._textMateProvider.Value.CreateTagger<IClassificationTag>(buffer),
() => this._serverProvider.Value.CreateTagger<IClassificationTag>(buffer),
SyntacticClassificationModeSelector.GetModeSelector(_lspClientServiceFactory, buffer));
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel.Composition;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Tagger
{
/// <summary>
/// this is almost straight copy from typescript for syntatic LSP experiement.
/// we won't attemp to change code to follow Roslyn style until we have result of the experiement
/// </summary>
[Export(typeof(IViewTaggerProvider))]
[PartCreationPolicy(CreationPolicy.Shared)]
[TagType(typeof(IClassificationTag))]
[ContentType(StringConstants.CSharpLspContentTypeName)]
[TextViewRole(PredefinedTextViewRoles.Document)]
internal sealed class CSharpModeAwareSyntacticClassificationViewTaggerProvider : ModeAwareSyntacticClassificationViewTaggerProvider
{
[ImportingConstructor]
public CSharpModeAwareSyntacticClassificationViewTaggerProvider(
Lazy<TextMateClassificationTaggerProvider> textMateProvider,
Lazy<SyntacticClassificationViewTaggerProvider> serverProvider,
CSharpLspClientServiceFactory lspClientServiceFactory)
: base(textMateProvider, serverProvider, lspClientServiceFactory)
{
}
}
[Export(typeof(IViewTaggerProvider))]
[PartCreationPolicy(CreationPolicy.Shared)]
[TagType(typeof(IClassificationTag))]
[ContentType(StringConstants.VBLspContentTypeName)]
[TextViewRole(PredefinedTextViewRoles.Document)]
internal sealed class VBModeAwareSyntacticClassificationViewTaggerProvider : ModeAwareSyntacticClassificationViewTaggerProvider
{
[ImportingConstructor]
public VBModeAwareSyntacticClassificationViewTaggerProvider(
Lazy<TextMateClassificationTaggerProvider> textMateProvider,
Lazy<SyntacticClassificationViewTaggerProvider> serverProvider,
VisualBasicLspClientServiceFactory lspClientServiceFactory)
: base(textMateProvider, serverProvider, lspClientServiceFactory)
{
}
}
internal class ModeAwareSyntacticClassificationViewTaggerProvider : IViewTaggerProvider
{
private readonly Lazy<TextMateClassificationTaggerProvider> _textMateProvider;
private readonly Lazy<SyntacticClassificationViewTaggerProvider> _serverProvider;
private readonly AbstractLspClientServiceFactory _lspClientServiceFactory;
[ImportingConstructor]
public ModeAwareSyntacticClassificationViewTaggerProvider(
Lazy<TextMateClassificationTaggerProvider> textMateProvider,
Lazy<SyntacticClassificationViewTaggerProvider> serverProvider,
AbstractLspClientServiceFactory lspClientServiceFactory)
{
_textMateProvider = textMateProvider;
_serverProvider = serverProvider;
_lspClientServiceFactory = lspClientServiceFactory;
}
ITagger<TTag> IViewTaggerProvider.CreateTagger<TTag>(ITextView textView, ITextBuffer buffer)
{
Debug.Assert(typeof(TTag).IsAssignableFrom(typeof(IClassificationTag)));
return CreateTagger(textView, buffer) as ITagger<TTag>;
}
private ITagger<IClassificationTag> CreateTagger(ITextView textView, ITextBuffer buffer)
{
return new ModeAwareTagger(
() => this._textMateProvider.Value.CreateTagger<IClassificationTag>(textView, buffer),
() => this._serverProvider.Value.CreateTagger<IClassificationTag>(textView, buffer),
SyntacticClassificationModeSelector.GetModeSelector(_lspClientServiceFactory, buffer));
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Tagger
{
/// <summary>
/// this is almost straight copy from typescript for syntatic LSP experiement.
/// we won't attemp to change code to follow Roslyn style until we have result of the experiement
/// </summary>
internal sealed class ModeAwareTagger : ITagger<IClassificationTag>, IDisposable
{
private readonly SyntacticClassificationModeSelector _modeSelector;
private readonly Lazy<ITagger<IClassificationTag>> _textMateTagger;
private readonly Lazy<ITagger<IClassificationTag>> _serverTagger;
public ModeAwareTagger(
Func<ITagger<IClassificationTag>> createTextMateTagger,
Func<ITagger<IClassificationTag>> createServerTagger,
SyntacticClassificationModeSelector modeSelector)
{
this._modeSelector = modeSelector;
this._textMateTagger = new Lazy<ITagger<IClassificationTag>>(() => CreateTagger(createTextMateTagger));
this._serverTagger = new Lazy<ITagger<IClassificationTag>>(() => CreateTagger(createServerTagger));
}
private ITagger<IClassificationTag> CreateTagger(Func<ITagger<IClassificationTag>> createTagger)
{
var tagger = createTagger();
tagger.TagsChanged += RaiseTagsChanged;
return tagger;
}
private void DisposeTagger(Lazy<ITagger<IClassificationTag>> tagger)
{
if (tagger.IsValueCreated)
{
tagger.Value.TagsChanged -= RaiseTagsChanged;
(tagger.Value as IDisposable)?.Dispose();
}
}
private void RaiseTagsChanged(object sender, SnapshotSpanEventArgs e)
{
if (ReferenceEquals(sender, this.CurrentTagger))
{
this.TagsChanged?.Invoke(sender, e);
}
}
public event EventHandler<SnapshotSpanEventArgs> /*ITagger<IClassificationTag>.*/TagsChanged;
private ITagger<IClassificationTag> CurrentTagger
{
get
{
var mode = this._modeSelector.GetMode();
switch (mode)
{
case SyntacticClassificationMode.TextMate:
return this._textMateTagger.Value;
case SyntacticClassificationMode.SyntaxLsp:
return this._serverTagger.Value;
case SyntacticClassificationMode.None:
return null;
default:
// shouldn't happen but don't crash. just use text mate
return this._textMateTagger.Value;
}
}
}
IEnumerable<ITagSpan<IClassificationTag>> ITagger<IClassificationTag>.GetTags(NormalizedSnapshotSpanCollection spans) =>
this.CurrentTagger?.GetTags(spans) ?? SpecializedCollections.EmptyEnumerable<ITagSpan<IClassificationTag>>();
void IDisposable.Dispose()
{
DisposeTagger(this._textMateTagger);
DisposeTagger(this._serverTagger);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Tagger
{
/// <remarks>
/// These values appear in telemetry and should not be changed.
/// </remarks>
internal enum SyntacticClassificationMode
{
None = 0,
/// <summary>Use TextMate for syntactic classification.</summary>
TextMate = 1,
/// <summary>Use the syntax-only for syntactic classification.</summary>
SyntaxLsp = 2,
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Classification;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Tagger
{
/// <summary>
/// this is almost straight copy from typescript for syntatic LSP experiement.
/// we won't attemp to change code to follow Roslyn style until we have result of the experiement
/// </summary>
internal sealed class SyntacticClassificationModeSelector
{
private readonly AbstractLspClientServiceFactory _lspClientServiceFactory;
private readonly ITextBuffer _buffer;
private int _modeCache = -1;
public SyntacticClassificationModeSelector(AbstractLspClientServiceFactory lspClientServiceFactory, ITextBuffer buffer)
{
_lspClientServiceFactory = lspClientServiceFactory;
_buffer = buffer;
}
public static SyntacticClassificationModeSelector GetModeSelector(AbstractLspClientServiceFactory lspClientServiceFactory, ITextBuffer buffer)
{
return new SyntacticClassificationModeSelector(lspClientServiceFactory, buffer);
}
public SyntacticClassificationMode GetMode()
{
if (_modeCache != -1)
{
return (SyntacticClassificationMode)_modeCache;
}
if (!Workspace.TryGetWorkspace(_buffer.AsTextContainer(), out var workspace))
{
return SyntacticClassificationMode.TextMate;
}
var experimentService = workspace.Services.GetService<IExperimentationService>();
if (experimentService == null)
{
return SyntacticClassificationMode.TextMate;
}
// this is most likely already ran, so it will be no-op
RoslynSyntaxClassificationService.EnsureInitializationAsync(_lspClientServiceFactory, CancellationToken.None).Wait(CancellationToken.None);
if (RoslynSyntaxClassificationService.ShouldRunExperiment(_lspClientServiceFactory, experimentService, WellKnownExperimentNames.SyntacticExp_LiveShareTagger_TextMate))
{
_modeCache = (int)SyntacticClassificationMode.TextMate;
}
else if (RoslynSyntaxClassificationService.ShouldRunExperiment(_lspClientServiceFactory, experimentService, WellKnownExperimentNames.SyntacticExp_LiveShareTagger_Remote))
{
_modeCache = (int)SyntacticClassificationMode.SyntaxLsp;
}
else
{
// tagger is disabled.
_modeCache = (int)SyntacticClassificationMode.None;
}
return (SyntacticClassificationMode)_modeCache;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor.Implementation.Classification;
using Microsoft.CodeAnalysis.Editor.Shared.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Classification;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Tagger
{
/// <summary>
/// this is almost straight copy from typescript for syntatic LSP experiement.
/// we won't attemp to change code to follow Roslyn style until we have result of the experiement
/// </summary>
internal sealed partial class SyntacticClassificationTaggerProvider
{
/// <summary>
/// Backing implementation for all <see cref="Tagger"/> instances for a single <see cref="ITextBuffer"/>.
/// </summary>
internal partial class TagComputer
{
private static readonly IEnumerable<ITagSpan<IClassificationTag>> EmptyTagSpanEnumerable = Array.Empty<ITagSpan<IClassificationTag>>();
private readonly ITextBuffer _textBuffer;
private readonly ClassificationTypeMap _typeMap;
private readonly WorkspaceRegistration _workspaceRegistration;
private readonly AsynchronousSerialWorkQueue _workQueue;
private readonly SyntacticClassificationTaggerProvider _taggerProvider;
private Workspace _workspace;
private TagSpanList<IClassificationTag> _lastProcessedTagList;
private int _taggerReferenceCount;
private int _isRequestPending; // int for Interlocked
public TagComputer(
ITextBuffer textBuffer,
ClassificationTypeMap typeMap,
IAsynchronousOperationListener asyncListener,
SyntacticClassificationTaggerProvider taggerProvider)
{
_textBuffer = textBuffer;
_typeMap = typeMap;
_taggerProvider = taggerProvider;
_workQueue = new AsynchronousSerialWorkQueue(taggerProvider._threadingContext, asyncListener);
_workspaceRegistration = Workspace.GetWorkspaceRegistration(textBuffer.AsTextContainer());
_workspaceRegistration.WorkspaceChanged += OnWorkspaceRegistrationChanged;
if (this._workspaceRegistration.Workspace != null)
{
ConnectToWorkspace(this._workspaceRegistration.Workspace);
}
}
private void OnWorkspaceRegistrationChanged(object sender, EventArgs e)
{
// We both try to connect synchronously, and register for workspace registration events.
// It's possible (particularly in tests), to connect in the startup path, but then get a
// previously scheduled, but not yet delivered event. Don't bother connecting to the
// same workspace again in that case.
var newWorkspace = this._workspaceRegistration.Workspace;
if (newWorkspace == this._workspace)
{
return;
}
DisconnectFromWorkspace();
if (newWorkspace != null)
{
ConnectToWorkspace(newWorkspace);
}
}
internal void IncrementReferenceCount()
{
this._taggerReferenceCount++;
}
internal void DecrementReferenceCountAndDisposeIfNecessary()
{
this._taggerReferenceCount--;
if (this._taggerReferenceCount == 0)
{
DisconnectFromWorkspace();
this._workspaceRegistration.WorkspaceChanged -= OnWorkspaceRegistrationChanged;
this._taggerProvider.DisconnectTagComputer(this._textBuffer);
}
}
private void ConnectToWorkspace(Workspace workspace)
{
workspace.WorkspaceChanged += this.OnWorkspaceChanged;
workspace.DocumentOpened += this.OnDocumentOpened;
workspace.DocumentActiveContextChanged += this.OnDocumentActiveContextChanged;
this._workspace = workspace;
EnqueueProcessSnapshotAsync();
}
public void DisconnectFromWorkspace()
{
var workspace = this._workspace;
if (workspace != null)
{
workspace.WorkspaceChanged -= this.OnWorkspaceChanged;
workspace.DocumentOpened -= this.OnDocumentOpened;
workspace.DocumentActiveContextChanged -= this.OnDocumentActiveContextChanged;
workspace = null;
}
}
private void EnqueueProcessSnapshotAsync(DocumentId updatedDocumentId = null)
{
var workspace = this._workspace;
if (workspace == null)
{
return;
}
var documentId = workspace.GetDocumentIdInCurrentContext(this._textBuffer.AsTextContainer());
if (documentId == null)
{
return;
}
// If the caller specified a DocumentId and it's the one for this buffer (in the current context),
// then there's nothing to do.
if (updatedDocumentId != null && updatedDocumentId != documentId)
{
// This is very common and not very interesting, so don't record it.
return;
}
if (Interlocked.CompareExchange(ref this._isRequestPending, 1, 0) != 0)
{
return;
}
_workQueue.EnqueueBackgroundTask(c => this.EnqueueProcessSnapshotWorkerAsync(documentId, c), GetType() + "." + nameof(EnqueueProcessSnapshotAsync) + ".1", CancellationToken.None);
}
private async Task EnqueueProcessSnapshotWorkerAsync(DocumentId documentId, CancellationToken cancellationToken)
{
var workspace = this._workspace;
if (workspace == null)
{
return;
}
// This is an essentially arbitrary version of the document - we basically just want the path.
var document = workspace.CurrentSolution.GetDocument(documentId);
if (document == null)
{
return;
}
// Changes after this point might not be incorporated into the server response, so
// allow scheduling of additional requests.
int wasRequestPending = Interlocked.Exchange(ref this._isRequestPending, 0);
Debug.Assert(wasRequestPending == 1);
var classificationService = document.Project.LanguageServices.GetService<IClassificationService>() as IRemoteClassificationService;
if (classificationService == null)
{
return;
}
var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var text = await tree.GetTextAsync(cancellationToken).ConfigureAwait(false);
var snapshot = text.FindCorrespondingEditorTextSnapshot();
if (snapshot == null)
{
return;
}
var classifiedSpans = ClassificationUtilities.GetOrCreateClassifiedSpanList();
await classificationService.AddRemoteSyntacticClassificationsAsync(document, TextSpan.FromBounds(0, tree.Length), classifiedSpans, cancellationToken).ConfigureAwait(false);
using var tagSpans = SharedPools.Default<List<ITagSpan<IClassificationTag>>>().GetPooledObject();
ClassificationUtilities.Convert(_typeMap, snapshot, classifiedSpans, tagSpans.Object.Add);
ClassificationUtilities.ReturnClassifiedSpanList(classifiedSpans);
var tagList = new TagSpanList<IClassificationTag>(tagSpans.Object);
Interlocked.Exchange(ref this._lastProcessedTagList, tagList);
this.TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(snapshot.GetFullSpan()));
}
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
public IEnumerable<ITagSpan<IClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans)
{
// A background thread might be updating lastProcessedTagList.
var tagList = Volatile.Read(ref this._lastProcessedTagList);
return tagList?.GetTags(spans) ?? EmptyTagSpanEnumerable;
}
private void OnDocumentActiveContextChanged(object sender, DocumentActiveContextChangedEventArgs args)
{
var workspace = this._workspace;
if (workspace != null && workspace == args.Solution.Workspace)
{
EnqueueProcessSnapshotAsync(args.NewActiveContextDocumentId);
}
}
private void OnDocumentOpened(object sender, DocumentEventArgs args)
{
EnqueueProcessSnapshotAsync(args.Document.Id);
}
private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs args)
{
// We're getting an event for a workspace we already disconnected from
var workspace = this._workspace;
if (args.NewSolution.Workspace != workspace)
{
// we are async so we are getting events from previous workspace we were associated with
// just ignore them
return;
}
switch (args.Kind)
{
case WorkspaceChangeKind.ProjectChanged:
{
var documentId = workspace.GetDocumentIdInCurrentContext(this._textBuffer.AsTextContainer());
if (documentId?.ProjectId == args.ProjectId)
{
EnqueueProcessSnapshotAsync();
}
break;
}
case WorkspaceChangeKind.DocumentChanged:
{
EnqueueProcessSnapshotAsync(args.DocumentId);
break;
}
}
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Tagger
{
/// <summary>
/// this is almost straight copy from typescript for syntatic LSP experiement.
/// we won't attemp to change code to follow Roslyn style until we have result of the experiement
/// </summary>
internal sealed partial class SyntacticClassificationTaggerProvider
{
internal sealed class TagSpanList<TTag> where TTag : ITag
{
public ITextSnapshot Snapshot { get; }
private readonly ImmutableArray<TagWrapper> tagWrappers;
public TagSpanList(IReadOnlyList<ITagSpan<TTag>> tagSpans)
{
if (tagSpans == null || tagSpans.Count == 0)
{
// Default field values are correct
return;
}
this.Snapshot = tagSpans[0].Span.Snapshot;
var builder = ImmutableArray.CreateBuilder<TagWrapper>(tagSpans.Count);
foreach (var tagSpan in GetSortedSpans(tagSpans))
{
Debug.Assert(!tagSpan.Span.IsEmpty);
Debug.Assert(tagSpan.Span.Snapshot == this.Snapshot);
builder.Add(new TagWrapper(tagSpan));
}
this.tagWrappers = builder.ToImmutable();
}
public IEnumerable<ITagSpan<TTag>> GetTags(NormalizedSnapshotSpanCollection spans)
{
var snapshot = this.Snapshot;
if (snapshot == null || spans.Count == 0)
{
return null;
}
var tags = new List<ITagSpan<TTag>>();
foreach (var span in spans)
{
var translatedSpan = span.TranslateTo(snapshot, SpanTrackingMode.EdgeExclusive);
int startIndex = GetStartIndex(translatedSpan.Start);
int endIndex = GetEndIndex(translatedSpan.End);
for (int i = startIndex; i < endIndex; i++)
{
var wrapper = this.tagWrappers[i];
if (wrapper.Tag.HasValue)
{
// There is no apparent benefit to translating the tag's span back into the requested snapshot.
tags.Add(wrapper.Tag.Value);
}
}
}
return tags;
}
private static IEnumerable<ITagSpan<TTag>> GetSortedSpans(IReadOnlyList<ITagSpan<TTag>> tagSpans)
{
for (int i = 1; i < tagSpans.Count; i++)
{
if (tagSpans[i - 1].Span.Start > tagSpans[i].Span.Start)
{
return tagSpans.OrderBy(t => t.Span.Start);
}
}
return tagSpans;
}
private int GetStartIndex(int position)
{
var target = new TagWrapper(Span.FromBounds(position, int.MaxValue));
var index = this.tagWrappers.BinarySearch(target, StartComparer.Instance);
int result;
if (index >= 0)
{
result = index;
}
else
{
result = ~index - 1;
if (result >= 0 && this.tagWrappers[result].Span.End <= position)
{
result++;
}
else
{
result = Math.Max(0, result);
}
}
return result;
}
private int GetEndIndex(int position)
{
var target = new TagWrapper(Span.FromBounds(0, position));
var index = this.tagWrappers.BinarySearch(target, EndComparer.Instance);
int result;
if (index >= 0)
{
result = index + 1;
}
else
{
result = ~index;
if (result < this.tagWrappers.Length && this.tagWrappers[result].Span.Start < position)
{
result = Math.Min(this.tagWrappers.Length, result + 1);
}
}
return result;
}
private sealed class StartComparer : IComparer<TagWrapper>
{
public static readonly IComparer<TagWrapper> Instance = new StartComparer();
private StartComparer() { }
int IComparer<TagWrapper>.Compare(TagWrapper x, TagWrapper y)
{
return x.Span.Start - y.Span.Start;
}
}
private sealed class EndComparer : IComparer<TagWrapper>
{
public static readonly IComparer<TagWrapper> Instance = new EndComparer();
private EndComparer() { }
int IComparer<TagWrapper>.Compare(TagWrapper x, TagWrapper y)
{
return x.Span.End - y.Span.End;
}
}
private struct TagWrapper
{
public readonly Span Span;
public readonly Optional<ITagSpan<TTag>> Tag;
public TagWrapper(Span span)
{
this.Span = span;
this.Tag = default;
}
public TagWrapper(ITagSpan<TTag> tag)
{
this.Span = tag.Span.Span;
this.Tag = new Optional<ITagSpan<TTag>>(tag);
}
public override string ToString()
{
return $"Wrapper [{Span.Start}, {Span.End}){(Tag.HasValue ? " " + Tag.Value.Tag : "")}";
}
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Tagger
{
/// <summary>
/// this is almost straight copy from typescript for syntatic LSP experiement.
/// we won't attemp to change code to follow Roslyn style until we have result of the experiement
/// </summary>
internal sealed partial class SyntacticClassificationTaggerProvider
{
private class Tagger : ITagger<IClassificationTag>, IDisposable
{
private TagComputer tagComputer;
public Tagger(TagComputer tagComputer)
{
this.tagComputer = tagComputer;
this.tagComputer.TagsChanged += OnTagsChanged;
}
public event EventHandler<SnapshotSpanEventArgs> /*ITagger<IClassificationTag>.*/TagsChanged;
IEnumerable<ITagSpan<IClassificationTag>> ITagger<IClassificationTag>.GetTags(NormalizedSnapshotSpanCollection spans)
{
if (this.tagComputer == null)
{
throw new ObjectDisposedException(nameof(SyntacticClassificationTaggerProvider) + "." + nameof(Tagger));
}
return this.tagComputer.GetTags(spans);
}
private void OnTagsChanged(object sender, SnapshotSpanEventArgs e)
{
TagsChanged?.Invoke(this, e);
}
public void Dispose()
{
if (this.tagComputer != null)
{
this.tagComputer.TagsChanged -= OnTagsChanged;
this.tagComputer.DecrementReferenceCountAndDisposeIfNecessary();
this.tagComputer = null;
}
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.ComponentModel.Composition;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Tagger
{
/// <remarks>
/// Adapted from Microsoft.CodeAnalysis.Editor.Implementation.Classification.SyntacticClassificationTaggerProvider.
/// The provider hands out taggers. Two taggers for the same buffer are backed by the same tag computer to avoid
/// duplicating work. This is important because tagger consumers may Dispose them and we only want to clean up
/// the corresponding computer when the last tagger for the buffer is disposed.
/// </remarks>
[Export]
[PartCreationPolicy(CreationPolicy.Shared)]
internal sealed partial class SyntacticClassificationTaggerProvider : ITaggerProvider
{
private readonly ClassificationTypeMap _typeMap;
private readonly IThreadingContext _threadingContext;
private readonly IAsynchronousOperationListener _listener;
private readonly ConditionalWeakTable<ITextBuffer, TagComputer> tagComputers = new ConditionalWeakTable<ITextBuffer, TagComputer>();
[ImportingConstructor]
public SyntacticClassificationTaggerProvider(
ClassificationTypeMap typeMap,
IThreadingContext threadingContext,
IAsynchronousOperationListenerProvider listenerProvider)
{
_typeMap = typeMap;
_threadingContext = threadingContext;
_listener = listenerProvider.GetListener(FeatureAttribute.Classification);
}
public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
{
if (!tagComputers.TryGetValue(buffer, out var tagComputer))
{
tagComputer = new TagComputer(buffer, _typeMap, _listener, this);
tagComputers.Add(buffer, tagComputer);
}
tagComputer.IncrementReferenceCount();
var tagger = new Tagger(tagComputer);
if (tagger is ITagger<T> typedTagger)
{
return typedTagger;
}
// Oops, we can't actually return this tagger, so just clean up
// (This seems like it should be impossible in practice, but Roslyn
// was hardened against it so we are as well.)
tagger.Dispose();
return null;
}
private void DisconnectTagComputer(ITextBuffer buffer)
{
tagComputers.Remove(buffer);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.ComponentModel.Composition;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Implementation.Classification;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.Tagging;
using Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Classification;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using TPL = System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Tagger
{
/// <summary>
/// this is almost straight copy from typescript for syntatic LSP experiement.
/// we won't attemp to change code to follow Roslyn style until we have result of the experiement
/// </summary>
[Export]
[PartCreationPolicy(CreationPolicy.Shared)]
internal sealed class SyntacticClassificationViewTaggerProvider : AbstractAsyncClassificationTaggerProvider
{
private readonly ClassificationTypeMap _typeMap;
[ImportingConstructor]
public SyntacticClassificationViewTaggerProvider(
IForegroundNotificationService notificationService,
ClassificationTypeMap typeMap,
IAsynchronousOperationListenerProvider asyncListenerProvider,
IThreadingContext threadingContext)
: base(notificationService, asyncListenerProvider, threadingContext)
{
_typeMap = typeMap;
}
protected override SpanTrackingMode SpanTrackingMode => SpanTrackingMode.EdgeExclusive;
protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer)
{
return TaggerEventSources.OnTextChanged(subjectBuffer, TaggerDelay.NearImmediate);
}
protected override async TPL.Task ProduceTagsAsync(
TaggerContext<IClassificationTag> taggerContext,
DocumentSnapshotSpan snapshotSpan)
{
// We should check this at the call site.
// This a safety check to make sure we do this when
// we introduce a new call site.
Debug.Assert(snapshotSpan.Document != null);
var document = snapshotSpan.Document;
var cancellationToken = taggerContext.CancellationToken;
var classificationService = document.Project.LanguageServices.GetService<IClassificationService>() as IRemoteClassificationService;
if (classificationService == null)
{
return;
}
var classifiedSpans = ClassificationUtilities.GetOrCreateClassifiedSpanList();
var tagSpan = TextSpan.FromBounds(snapshotSpan.SnapshotSpan.Start, snapshotSpan.SnapshotSpan.End);
await classificationService.AddRemoteSyntacticClassificationsAsync(document, tagSpan, classifiedSpans, cancellationToken).ConfigureAwait(false);
ClassificationUtilities.Convert(_typeMap, snapshotSpan.SnapshotSpan.Snapshot, classifiedSpans, taggerContext.AddTag);
ClassificationUtilities.ReturnClassifiedSpanList(classifiedSpans);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
namespace Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Tagger
{
// We implement both ITaggerProvider and IViewTaggerProvider. The former is so that
// we can classify buffers not associated with views (VS actually uses these in cases like the
// scroll bar "scroll map" feature. The latter is so that we are told about text views, and
// thus can register their format maps with the FormatMappingChangedWatcher.
[Export]
[PartCreationPolicy(CreationPolicy.Shared)]
internal sealed class TextMateClassificationTaggerProvider : ITaggerProvider, IViewTaggerProvider
{
private readonly ICommonEditorAssetServiceFactory _assetServiceFactory;
[ImportingConstructor]
public TextMateClassificationTaggerProvider(ICommonEditorAssetServiceFactory assetServiceFactory)
{
_assetServiceFactory = assetServiceFactory;
}
public ITagger<T> CreateTagger<T>(ITextView view, ITextBuffer buffer) where T : ITag
{
return CreateTagger<T>(buffer);
}
public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
{
buffer.Properties.GetOrCreateSingletonProperty("defaultFileExtensionForGrammar", GetDefaultFileExtension);
var factory = _assetServiceFactory.GetOrCreate(buffer);
var taggerProvider = factory.FindAsset<ITaggerProvider>((metadata) => metadata.TagTypes.Any(tagType => typeof(T).IsAssignableFrom(tagType)));
var tagger = taggerProvider?.CreateTagger<T>(buffer);
return tagger;
string GetDefaultFileExtension()
{
if (!TryGetFilePathFromTextDocument(buffer, out var filePath))
{
// nothing we can do without filepath
return null;
}
return Path.GetExtension(filePath);
}
static bool TryGetFilePathFromTextDocument(ITextBuffer buffer, out string filePath)
{
if (buffer.Properties.TryGetProperty<ITextDocument>(typeof(ITextDocument), out var textDocument))
{
filePath = textDocument.FilePath;
return true;
}
filePath = null;
return false;
}
}
}
}
......@@ -36,5 +36,7 @@ internal static class WellKnownExperimentNames
// Syntactic LSP experiment treatments.
public const string SyntacticExp_Remote = "RoslynLsp";
public const string SyntacticExp_Local = "RoslynLocal";
public const string SyntacticExp_LiveShareTagger_Remote = "RoslynLsp_Tagger";
public const string SyntacticExp_LiveShareTagger_TextMate = "RoslynTextMate_Tagger";
}
}
......@@ -53,6 +53,8 @@ public static StatisticResult GetStatistics(List<int> values)
return new StatisticResult(max, min, median, mean, range, mode, values.Count);
}
public bool IsEmpty => _map.IsEmpty;
public IEnumerator<KeyValuePair<object, T>> GetEnumerator()
{
return _map.GetEnumerator();
......
......@@ -463,5 +463,6 @@ internal enum FunctionId
Liveshare_UnknownCodeAction,
Liveshare_LexicalClassifications,
Liveshare_SyntacticClassifications,
Liveshare_SyntacticTagger,
}
}
......@@ -8,11 +8,13 @@ internal sealed class SyntacticLspLogger
{
private static readonly HistogramLogAggregator s_lexicalClassificationsLogAggregator = new HistogramLogAggregator(bucketSize: 100, maxBucketValue: 5000);
private static readonly HistogramLogAggregator s_syntacticClassificationsRemoteAggregator = new HistogramLogAggregator(bucketSize: 100, maxBucketValue: 5000);
private static readonly HistogramLogAggregator s_syntacticTaggerRemoteAggregator = new HistogramLogAggregator(bucketSize: 100, maxBucketValue: 5000);
internal enum RequestType
{
LexicalClassifications,
SyntacticClassifications,
SyntacticTagger,
}
internal static void LogRequestLatency(RequestType requestType, decimal latency)
......@@ -25,6 +27,9 @@ internal static void LogRequestLatency(RequestType requestType, decimal latency)
case RequestType.SyntacticClassifications:
s_syntacticClassificationsRemoteAggregator.IncreaseCount(latency);
break;
case RequestType.SyntacticTagger:
s_syntacticTaggerRemoteAggregator.IncreaseCount(latency);
break;
default:
break;
......@@ -33,23 +38,21 @@ internal static void LogRequestLatency(RequestType requestType, decimal latency)
internal static void ReportTelemetry()
{
Logger.Log(FunctionId.Liveshare_LexicalClassifications, KeyValueLogMessage.Create(m =>
{
foreach (var kv in s_lexicalClassificationsLogAggregator)
{
var info = $"{RequestType.LexicalClassifications}.{kv.Key}";
m[info] = kv.Value.GetCount();
}
}));
ReportTelemetry(FunctionId.Liveshare_LexicalClassifications, RequestType.LexicalClassifications.ToString(), s_lexicalClassificationsLogAggregator);
ReportTelemetry(FunctionId.Liveshare_SyntacticClassifications, RequestType.SyntacticClassifications.ToString(), s_syntacticClassificationsRemoteAggregator);
ReportTelemetry(FunctionId.Liveshare_SyntacticTagger, RequestType.SyntacticTagger.ToString(), s_syntacticTaggerRemoteAggregator);
Logger.Log(FunctionId.Liveshare_SyntacticClassifications, KeyValueLogMessage.Create(m =>
static void ReportTelemetry(FunctionId functionId, string typeName, HistogramLogAggregator aggregator)
{
foreach (var kv in s_syntacticClassificationsRemoteAggregator)
Logger.Log(functionId, KeyValueLogMessage.Create(m =>
{
var info = $"{RequestType.SyntacticClassifications}.{kv.Key}";
m[info] = kv.Value.GetCount();
}
}));
foreach (var kv in aggregator)
{
var info = $"{typeName}.{kv.Key}";
m[info] = kv.Value.GetCount();
}
}));
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册