提交 0e0e61e8 编写于 作者: C CyrusNajmabadi

Merge branch 'master' into convertIfToSwitchOnlyForComplexCases

// 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;
namespace Microsoft.CodeAnalysis
{
/// <summary>
......@@ -33,7 +35,14 @@ public bool HasValue
/// <summary>
/// Gets the value of the current object.
/// </summary>
/// <returns></returns>
/// <remarks>
/// <para>Unlike <see cref="Nullable{T}.Value"/>, this property does not throw an exception when
/// <see cref="HasValue"/> is <see langword="false"/>.</para>
/// </remarks>
/// <returns>
/// <para>The value if <see cref="HasValue"/> is <see langword="true"/>; otherwise, the default value for type
/// <typeparamref name="T"/>.</para>
/// </returns>
public T Value
{
get { return _value; }
......
......@@ -251,5 +251,22 @@ public async Task TestAfterOutKeywordInParameter()
@"class C {
void M1(out $$");
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestVarPatternInSwitch()
{
await VerifyKeywordAsync(AddInsideMethod(
@"switch(o)
{
case $$
}
"));
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestVarPatternInIs()
{
await VerifyKeywordAsync(AddInsideMethod("var b = o is $$ "));
}
}
}
......@@ -105,6 +105,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="FindUsages\AbstractFindUsagesService.cs" />
<Compile Include="FindUsages\ClassifiedSpansAndHighlightSpan.cs" />
<Compile Include="FindUsages\AbstractFindUsagesService.DefinitionTrackingContext.cs" />
<Compile Include="FindUsages\FindUsagesHelpers.cs" />
<Compile Include="FindUsages\FindUsagesContext.cs" />
......
......@@ -32,11 +32,11 @@ public DefinitionTrackingContext(IFindUsagesContext underlyingContext)
public CancellationToken CancellationToken
=> _underlyingContext.CancellationToken;
public void ReportMessage(string message)
=> _underlyingContext.ReportMessage(message);
public Task ReportMessageAsync(string message)
=> _underlyingContext.ReportMessageAsync(message);
public void SetSearchTitle(string title)
=> _underlyingContext.SetSearchTitle(title);
public Task SetSearchTitleAsync(string title)
=> _underlyingContext.SetSearchTitleAsync(title);
public Task OnReferenceFoundAsync(SourceReferenceItem reference)
=> _underlyingContext.OnReferenceFoundAsync(reference);
......
// 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.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.FindSymbols;
......@@ -30,9 +30,13 @@ private class FindLiteralsProgressAdapter : IStreamingFindLiteralReferencesProgr
_definition = definition;
}
public Task OnReferenceFoundAsync(Document document, TextSpan span)
=> _context.OnReferenceFoundAsync(new SourceReferenceItem(
_definition, new DocumentSpan(document, span), isWrittenTo: false));
public async Task OnReferenceFoundAsync(Document document, TextSpan span)
{
var documentSpan = await ClassifiedSpansAndHighlightSpan.GetClassifiedDocumentSpanAsync(
document, span, _context.CancellationToken).ConfigureAwait(false);
await _context.OnReferenceFoundAsync(new SourceReferenceItem(
_definition, documentSpan, isWrittenTo: false)).ConfigureAwait(false);
}
public Task ReportProgressAsync(int current, int maximum)
=> _context.ReportProgressAsync(current, maximum);
......@@ -56,16 +60,15 @@ private class FindReferencesProgressAdapter : ForegroundThreadAffinitizedObject,
/// This dictionary allows us to make that mapping once and then keep it around for
/// all future callbacks.
/// </summary>
private readonly ConcurrentDictionary<ISymbol, DefinitionItem> _definitionToItem =
new ConcurrentDictionary<ISymbol, DefinitionItem>(MetadataUnifyingEquivalenceComparer.Instance);
private readonly Dictionary<ISymbol, DefinitionItem> _definitionToItem =
new Dictionary<ISymbol, DefinitionItem>(MetadataUnifyingEquivalenceComparer.Instance);
private readonly Func<ISymbol, DefinitionItem> _definitionFactory;
private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1);
public FindReferencesProgressAdapter(Solution solution, IFindUsagesContext context)
{
_solution = solution;
_context = context;
_definitionFactory = s => s.ToDefinitionItem(solution, includeHiddenLocations: false);
}
// Do nothing functions. The streaming far service doesn't care about
......@@ -83,14 +86,26 @@ public FindReferencesProgressAdapter(Solution solution, IFindUsagesContext conte
// used by the FAR engine to the INavigableItems used by the streaming FAR
// feature.
private DefinitionItem GetDefinitionItem(SymbolAndProjectId definition)
private async Task<DefinitionItem> GetDefinitionItemAsync(SymbolAndProjectId definition)
{
return _definitionToItem.GetOrAdd(definition.Symbol, _definitionFactory);
using (await _gate.DisposableWaitAsync(_context.CancellationToken).ConfigureAwait(false))
{
if (!_definitionToItem.TryGetValue(definition.Symbol, out var definitionItem))
{
definitionItem = await definition.Symbol.ToClassifiedDefinitionItemAsync(
_solution, includeHiddenLocations: false, cancellationToken: _context.CancellationToken).ConfigureAwait(false);
_definitionToItem[definition.Symbol] = definitionItem;
}
return definitionItem;
}
}
public Task OnDefinitionFoundAsync(SymbolAndProjectId definition)
public async Task OnDefinitionFoundAsync(SymbolAndProjectId definition)
{
return _context.OnDefinitionFoundAsync(GetDefinitionItem(definition));
var definitionItem = await GetDefinitionItemAsync(definition).ConfigureAwait(false);
await _context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false);
}
public async Task OnReferenceFoundAsync(SymbolAndProjectId definition, ReferenceLocation location)
......@@ -101,8 +116,10 @@ public async Task OnReferenceFoundAsync(SymbolAndProjectId definition, Reference
return;
}
var referenceItem = location.TryCreateSourceReferenceItem(
GetDefinitionItem(definition), includeHiddenLocations: false);
var definitionItem = await GetDefinitionItemAsync(definition).ConfigureAwait(false);
var referenceItem = await location.TryCreateSourceReferenceItemAsync(
definitionItem, includeHiddenLocations: false,
cancellationToken: _context.CancellationToken).ConfigureAwait(false);
if (referenceItem != null)
{
......
......@@ -14,13 +14,16 @@ namespace Microsoft.CodeAnalysis.Editor.FindUsages
{
internal abstract partial class AbstractFindUsagesService : IFindUsagesService
{
public async Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context)
public async Task FindImplementationsAsync(
Document document, int position, IFindUsagesContext context)
{
var cancellationToken = context.CancellationToken;
var tuple = await FindUsagesHelpers.FindImplementationsAsync(
document, position, context.CancellationToken).ConfigureAwait(false);
document, position, cancellationToken).ConfigureAwait(false);
if (tuple == null)
{
context.ReportMessage(EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret);
await context.ReportMessageAsync(
EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret).ConfigureAwait(false);
return;
}
......@@ -28,18 +31,19 @@ public async Task FindImplementationsAsync(Document document, int position, IFin
if (message != null)
{
context.ReportMessage(message);
await context.ReportMessageAsync(message).ConfigureAwait(false);
return;
}
context.SetSearchTitle(string.Format(EditorFeaturesResources._0_implementations,
FindUsagesHelpers.GetDisplayName(tuple.Value.symbol)));
await context.SetSearchTitleAsync(
string.Format(EditorFeaturesResources._0_implementations,
FindUsagesHelpers.GetDisplayName(tuple.Value.symbol))).ConfigureAwait(false);
var project = tuple.Value.project;
foreach (var implementation in tuple.Value.implementations)
{
var definitionItem = implementation.ToDefinitionItem(
project.Solution, includeHiddenLocations: false);
var definitionItem = await implementation.ToClassifiedDefinitionItemAsync(
project.Solution, includeHiddenLocations: false, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false);
}
}
......@@ -125,9 +129,8 @@ public async Task FindImplementationsAsync(Document document, int position, IFin
public static async Task FindSymbolReferencesAsync(
IFindUsagesContext context, ISymbol symbol, Project project, CancellationToken cancellationToken)
{
context.SetSearchTitle(string.Format(EditorFeaturesResources._0_references,
FindUsagesHelpers.GetDisplayName(symbol)));
await context.SetSearchTitleAsync(string.Format(EditorFeaturesResources._0_references,
FindUsagesHelpers.GetDisplayName(symbol))).ConfigureAwait(false);
var progressAdapter = new FindReferencesProgressAdapter(project.Solution, context);
// Now call into the underlying FAR engine to find reference. The FAR
......@@ -193,7 +196,7 @@ public async Task FindImplementationsAsync(Document document, int position, IFin
}
var searchTitle = string.Format(EditorFeaturesResources._0_references, title);
context.SetSearchTitle(searchTitle);
await context.SetSearchTitleAsync(searchTitle).ConfigureAwait(false);
var solution = document.Project.Solution;
......
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Editor.FindUsages
{
internal struct ClassifiedSpansAndHighlightSpan
{
private const string Key = nameof(ClassifiedSpansAndHighlightSpan);
public readonly ImmutableArray<ClassifiedSpan> ClassifiedSpans;
public readonly TextSpan HighlightSpan;
public ClassifiedSpansAndHighlightSpan(
ImmutableArray<ClassifiedSpan> classifiedSpans,
TextSpan highlightSpan)
{
ClassifiedSpans = classifiedSpans;
HighlightSpan = highlightSpan;
}
public static async Task<DocumentSpan> GetClassifiedDocumentSpanAsync(
Document document, TextSpan sourceSpan, CancellationToken cancellationToken)
{
var classifiedSpans = await ClassifyAsync(
document, sourceSpan, cancellationToken).ConfigureAwait(false);
var properties = ImmutableDictionary<string, object>.Empty.Add(Key, classifiedSpans);
return new DocumentSpan(document, sourceSpan, properties);
}
public static async Task<ClassifiedSpansAndHighlightSpan> ClassifyAsync(
DocumentSpan documentSpan, CancellationToken cancellationToken)
{
// If the document span is providing us with the classified spans up front, then we
// can just use that. Otherwise, go back and actually classify the text for the line
// the document span is on.
if (documentSpan.Properties.TryGetValue(Key, out var value))
{
return (ClassifiedSpansAndHighlightSpan)value;
}
return await ClassifyAsync(
documentSpan.Document, documentSpan.SourceSpan, cancellationToken).ConfigureAwait(false);
}
private static async Task<ClassifiedSpansAndHighlightSpan> ClassifyAsync(
Document document, TextSpan sourceSpan, CancellationToken cancellationToken)
{
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var narrowSpan = sourceSpan;
var lineSpan = GetLineSpanForReference(sourceText, narrowSpan);
var taggedLineParts = await GetTaggedTextForDocumentRegionAsync(
document, narrowSpan, lineSpan, cancellationToken).ConfigureAwait(false);
return taggedLineParts;
}
private static TextSpan GetLineSpanForReference(SourceText sourceText, TextSpan referenceSpan)
{
var sourceLine = sourceText.Lines.GetLineFromPosition(referenceSpan.Start);
var firstNonWhitespacePosition = sourceLine.GetFirstNonWhitespacePosition().Value;
return TextSpan.FromBounds(firstNonWhitespacePosition, sourceLine.End);
}
private static async Task<ClassifiedSpansAndHighlightSpan> GetTaggedTextForDocumentRegionAsync(
Document document, TextSpan narrowSpan, TextSpan widenedSpan, CancellationToken cancellationToken)
{
var highlightSpan = new TextSpan(
start: narrowSpan.Start - widenedSpan.Start,
length: narrowSpan.Length);
var classifiedSpans = await GetClassifiedSpansAsync(
document, narrowSpan, widenedSpan, cancellationToken).ConfigureAwait(false);
return new ClassifiedSpansAndHighlightSpan(classifiedSpans, highlightSpan);
}
private static async Task<ImmutableArray<ClassifiedSpan>> GetClassifiedSpansAsync(
Document document, TextSpan narrowSpan, TextSpan widenedSpan, CancellationToken cancellationToken)
{
var classificationService = document.GetLanguageService<IEditorClassificationService>();
if (classificationService == null)
{
// For languages that don't expose a classification service, we show the entire
// item as plain text. Break the text into three spans so that we can properly
// highlight the 'narrow-span' later on when we display the item.
return ImmutableArray.Create(
new ClassifiedSpan(ClassificationTypeNames.Text, TextSpan.FromBounds(widenedSpan.Start, narrowSpan.Start)),
new ClassifiedSpan(ClassificationTypeNames.Text, narrowSpan),
new ClassifiedSpan(ClassificationTypeNames.Text, TextSpan.FromBounds(narrowSpan.End, widenedSpan.End)));
}
// Call out to the individual language to classify the chunk of text around the
// reference. We'll get both the syntactic and semantic spans for this region.
// Because the semantic tags may override the semantic ones (for example,
// "DateTime" might be syntactically an identifier, but semantically a struct
// name), we'll do a later merging step to get the final correct list of
// classifications. For tagging, normally the editor handles this. But as
// we're producing the list of Inlines ourselves, we have to handles this here.
var syntaxSpans = ListPool<ClassifiedSpan>.Allocate();
var semanticSpans = ListPool<ClassifiedSpan>.Allocate();
try
{
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
await classificationService.AddSyntacticClassificationsAsync(
document, widenedSpan, syntaxSpans, cancellationToken).ConfigureAwait(false);
await classificationService.AddSemanticClassificationsAsync(
document, widenedSpan, semanticSpans, cancellationToken).ConfigureAwait(false);
var classifiedSpans = MergeClassifiedSpans(
syntaxSpans, semanticSpans, widenedSpan, sourceText);
return classifiedSpans;
}
finally
{
ListPool<ClassifiedSpan>.Free(syntaxSpans);
ListPool<ClassifiedSpan>.Free(semanticSpans);
}
}
private static ImmutableArray<ClassifiedSpan> MergeClassifiedSpans(
List<ClassifiedSpan> syntaxSpans, List<ClassifiedSpan> semanticSpans,
TextSpan widenedSpan, SourceText sourceText)
{
// The spans produced by the language services may not be ordered
// (indeed, this happens with semantic classification as different
// providers produce different results in an arbitrary order). Order
// them first before proceeding.
Order(syntaxSpans);
Order(semanticSpans);
// It's possible for us to get classified spans that occur *before*
// or after the span we want to present. This happens because the calls to
// AddSyntacticClassificationsAsync and AddSemanticClassificationsAsync
// may return more spans than the range asked for. While bad form,
// it's never been a requirement that implementation not do that.
// For example, the span may be the non-full-span of a node, but the
// classifiers may still return classifications for leading/trailing
// trivia even if it's out of the bounds of that span.
//
// To deal with that, we adjust all spans so that they don't go outside
// of the range we care about.
AdjustSpans(syntaxSpans, widenedSpan);
AdjustSpans(semanticSpans, widenedSpan);
// The classification service will only produce classifications for
// things it knows about. i.e. there will be gaps in what it produces.
// Fill in those gaps so we have *all* parts of the span
// classified properly.
var filledInSyntaxSpans = ArrayBuilder<ClassifiedSpan>.GetInstance();
var filledInSemanticSpans = ArrayBuilder<ClassifiedSpan>.GetInstance();
try
{
FillInClassifiedSpanGaps(sourceText, widenedSpan.Start, syntaxSpans, filledInSyntaxSpans);
FillInClassifiedSpanGaps(sourceText, widenedSpan.Start, semanticSpans, filledInSemanticSpans);
// Now merge the lists together, taking all the results from syntaxParts
// unless they were overridden by results in semanticParts.
return MergeParts(filledInSyntaxSpans, filledInSemanticSpans);
}
finally
{
filledInSyntaxSpans.Free();
filledInSemanticSpans.Free();
}
}
private static void Order(List<ClassifiedSpan> syntaxSpans)
=> syntaxSpans.Sort((s1, s2) => s1.TextSpan.Start - s2.TextSpan.Start);
private static void AdjustSpans(List<ClassifiedSpan> spans, TextSpan widenedSpan)
{
for (var i = 0; i < spans.Count; i++)
{
var span = spans[i];
// Make sure the span actually intersects 'widenedSpan'. If it
// does not, just put in an empty length span. It will get ignored later
// when we walk through this list.
var intersection = span.TextSpan.Intersection(widenedSpan);
if (i > 0 && intersection != null)
{
if (spans[i - 1].TextSpan.End > intersection.Value.Start)
{
// This span isn't strictly after the previous span. Ignore it.
intersection = null;
}
}
var newSpan = new ClassifiedSpan(span.ClassificationType,
intersection ?? new TextSpan());
spans[i] = newSpan;
}
}
private static void FillInClassifiedSpanGaps(
SourceText sourceText, int startPosition,
List<ClassifiedSpan> classifiedSpans, ArrayBuilder<ClassifiedSpan> result)
{
foreach (var span in classifiedSpans)
{
// Ignore empty spans. We can get those when the classification service
// returns spans outside of the range of the span we asked to classify.
if (span.TextSpan.Length == 0)
{
continue;
}
// If there is space between this span and the last one, then add a space.
if (startPosition != span.TextSpan.Start)
{
result.Add(new ClassifiedSpan(ClassificationTypeNames.Text,
TextSpan.FromBounds(
startPosition, span.TextSpan.Start)));
}
result.Add(span);
startPosition = span.TextSpan.End;
}
}
private static ImmutableArray<ClassifiedSpan> MergeParts(
ArrayBuilder<ClassifiedSpan> syntaxParts,
ArrayBuilder<ClassifiedSpan> semanticParts)
{
// Take all the syntax parts. However, if any have been overridden by a
// semantic part, then choose that one.
var finalParts = ArrayBuilder<ClassifiedSpan>.GetInstance();
var lastReplacementIndex = 0;
for (int i = 0, n = syntaxParts.Count; i < n; i++)
{
var syntaxPartAndSpan = syntaxParts[i];
// See if we can find a semantic part to replace this syntax part.
var replacementIndex = semanticParts.FindIndex(
lastReplacementIndex, t => t.TextSpan == syntaxPartAndSpan.TextSpan);
// Take the semantic part if it's just 'text'. We want to keep it if
// the semantic classifier actually produced an interesting result
// (as opposed to it just being a 'gap' classification).
var part = replacementIndex >= 0 && !IsClassifiedAsText(semanticParts[replacementIndex])
? semanticParts[replacementIndex]
: syntaxPartAndSpan;
finalParts.Add(part);
if (replacementIndex >= 0)
{
// If we found a semantic replacement, update the lastIndex.
// That way we can start searching from that point instead
// of checking all the elements each time.
lastReplacementIndex = replacementIndex + 1;
}
}
return finalParts.ToImmutableAndFree();
}
private static bool IsClassifiedAsText(ClassifiedSpan partAndSpan)
{
// Don't take 'text' from the semantic parts. We'll get those for the
// spaces between the actual interesting semantic spans, and we don't
// want them to override actual good syntax spans.
return partAndSpan.ClassificationType == ClassificationTypeNames.Text;
}
}
}
\ No newline at end of file
......@@ -15,13 +15,9 @@ protected FindUsagesContext()
{
}
public virtual void ReportMessage(string message)
{
}
public virtual Task ReportMessageAsync(string message) => SpecializedTasks.EmptyTask;
public virtual void SetSearchTitle(string title)
{
}
public virtual Task SetSearchTitleAsync(string title) => SpecializedTasks.EmptyTask;
public virtual Task OnCompletedAsync() => SpecializedTasks.EmptyTask;
......
......@@ -4,6 +4,7 @@
using System.Composition;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Features.RQName;
using Microsoft.CodeAnalysis.FindSymbols;
......@@ -36,10 +37,36 @@ internal class DefaultDefinitionsAndReferencesFactory : IDefinitionsAndReference
internal static class DefinitionItemExtensions
{
public static DefinitionItem ToDefinitionItem(
public static DefinitionItem ToNonClassifiedDefinitionItem(
this ISymbol definition,
Solution solution,
bool includeHiddenLocations)
{
// Because we're passing in 'false' for 'includeClassifiedSpans', this won't ever have
// to actually do async work. This is because the only asynchrony is when we are trying
// to compute the classified spans for the locations of the definition. So it's totally
// fine to pass in CancellationToken.None and block on the result.
return ToDefinitionItemAsync(definition, solution, includeHiddenLocations,
includeClassifiedSpans: false, cancellationToken: CancellationToken.None).Result;
}
public static Task<DefinitionItem> ToClassifiedDefinitionItemAsync(
this ISymbol definition,
Solution solution,
bool includeHiddenLocations,
CancellationToken cancellationToken)
{
return ToDefinitionItemAsync(definition, solution,
includeHiddenLocations, includeClassifiedSpans: true, cancellationToken: cancellationToken);
}
private static async Task<DefinitionItem> ToDefinitionItemAsync(
this ISymbol definition,
Solution solution,
bool includeHiddenLocations,
bool includeClassifiedSpans,
CancellationToken cancellationToken)
{
// Ensure we're working with the original definition for the symbol. I.e. When we're
// creating definition items, we want to create them for types like Dictionary<TKey,TValue>
......@@ -84,7 +111,12 @@ internal static class DefinitionItemExtensions
var document = solution.GetDocument(location.SourceTree);
if (document != null)
{
sourceLocations.Add(new DocumentSpan(document, location.SourceSpan));
var documentLocation = !includeClassifiedSpans
? new DocumentSpan(document, location.SourceSpan)
: await ClassifiedSpansAndHighlightSpan.GetClassifiedDocumentSpanAsync(
document, location.SourceSpan, cancellationToken).ConfigureAwait(false);
sourceLocations.Add(documentLocation);
}
}
}
......@@ -129,10 +161,11 @@ internal static class DefinitionItemExtensions
return properties;
}
public static SourceReferenceItem TryCreateSourceReferenceItem(
public static async Task<SourceReferenceItem> TryCreateSourceReferenceItemAsync(
this ReferenceLocation referenceLocation,
DefinitionItem definitionItem,
bool includeHiddenLocations)
bool includeHiddenLocations,
CancellationToken cancellationToken)
{
var location = referenceLocation.Location;
......@@ -143,10 +176,13 @@ internal static class DefinitionItemExtensions
return null;
}
return new SourceReferenceItem(
definitionItem,
new DocumentSpan(referenceLocation.Document, location.SourceSpan),
referenceLocation.IsWrittenTo);
var document = referenceLocation.Document;
var sourceSpan = location.SourceSpan;
var documentSpan = await ClassifiedSpansAndHighlightSpan.GetClassifiedDocumentSpanAsync(
document, sourceSpan, cancellationToken).ConfigureAwait(false);
return new SourceReferenceItem(definitionItem, documentSpan, referenceLocation.IsWrittenTo);
}
private static SymbolDisplayFormat GetFormat(ISymbol definition)
......
......@@ -12,12 +12,12 @@ internal interface IFindUsagesContext
/// <summary>
/// Report a message to be displayed to the user.
/// </summary>
void ReportMessage(string message);
Task ReportMessageAsync(string message);
/// <summary>
/// Set the title of the window that results are displayed in.
/// </summary>
void SetSearchTitle(string title);
Task SetSearchTitleAsync(string title);
Task OnDefinitionFoundAsync(DefinitionItem definition);
Task OnReferenceFoundAsync(SourceReferenceItem reference);
......
......@@ -31,11 +31,17 @@ public SimpleFindUsagesContext(CancellationToken cancellationToken)
public string Message { get; private set; }
public string SearchTitle { get; private set; }
public override void ReportMessage(string message)
=> Message = message;
public override Task ReportMessageAsync(string message)
{
Message = message;
return SpecializedTasks.EmptyTask;
}
public override void SetSearchTitle(string title)
=> SearchTitle = title;
public override Task SetSearchTitleAsync(string title)
{
SearchTitle = title;
return SpecializedTasks.EmptyTask;
}
public ImmutableArray<DefinitionItem> GetDefinitions()
{
......
......@@ -61,7 +61,8 @@ internal static class GoToDefinitionHelpers
}
var definitions = ArrayBuilder<DefinitionItem>.GetInstance();
var definitionItem = symbol.ToDefinitionItem(solution, includeHiddenLocations: true);
var definitionItem = symbol.ToClassifiedDefinitionItemAsync(
solution, includeHiddenLocations: true, cancellationToken: cancellationToken).WaitAndGetResult(cancellationToken);
if (thirdPartyNavigationAllowed)
{
......@@ -72,34 +73,12 @@ internal static class GoToDefinitionHelpers
definitions.Add(definitionItem);
var presenter = GetFindUsagesPresenter(streamingPresenters);
var presenter = streamingPresenters.FirstOrDefault()?.Value;
var title = string.Format(EditorFeaturesResources._0_declarations,
FindUsagesHelpers.GetDisplayName(symbol));
return presenter.TryNavigateToOrPresentItemsAsync(
project.Solution.Workspace, title, definitions.ToImmutableAndFree()).WaitAndGetResult(cancellationToken);
}
private static IStreamingFindUsagesPresenter GetFindUsagesPresenter(
IEnumerable<Lazy<IStreamingFindUsagesPresenter>> streamingPresenters)
{
try
{
return streamingPresenters.FirstOrDefault()?.Value;
}
catch
{
return null;
}
}
private static bool TryThirdPartyNavigation(
ISymbol symbol, Solution solution, CancellationToken cancellationToken)
{
var symbolNavigationService = solution.Workspace.Services.GetService<ISymbolNavigationService>();
// Notify of navigation so third parties can intercept the navigation
return symbolNavigationService.TrySymbolNavigationNotify(symbol, solution, cancellationToken);
}
}
}
\ No newline at end of file
......@@ -60,7 +60,7 @@ private PeekableItemFactory(IMetadataAsSourceFileService metadataAsSourceFileSer
}
var symbolNavigationService = solution.Workspace.Services.GetService<ISymbolNavigationService>();
var definitionItem = symbol.ToDefinitionItem(solution, includeHiddenLocations: true);
var definitionItem = symbol.ToNonClassifiedDefinitionItem(solution, includeHiddenLocations: true);
if (symbolNavigationService.WouldNavigateToSymbol(
definitionItem, solution, cancellationToken,
......
......@@ -466,5 +466,140 @@ namespace ConsoleApplication1
result.AssertLabeledSpansAre("stmt2", "this.list = list.ToList();", RelatedLocationType.ResolvedReferenceConflict)
End Using
End Sub
<Fact>
<Trait(Traits.Feature, Traits.Features.Rename)>
<WorkItem(17177, "https://github.com/dotnet/roslyn/issues/17177")>
Public Sub ConflictsBetweenSwitchCaseStatementsWithoutBlocks()
Using result = RenameEngineResult.Create(_outputHelper,
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
class Test
{
static void Main()
{
switch (true)
{
case true:
object {|stmt1:$$i|} = null;
break;
case false:
object {|stmt2:j|} = null;
break;
}
}
}
</Document>
</Project>
</Workspace>, renameTo:="j")
result.AssertLabeledSpansAre("stmt1", "j", RelatedLocationType.NoConflict)
result.AssertLabeledSpansAre("stmt2", "j", RelatedLocationType.UnresolvableConflict)
End Using
End Sub
<Fact>
<Trait(Traits.Feature, Traits.Features.Rename)>
<WorkItem(17177, "https://github.com/dotnet/roslyn/issues/17177")>
Public Sub NoConflictsBetweenSwitchCaseStatementsWithBlocks()
Using result = RenameEngineResult.Create(_outputHelper,
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
class Test
{
static void Main()
{
switch (true)
{
case true:
{
object {|stmt1:$$i|} = null;
break;
}
case false:
{
object j = null;
break;
}
}
}
}
</Document>
</Project>
</Workspace>, renameTo:="j")
result.AssertLabeledSpansAre("stmt1", "j", RelatedLocationType.NoConflict)
End Using
End Sub
<Fact>
<Trait(Traits.Feature, Traits.Features.Rename)>
<WorkItem(17177, "https://github.com/dotnet/roslyn/issues/17177")>
Public Sub NoConflictsBetweenSwitchCaseStatementFirstStatementWithBlock()
Using result = RenameEngineResult.Create(_outputHelper,
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
class Test
{
static void Main()
{
switch (true)
{
case true:
{
object {|stmt1:$$i|} = null;
break;
}
case false:
object {|stmt2:j|} = null;
break;
}
}
}
</Document>
</Project>
</Workspace>, renameTo:="j")
result.AssertLabeledSpansAre("stmt1", "j", RelatedLocationType.NoConflict)
result.AssertLabeledSpansAre("stmt2", "j", RelatedLocationType.UnresolvableConflict)
End Using
End Sub
<Fact>
<Trait(Traits.Feature, Traits.Features.Rename)>
<WorkItem(17177, "https://github.com/dotnet/roslyn/issues/17177")>
Public Sub NoConflictsBetweenSwitchCaseStatementSecondStatementWithBlock()
Using result = RenameEngineResult.Create(_outputHelper,
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
class Test
{
static void Main()
{
switch (true)
{
case true:
object {|stmt1:$$i|} = null;
break;
case false:
{
object {|stmt2:j|} = null;
break;
}
}
}
}
</Document>
</Project>
</Workspace>, renameTo:="j")
result.AssertLabeledSpansAre("stmt1", "j", RelatedLocationType.NoConflict)
result.AssertLabeledSpansAre("stmt2", "j", RelatedLocationType.UnresolvableConflict)
End Using
End Sub
End Class
End Namespace
......@@ -59,17 +59,17 @@ public static async Task<RemoteHostClient> CreateAsync(Workspace workspace, bool
public AssetStorage AssetStorage => _inprocServices.AssetStorage;
protected override async Task<Session> TryCreateServiceSessionAsync(string serviceName, PinnedRemotableDataScope snapshot, object callbackTarget, CancellationToken cancellationToken)
protected override async Task<Session> TryCreateServiceSessionAsync(string serviceName, Optional<Func<CancellationToken, Task<PinnedRemotableDataScope>>> getSnapshotAsync, object callbackTarget, CancellationToken cancellationToken)
{
// get stream from service hub to communicate snapshot/asset related information
// this is the back channel the system uses to move data between VS and remote host
var snapshotStream = await _inprocServices.RequestServiceAsync(WellKnownServiceHubServices.SnapshotService, cancellationToken).ConfigureAwait(false);
var snapshotStream = getSnapshotAsync.Value == null ? null : await _inprocServices.RequestServiceAsync(WellKnownServiceHubServices.SnapshotService, cancellationToken).ConfigureAwait(false);
// get stream from service hub to communicate service specific information
// this is what consumer actually use to communicate information
var serviceStream = await _inprocServices.RequestServiceAsync(serviceName, cancellationToken).ConfigureAwait(false);
return await JsonRpcSession.CreateAsync(snapshot, callbackTarget, serviceStream, snapshotStream, cancellationToken).ConfigureAwait(false);
return await JsonRpcSession.CreateAsync(getSnapshotAsync, callbackTarget, serviceStream, snapshotStream, cancellationToken).ConfigureAwait(false);
}
protected override void OnConnected()
......
......@@ -19,7 +19,8 @@ private bool IsValidContext(CSharpSyntaxContext context)
{
if (context.IsStatementContext ||
context.IsGlobalStatementContext ||
context.IsPossibleTupleContext)
context.IsPossibleTupleContext ||
context.IsPatternContext)
{
return true;
}
......
// 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.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.GeneratedCodeRecognition;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
......@@ -19,10 +18,24 @@ internal struct DocumentSpan : IEquatable<DocumentSpan>
public Document Document { get; }
public TextSpan SourceSpan { get; }
/// <summary>
/// Additional information attached to a document span by it creator.
/// </summary>
public ImmutableDictionary<string, object> Properties { get; }
public DocumentSpan(Document document, TextSpan sourceSpan)
: this(document, sourceSpan, properties: null)
{
}
public DocumentSpan(
Document document,
TextSpan sourceSpan,
ImmutableDictionary<string, object> properties)
{
Document = document;
SourceSpan = sourceSpan;
Properties = properties;
}
public override bool Equals(object obj)
......
......@@ -158,8 +158,8 @@ public bool TrySymbolNavigationNotify(ISymbol symbol, Solution solution, Cancell
ISymbol symbol, Solution solution, CancellationToken cancellationToken)
{
AssertIsForeground();
var definitionItem = symbol.ToDefinitionItem(solution, includeHiddenLocations: true);
var definitionItem = symbol.ToNonClassifiedDefinitionItem(solution, includeHiddenLocations: true);
definitionItem.Properties.TryGetValue(DefinitionItem.RQNameKey1, out var rqName);
if (!TryGetNavigationAPIRequiredArguments(
......
......@@ -4,15 +4,12 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.Shell.FindAllReferences;
......@@ -221,8 +218,12 @@ public IDisposable Subscribe(ITableDataSink sink)
#region FindUsagesContext overrides.
public sealed override void SetSearchTitle(string title)
=> _findReferencesWindow.Title = title;
public sealed override Task SetSearchTitleAsync(string title)
{
// Note: IFindAllReferenceWindow.Title is safe to set from any thread.
_findReferencesWindow.Title = title;
return SpecializedTasks.EmptyTask;
}
public sealed override async Task OnCompletedAsync()
{
......@@ -271,22 +272,12 @@ protected async Task<(Guid, string projectName, SourceText)> GetGuidAndProjectNa
var document = documentSpan.Document;
var (guid, projectName, sourceText) = await GetGuidAndProjectNameAndSourceTextAsync(document).ConfigureAwait(false);
var narrowSpan = documentSpan.SourceSpan;
var lineSpan = GetLineSpanForReference(sourceText, narrowSpan);
var taggedLineParts = await GetTaggedTextForDocumentRegionAsync(document, narrowSpan, lineSpan).ConfigureAwait(false);
var classifiedSpansAndHighlightSpan =
await ClassifiedSpansAndHighlightSpan.ClassifyAsync(documentSpan, CancellationToken).ConfigureAwait(false);
return new DocumentSpanEntry(
this, definitionBucket, documentSpan, spanKind,
projectName, guid, sourceText, taggedLineParts);
}
private TextSpan GetLineSpanForReference(SourceText sourceText, TextSpan referenceSpan)
{
var sourceLine = sourceText.Lines.GetLineFromPosition(referenceSpan.Start);
var firstNonWhitespacePosition = sourceLine.GetFirstNonWhitespacePosition().Value;
return TextSpan.FromBounds(firstNonWhitespacePosition, sourceLine.End);
projectName, guid, sourceText, classifiedSpansAndHighlightSpan);
}
private TextSpan GetRegionSpanForReference(SourceText sourceText, TextSpan referenceSpan)
......@@ -302,202 +293,6 @@ private TextSpan GetRegionSpanForReference(SourceText sourceText, TextSpan refer
sourceText.Lines[lastLineNumber].End);
}
private async Task<ClassifiedSpansAndHighlightSpan> GetTaggedTextForDocumentRegionAsync(
Document document, TextSpan narrowSpan, TextSpan widenedSpan)
{
var highlightSpan = new TextSpan(
start: narrowSpan.Start - widenedSpan.Start,
length: narrowSpan.Length);
var classifiedSpans = await GetClassifiedSpansAsync(document, narrowSpan, widenedSpan).ConfigureAwait(false);
return new ClassifiedSpansAndHighlightSpan(classifiedSpans, highlightSpan);
}
private async Task<ImmutableArray<ClassifiedSpan>> GetClassifiedSpansAsync(
Document document, TextSpan narrowSpan, TextSpan widenedSpan)
{
var classificationService = document.GetLanguageService<IEditorClassificationService>();
if (classificationService == null)
{
// For languages that don't expose a classification service, we show the entire
// item as plain text. Break the text into three spans so that we can properly
// highlight the 'narrow-span' later on when we display the item.
return ImmutableArray.Create(
new ClassifiedSpan(ClassificationTypeNames.Text, TextSpan.FromBounds(widenedSpan.Start, narrowSpan.Start)),
new ClassifiedSpan(ClassificationTypeNames.Text, narrowSpan),
new ClassifiedSpan(ClassificationTypeNames.Text, TextSpan.FromBounds(narrowSpan.End, widenedSpan.End)));
}
// Call out to the individual language to classify the chunk of text around the
// reference. We'll get both the syntactic and semantic spans for this region.
// Because the semantic tags may override the semantic ones (for example,
// "DateTime" might be syntactically an identifier, but semantically a struct
// name), we'll do a later merging step to get the final correct list of
// classifications. For tagging, normally the editor handles this. But as
// we're producing the list of Inlines ourselves, we have to handles this here.
var syntaxSpans = ListPool<ClassifiedSpan>.Allocate();
var semanticSpans = ListPool<ClassifiedSpan>.Allocate();
try
{
var sourceText = await document.GetTextAsync(CancellationToken).ConfigureAwait(false);
await classificationService.AddSyntacticClassificationsAsync(
document, widenedSpan, syntaxSpans, CancellationToken).ConfigureAwait(false);
await classificationService.AddSemanticClassificationsAsync(
document, widenedSpan, semanticSpans, CancellationToken).ConfigureAwait(false);
var classifiedSpans = MergeClassifiedSpans(
syntaxSpans, semanticSpans, widenedSpan, sourceText);
return classifiedSpans;
}
finally
{
ListPool<ClassifiedSpan>.Free(syntaxSpans);
ListPool<ClassifiedSpan>.Free(semanticSpans);
}
}
private ImmutableArray<ClassifiedSpan> MergeClassifiedSpans(
List<ClassifiedSpan> syntaxSpans, List<ClassifiedSpan> semanticSpans,
TextSpan widenedSpan, SourceText sourceText)
{
// The spans produced by the language services may not be ordered
// (indeed, this happens with semantic classification as different
// providers produce different results in an arbitrary order). Order
// them first before proceeding.
Order(syntaxSpans);
Order(semanticSpans);
// It's possible for us to get classified spans that occur *before*
// or after the span we want to present. This happens because the calls to
// AddSyntacticClassificationsAsync and AddSemanticClassificationsAsync
// may return more spans than the range asked for. While bad form,
// it's never been a requirement that implementation not do that.
// For example, the span may be the non-full-span of a node, but the
// classifiers may still return classifications for leading/trailing
// trivia even if it's out of the bounds of that span.
//
// To deal with that, we adjust all spans so that they don't go outside
// of the range we care about.
AdjustSpans(syntaxSpans, widenedSpan);
AdjustSpans(semanticSpans, widenedSpan);
// The classification service will only produce classifications for
// things it knows about. i.e. there will be gaps in what it produces.
// Fill in those gaps so we have *all* parts of the span
// classified properly.
var filledInSyntaxSpans = ArrayBuilder<ClassifiedSpan>.GetInstance();
var filledInSemanticSpans = ArrayBuilder<ClassifiedSpan>.GetInstance();
try
{
FillInClassifiedSpanGaps(sourceText, widenedSpan.Start, syntaxSpans, filledInSyntaxSpans);
FillInClassifiedSpanGaps(sourceText, widenedSpan.Start, semanticSpans, filledInSemanticSpans);
// Now merge the lists together, taking all the results from syntaxParts
// unless they were overridden by results in semanticParts.
return MergeParts(filledInSyntaxSpans, filledInSemanticSpans);
}
finally
{
filledInSyntaxSpans.Free();
filledInSemanticSpans.Free();
}
}
private void AdjustSpans(List<ClassifiedSpan> spans, TextSpan widenedSpan)
{
for (var i = 0; i < spans.Count; i++)
{
var span = spans[i];
// Make sure the span actually intersects 'widenedSpan'. If it
// does not, just put in an empty length span. It will get ignored later
// when we walk through this list.
var intersection = span.TextSpan.Intersection(widenedSpan);
var newSpan = new ClassifiedSpan(span.ClassificationType,
intersection ?? new TextSpan());
spans[i] = newSpan;
}
}
private static void FillInClassifiedSpanGaps(
SourceText sourceText, int startPosition,
List<ClassifiedSpan> classifiedSpans, ArrayBuilder<ClassifiedSpan> result)
{
foreach (var span in classifiedSpans)
{
// Ignore empty spans. We can get those when the classification service
// returns spans outside of the range of the span we asked to classify.
if (span.TextSpan.Length == 0)
{
continue;
}
// If there is space between this span and the last one, then add a space.
if (startPosition != span.TextSpan.Start)
{
result.Add(new ClassifiedSpan(ClassificationTypeNames.Text,
TextSpan.FromBounds(
startPosition, span.TextSpan.Start)));
}
result.Add(span);
startPosition = span.TextSpan.End;
}
}
private void Order(List<ClassifiedSpan> syntaxSpans)
{
syntaxSpans.Sort((s1, s2) => s1.TextSpan.Start - s2.TextSpan.Start);
}
private ImmutableArray<ClassifiedSpan> MergeParts(
ArrayBuilder<ClassifiedSpan> syntaxParts,
ArrayBuilder<ClassifiedSpan> semanticParts)
{
// Take all the syntax parts. However, if any have been overridden by a
// semantic part, then choose that one.
var finalParts = ArrayBuilder<ClassifiedSpan>.GetInstance();
var lastReplacementIndex = 0;
for (int i = 0, n = syntaxParts.Count; i < n; i++)
{
var syntaxPartAndSpan = syntaxParts[i];
// See if we can find a semantic part to replace this syntax part.
var replacementIndex = semanticParts.FindIndex(
lastReplacementIndex, t => t.TextSpan == syntaxPartAndSpan.TextSpan);
// Take the semantic part if it's just 'text'. We want to keep it if
// the semantic classifier actually produced an interesting result
// (as opposed to it just being a 'gap' classification).
var part = replacementIndex >= 0 && !IsClassifiedAsText(semanticParts[replacementIndex])
? semanticParts[replacementIndex]
: syntaxPartAndSpan;
finalParts.Add(part);
if (replacementIndex >= 0)
{
// If we found a semantic replacement, update the lastIndex.
// That way we can start searching from that point instead
// of checking all the elements each time.
lastReplacementIndex = replacementIndex + 1;
}
}
return finalParts.ToImmutableAndFree();
}
private bool IsClassifiedAsText(ClassifiedSpan partAndSpan)
{
// Don't take 'text' from the semantic parts. We'll get those for the
// spaces between the actual interesting semantic spans, and we don't
// want them to override actual good syntax spans.
return partAndSpan.ClassificationType == ClassificationTypeNames.Text;
}
public sealed override Task OnReferenceFoundAsync(SourceReferenceItem reference)
=> OnReferenceFoundWorkerAsync(reference);
......
......@@ -8,10 +8,12 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo;
using Microsoft.CodeAnalysis.Editor.Implementation.ReferenceHighlighting;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Preview;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions;
......
// 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.Collections.Immutable;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal partial class StreamingFindUsagesPresenter
{
private struct ClassifiedSpansAndHighlightSpan
{
public readonly ImmutableArray<ClassifiedSpan> ClassifiedSpans;
public readonly TextSpan HighlightSpan;
public ClassifiedSpansAndHighlightSpan(
ImmutableArray<ClassifiedSpan> classifiedSpans,
TextSpan highlightSpan)
{
ClassifiedSpans = classifiedSpans;
HighlightSpan = highlightSpan;
}
}
}
}
\ No newline at end of file
......@@ -7,6 +7,7 @@
using System.Threading.Tasks;
using StreamJsonRpc;
using Microsoft.CodeAnalysis.Remote;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Remote
{
......@@ -22,6 +23,8 @@ internal class JsonRpcClient : IDisposable
public JsonRpcClient(
Stream stream, object callbackTarget, bool useThisAsCallback, CancellationToken cancellationToken)
{
Contract.Requires(stream != null);
var target = useThisAsCallback ? this : callbackTarget;
_cancellationToken = cancellationToken;
......
......@@ -31,15 +31,35 @@ internal class JsonRpcSession : RemoteHostClient.Session
private readonly CancellationTokenRegistration _cancellationRegistration;
public static async Task<JsonRpcSession> CreateAsync(
PinnedRemotableDataScope snapshot,
Optional<Func<CancellationToken, Task<PinnedRemotableDataScope>>> getSnapshotAsync,
object callbackTarget,
Stream serviceStream,
Stream snapshotStreamOpt,
CancellationToken cancellationToken)
{
var session = new JsonRpcSession(snapshot, callbackTarget, serviceStream, snapshotStreamOpt, cancellationToken);
var snapshot = getSnapshotAsync.Value == null ? null : await getSnapshotAsync.Value(cancellationToken).ConfigureAwait(false);
await session.InitializeAsync().ConfigureAwait(false);
JsonRpcSession session;
try
{
session = new JsonRpcSession(snapshot, callbackTarget, serviceStream, snapshotStreamOpt, cancellationToken);
}
catch
{
snapshot?.Dispose();
throw;
}
try
{
await session.InitializeAsync().ConfigureAwait(false);
}
catch when (!cancellationToken.IsCancellationRequested)
{
// The session disposes of itself when cancellation is requested.
session.Dispose();
throw;
}
return session;
}
......@@ -52,6 +72,8 @@ internal class JsonRpcSession : RemoteHostClient.Session
CancellationToken cancellationToken) :
base(snapshot, cancellationToken)
{
Contract.Requires((snapshot == null) == (snapshotStreamOpt == null));
// get session id
_currentSessionId = Interlocked.Increment(ref s_sessionId);
......
......@@ -5,6 +5,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Execution;
......@@ -114,17 +115,17 @@ private static async Task RegisterWorkspaceHostAsync(Workspace workspace, Remote
_rpc.StartListening();
}
protected override async Task<Session> TryCreateServiceSessionAsync(string serviceName, PinnedRemotableDataScope snapshot, object callbackTarget, CancellationToken cancellationToken)
protected override async Task<Session> TryCreateServiceSessionAsync(string serviceName, Optional<Func<CancellationToken, Task<PinnedRemotableDataScope>>> getSnapshotAsync, object callbackTarget, CancellationToken cancellationToken)
{
// get stream from service hub to communicate snapshot/asset related information
// this is the back channel the system uses to move data between VS and remote host for solution related information
var snapshotStream = snapshot == null ? null : await RequestServiceAsync(_hubClient, WellKnownServiceHubServices.SnapshotService, _hostGroup, _timeout, cancellationToken).ConfigureAwait(false);
var snapshotStream = getSnapshotAsync.Value == null ? null : await RequestServiceAsync(_hubClient, WellKnownServiceHubServices.SnapshotService, _hostGroup, _timeout, cancellationToken).ConfigureAwait(false);
// get stream from service hub to communicate service specific information
// this is what consumer actually use to communicate information
var serviceStream = await RequestServiceAsync(_hubClient, serviceName, _hostGroup, _timeout, cancellationToken).ConfigureAwait(false);
return await JsonRpcSession.CreateAsync(snapshot, callbackTarget, serviceStream, snapshotStream, cancellationToken).ConfigureAwait(false);
return await JsonRpcSession.CreateAsync(getSnapshotAsync, callbackTarget, serviceStream, snapshotStream, cancellationToken).ConfigureAwait(false);
}
protected override void OnConnected()
......
......@@ -102,7 +102,6 @@
<Compile Include="FindReferences\StreamingFindUsagesPresenter.cs" />
<Compile Include="FindReferences\ToolTips\DisposableToolTip.cs" />
<Compile Include="FindReferences\TableEntriesSnapshot.cs" />
<Compile Include="FindReferences\TaggedTextAndHighlightSpan.cs" />
<Compile Include="FindReferences\ToolTips\LazyToolTip.cs" />
<Compile Include="ProjectSystem\DeferredProjectWorkspaceService.cs" />
<Compile Include="Remote\JsonRpcClient.cs" />
......
......@@ -30,7 +30,7 @@ static void Main(string[] args)
}
}";
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/18879"), Trait(Traits.Feature, Traits.Features.EncapsulateField)]
[Fact, Trait(Traits.Feature, Traits.Features.EncapsulateField)]
public void EncapsulateThroughCommand()
{
SetUpEditor(TestSource);
......
......@@ -20,7 +20,7 @@ public CSharpNavigateTo(VisualStudioInstanceFactory instanceFactory)
{
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/18870"), Trait(Traits.Feature, Traits.Features.SignatureHelp)]
[Fact, Trait(Traits.Feature, Traits.Features.SignatureHelp)]
public void NavigateTo()
{
var project = new ProjectUtils.Project(ProjectName);
......
......@@ -61,7 +61,7 @@ public void VerifySharpLoadCompletionList()
VisualStudio.InteractiveWindow.Verify.CompletionItemsExist("C:");
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/18877")]
[Fact]
public void VerifyNoCrashOnEnter()
{
VisualStudio.Workspace.SetUseSuggestionMode(false);
......
......@@ -214,7 +214,7 @@ public void AddAssemblyReferenceAndTypesToInteractive()
VisualStudio.Workspace.WaitForAsyncOperations(FeatureAttribute.SolutionCrawler);
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/18880")]
[Fact]
public void ResetInteractiveFromProjectAndVerify()
{
var assembly = new ProjectUtils.AssemblyReference("System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
......
......@@ -18,7 +18,7 @@ public BasicNavigateTo(VisualStudioInstanceFactory instanceFactory)
{
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/18870"), Trait(Traits.Feature, Traits.Features.SignatureHelp)]
[Fact, Trait(Traits.Feature, Traits.Features.SignatureHelp)]
public void NavigateTo()
{
var project = new ProjectUtils.Project(ProjectName);
......
......@@ -98,13 +98,15 @@ internal sealed class CSharpSyntaxContext : SyntaxContext
bool isCatchFilterContext,
bool isDestructorTypeContext,
bool isPossibleTupleContext,
bool isPatternContext,
CancellationToken cancellationToken)
: base(workspace, semanticModel, position, leftToken, targetToken,
isTypeContext, isNamespaceContext, isNamespaceDeclarationNameContext,
isPreProcessorDirectiveContext,
isRightOfDotOrArrowOrColonColon, isStatementContext, isAnyExpressionContext,
isAttributeNameContext, isEnumTypeMemberAccessContext, isNameOfContext,
isInQuery, isInImportsDirective, IsWithinAsyncMethod(), isPossibleTupleContext, cancellationToken)
isInQuery, isInImportsDirective, IsWithinAsyncMethod(), isPossibleTupleContext,
isPatternContext, cancellationToken)
{
this.ContainingTypeDeclaration = containingTypeDeclaration;
this.ContainingTypeOrEnumDeclaration = containingTypeOrEnumDeclaration;
......@@ -242,6 +244,7 @@ private static CSharpSyntaxContext CreateContextWorker(Workspace workspace, Sema
syntaxTree.IsCatchFilterContext(position, leftToken),
isDestructorTypeContext,
syntaxTree.IsPossibleTupleContext(leftToken, position),
syntaxTree.IsPatternContext(leftToken, position),
cancellationToken);
}
......
......@@ -1260,6 +1260,20 @@ public static bool IsPossibleTupleContext(this SyntaxTree syntaxTree, SyntaxToke
return false;
}
public static bool IsPatternContext(this SyntaxTree syntaxTree, SyntaxToken leftToken, int position)
{
leftToken = leftToken.GetPreviousTokenIfTouchingWord(position);
// case $$
// is $$
if (leftToken.IsKind(SyntaxKind.CaseKeyword, SyntaxKind.IsKeyword))
{
return true;
}
return false;
}
private static SyntaxToken FindTokenOnLeftOfNode(SyntaxNode node)
{
return node.FindTokenOnLeftOfPosition(node.SpanStart);
......
......@@ -37,12 +37,17 @@ public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
}
public override void VisitBlock(BlockSyntax node)
{
VisitBlockStatements(node, node.Statements);
}
private void VisitBlockStatements(SyntaxNode node, IEnumerable<SyntaxNode> statements)
{
var tokens = new List<SyntaxToken>();
// We want to collect any variable declarations that are in the block
// before visiting nested statements
foreach (var statement in node.Statements)
foreach (var statement in statements)
{
if (statement.Kind() == SyntaxKind.LocalDeclarationStatement)
{
......@@ -183,6 +188,13 @@ public override void VisitQueryContinuation(QueryContinuationSyntax node)
_tracker.RemoveIdentifier(node.Identifier);
}
public override void VisitSwitchStatement(SwitchStatementSyntax node)
{
var statements = node.ChildNodes().Where(x => x.IsKind(SyntaxKind.SwitchSection)).SelectMany(x => x.ChildNodes());
VisitBlockStatements(node, statements);
}
public IEnumerable<SyntaxToken> ConflictingTokens
{
get
......
......@@ -12,10 +12,11 @@ namespace Microsoft.CodeAnalysis.Execution
/// <summary>
/// checksum scope that one can use to pin assets in memory while working on remote host
/// </summary>
internal class PinnedRemotableDataScope : IDisposable
internal sealed class PinnedRemotableDataScope : IDisposable
{
private readonly AssetStorages _storages;
private readonly AssetStorages.Storage _storage;
private bool _disposed;
public readonly Checksum SolutionChecksum;
......@@ -63,7 +64,12 @@ public RemotableData GetRemotableData(Checksum checksum, CancellationToken cance
public void Dispose()
{
_storages.UnregisterSnapshot(this);
if (!_disposed)
{
_disposed = true;
_storages.UnregisterSnapshot(this);
}
GC.SuppressFinalize(this);
}
......
......@@ -93,18 +93,20 @@ internal static partial class DeclarationFinder
private static async Task<(bool, ImmutableArray<SymbolAndProjectId>)> TryFindAllDeclarationsWithNormalQueryInRemoteProcessAsync(
Project project, SearchQuery query, SymbolFilter criteria, CancellationToken cancellationToken)
{
var session = await SymbolFinder.TryGetRemoteSessionAsync(
project.Solution, cancellationToken).ConfigureAwait(false);
if (session != null)
using (var session = await SymbolFinder.TryGetRemoteSessionAsync(
project.Solution, cancellationToken).ConfigureAwait(false))
{
var result = await session.InvokeAsync<SerializableSymbolAndProjectId[]>(
nameof(IRemoteSymbolFinder.FindAllDeclarationsWithNormalQueryAsync),
project.Id, query.Name, query.Kind, criteria).ConfigureAwait(false);
if (session != null)
{
var result = await session.InvokeAsync<SerializableSymbolAndProjectId[]>(
nameof(IRemoteSymbolFinder.FindAllDeclarationsWithNormalQueryAsync),
project.Id, query.Name, query.Kind, criteria).ConfigureAwait(false);
var rehydrated = await RehydrateAsync(
project.Solution, result, cancellationToken).ConfigureAwait(false);
var rehydrated = await RehydrateAsync(
project.Solution, result, cancellationToken).ConfigureAwait(false);
return (true, rehydrated);
return (true, rehydrated);
}
}
return (false, ImmutableArray<SymbolAndProjectId>.Empty);
......
......@@ -118,17 +118,19 @@ internal static partial class DeclarationFinder
private static async Task<(bool, ImmutableArray<SymbolAndProjectId>)> TryFindSourceDeclarationsWithNormalQueryInRemoteProcessAsync(
Solution solution, string name, bool ignoreCase, SymbolFilter criteria, CancellationToken cancellationToken)
{
var session = await SymbolFinder.TryGetRemoteSessionAsync(solution, cancellationToken).ConfigureAwait(false);
if (session != null)
using (var session = await SymbolFinder.TryGetRemoteSessionAsync(solution, cancellationToken).ConfigureAwait(false))
{
var result = await session.InvokeAsync<SerializableSymbolAndProjectId[]>(
nameof(IRemoteSymbolFinder.FindSolutionSourceDeclarationsWithNormalQueryAsync),
name, ignoreCase, criteria).ConfigureAwait(false);
if (session != null)
{
var result = await session.InvokeAsync<SerializableSymbolAndProjectId[]>(
nameof(IRemoteSymbolFinder.FindSolutionSourceDeclarationsWithNormalQueryAsync),
name, ignoreCase, criteria).ConfigureAwait(false);
var rehydrated = await RehydrateAsync(
solution, result, cancellationToken).ConfigureAwait(false);
var rehydrated = await RehydrateAsync(
solution, result, cancellationToken).ConfigureAwait(false);
return (true, rehydrated);
return (true, rehydrated);
}
}
return (false, ImmutableArray<SymbolAndProjectId>.Empty);
......@@ -137,17 +139,19 @@ internal static partial class DeclarationFinder
private static async Task<(bool, ImmutableArray<SymbolAndProjectId>)> TryFindSourceDeclarationsWithNormalQueryInRemoteProcessAsync(
Project project, string name, bool ignoreCase, SymbolFilter criteria, CancellationToken cancellationToken)
{
var session = await SymbolFinder.TryGetRemoteSessionAsync(project.Solution, cancellationToken).ConfigureAwait(false);
if (session != null)
using (var session = await SymbolFinder.TryGetRemoteSessionAsync(project.Solution, cancellationToken).ConfigureAwait(false))
{
var result = await session.InvokeAsync<SerializableSymbolAndProjectId[]>(
nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithNormalQueryAsync),
project.Id, name, ignoreCase, criteria).ConfigureAwait(false);
if (session != null)
{
var result = await session.InvokeAsync<SerializableSymbolAndProjectId[]>(
nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithNormalQueryAsync),
project.Id, name, ignoreCase, criteria).ConfigureAwait(false);
var rehydrated = await RehydrateAsync(
project.Solution, result, cancellationToken).ConfigureAwait(false);
var rehydrated = await RehydrateAsync(
project.Solution, result, cancellationToken).ConfigureAwait(false);
return (true, rehydrated);
return (true, rehydrated);
}
}
return (false, ImmutableArray<SymbolAndProjectId>.Empty);
......@@ -156,17 +160,19 @@ internal static partial class DeclarationFinder
private static async Task<(bool, ImmutableArray<SymbolAndProjectId>)> TryFindSourceDeclarationsWithPatternInRemoteProcessAsync(
Project project, string pattern, SymbolFilter criteria, CancellationToken cancellationToken)
{
var session = await SymbolFinder.TryGetRemoteSessionAsync(project.Solution, cancellationToken).ConfigureAwait(false);
if (session != null)
using (var session = await SymbolFinder.TryGetRemoteSessionAsync(project.Solution, cancellationToken).ConfigureAwait(false))
{
var result = await session.InvokeAsync<SerializableSymbolAndProjectId[]>(
nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithPatternAsync),
project.Id, pattern, criteria).ConfigureAwait(false);
if (session != null)
{
var result = await session.InvokeAsync<SerializableSymbolAndProjectId[]>(
nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithPatternAsync),
project.Id, pattern, criteria).ConfigureAwait(false);
var rehydrated = await RehydrateAsync(
project.Solution, result, cancellationToken).ConfigureAwait(false);
var rehydrated = await RehydrateAsync(
project.Solution, result, cancellationToken).ConfigureAwait(false);
return (true, rehydrated);
return (true, rehydrated);
}
}
return (false, ImmutableArray<SymbolAndProjectId>.Empty);
......
......@@ -63,7 +63,7 @@ internal sealed partial class PatternMatcher : IDisposable
{
// PERF: Avoid string.Split allocations when the pattern doesn't contain a dot.
_dotSeparatedPatternSegments = pattern.Length > 0
? new PatternSegment[1] { _fullPatternSegment }
? new PatternSegment[1] { new PatternSegment(pattern.Trim(), allowFuzzyMatching) }
: Array.Empty<PatternSegment>();
}
else
......
......@@ -24,30 +24,6 @@ protected RemoteHostClient(Workspace workspace)
public event EventHandler<bool> ConnectionChanged;
[Obsolete("use TryCreateServiceSessionAsync instead")]
public Task<Session> CreateServiceSessionAsync(string serviceName, CancellationToken cancellationToken)
{
return CreateServiceSessionAsync(serviceName, callbackTarget: null, cancellationToken: cancellationToken);
}
[Obsolete("use TryCreateServiceSessionAsync instead")]
public Task<Session> CreateServiceSessionAsync(string serviceName, object callbackTarget, CancellationToken cancellationToken)
{
return TryCreateServiceSessionAsync(serviceName, snapshot: null, callbackTarget: callbackTarget, cancellationToken: cancellationToken);
}
[Obsolete("use TryCreateServiceSessionAsync instead")]
public Task<Session> CreateServiceSessionAsync(string serviceName, Solution solution, CancellationToken cancellationToken)
{
return CreateServiceSessionAsync(serviceName, solution, callbackTarget: null, cancellationToken: cancellationToken);
}
[Obsolete("use TryCreateServiceSessionAsync instead")]
public Task<Session> CreateServiceSessionAsync(string serviceName, Solution solution, object callbackTarget, CancellationToken cancellationToken)
{
return TryCreateServiceSessionAsync(serviceName, solution, callbackTarget, cancellationToken);
}
/// <summary>
/// Create <see cref="RemoteHostClient.Session"/> for the <paramref name="serviceName"/> if possible.
/// otherwise, return null.
......@@ -69,7 +45,7 @@ public Task<Session> TryCreateServiceSessionAsync(string serviceName, Cancellati
/// </summary>
public Task<Session> TryCreateServiceSessionAsync(string serviceName, object callbackTarget, CancellationToken cancellationToken)
{
return TryCreateServiceSessionAsync(serviceName, snapshot: null, callbackTarget: callbackTarget, cancellationToken: cancellationToken);
return TryCreateServiceSessionAsync(serviceName, getSnapshotAsync: null, callbackTarget: callbackTarget, cancellationToken: cancellationToken);
}
/// <summary>
......@@ -93,26 +69,15 @@ public Task<Session> TryCreateServiceSessionAsync(string serviceName, Solution s
/// </summary>
public async Task<Session> TryCreateServiceSessionAsync(string serviceName, Solution solution, object callbackTarget, CancellationToken cancellationToken)
{
var snapshot = await GetPinnedScopeAsync(solution, cancellationToken).ConfigureAwait(false);
return await TryCreateServiceSessionAsync(serviceName, snapshot, callbackTarget, cancellationToken).ConfigureAwait(false);
Func<CancellationToken, Task<PinnedRemotableDataScope>> getSnapshotAsync = ct => GetPinnedScopeAsync(solution, ct);
return await TryCreateServiceSessionAsync(serviceName, getSnapshotAsync, callbackTarget, cancellationToken).ConfigureAwait(false);
}
protected abstract void OnConnected();
protected abstract void OnDisconnected();
[Obsolete]
protected virtual Task<Session> CreateServiceSessionAsync(string serviceName, PinnedRemotableDataScope snapshot, object callbackTarget, CancellationToken cancellationToken)
{
return SpecializedTasks.Default<Session>();
}
protected virtual Task<Session> TryCreateServiceSessionAsync(string serviceName, PinnedRemotableDataScope snapshot, object callbackTarget, CancellationToken cancellationToken)
{
#pragma warning disable CS0612 // leave it for now to not break backward compatibility
return CreateServiceSessionAsync(serviceName, snapshot, callbackTarget, cancellationToken);
#pragma warning restore CS0612 // Type or member is obsolete
}
protected abstract Task<Session> TryCreateServiceSessionAsync(string serviceName, Optional<Func<CancellationToken, Task<PinnedRemotableDataScope>>> getSnapshotAsync, object callbackTarget, CancellationToken cancellationToken);
internal void Shutdown()
{
......@@ -210,7 +175,7 @@ public class NoOpClient : RemoteHostClient
}
protected override Task<Session> TryCreateServiceSessionAsync(
string serviceName, PinnedRemotableDataScope snapshot, object callbackTarget, CancellationToken cancellationToken)
string serviceName, Optional<Func<CancellationToken, Task<PinnedRemotableDataScope>>> getSnapshotAsync, object callbackTarget, CancellationToken cancellationToken)
{
return SpecializedTasks.Default<Session>();
}
......
......@@ -32,6 +32,7 @@ internal abstract class SyntaxContext
bool isInImportsDirective,
bool isWithinAsyncMethod,
bool isPossibleTupleContext,
bool isPatternContext,
CancellationToken cancellationToken)
{
this.Workspace = workspace;
......@@ -54,6 +55,7 @@ internal abstract class SyntaxContext
this.IsInImportsDirective = isInImportsDirective;
this.IsWithinAsyncMethod = isWithinAsyncMethod;
this.IsPossibleTupleContext = isPossibleTupleContext;
this.IsPatternContext = isPatternContext;
this.InferredTypes = ComputeInferredTypes(workspace, semanticModel, position, cancellationToken);
}
......@@ -83,6 +85,7 @@ internal abstract class SyntaxContext
public bool IsInImportsDirective { get; }
public bool IsWithinAsyncMethod { get; }
public bool IsPossibleTupleContext { get; }
public bool IsPatternContext { get; }
public IEnumerable<ITypeSymbol> InferredTypes { get; }
......
......@@ -80,9 +80,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Classification.Classifiers
Dim token = GetNameToken(node)
Return SpecializedCollections.SingletonEnumerable(
New ClassifiedSpan(token.Span, ClassificationTypeNames.Keyword))
Else
' We bound to a constructor, but we weren't something like the 'New' in 'X.New'.
' This can happen when we're actually just binding the full node 'X.New'. In this
' case, don't return anything for this full node. We'll end up hitting the
' 'New' node as the worker walks down, and we'll classify it then.
Return Nothing
End If
symbol = method.ContainingType
End If
End If
......
......@@ -97,6 +97,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery
isInImportsDirective:=isInImportsDirective,
isWithinAsyncMethod:=IsWithinAsyncMethod(targetToken, cancellationToken),
isPossibleTupleContext:=isPossibleTupleContext,
isPatternContext:=False,
cancellationToken:=cancellationToken)
Dim syntaxTree = semanticModel.SyntaxTree
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册