-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Quick Info styles #35667
Implement Quick Info styles #35667
Changes from all commits
0d755cc
7d393bc
93bd62b
07c8cac
8f2bb71
217f1b3
70ac333
8fa6d8c
685adf9
bcb448a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// 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.Composition; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.Host.Mef; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.Editor.Implementation | ||
{ | ||
[ExportWorkspaceService(typeof(INavigateToLinkService), layer: ServiceLayer.Default)] | ||
[Shared] | ||
internal sealed class DefaultNavigateToLinkService : INavigateToLinkService | ||
{ | ||
[ImportingConstructor] | ||
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] | ||
public DefaultNavigateToLinkService() | ||
{ | ||
} | ||
|
||
public Task<bool> TryNavigateToLinkAsync(Uri uri, CancellationToken cancellationToken) | ||
=> SpecializedTasks.False; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// 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.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.Host; | ||
|
||
namespace Microsoft.CodeAnalysis.Editor | ||
{ | ||
internal interface INavigateToLinkService : IWorkspaceService | ||
{ | ||
Task<bool> TryNavigateToLinkAsync(Uri uri, CancellationToken cancellationToken); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,87 @@ | ||
// 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.Linq; | ||
using System.Threading; | ||
using Microsoft.CodeAnalysis.Classification; | ||
using Microsoft.CodeAnalysis.Editor.GoToDefinition; | ||
using Microsoft.CodeAnalysis.Editor.Host; | ||
using Microsoft.VisualStudio.Text.Adornments; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense | ||
{ | ||
internal static class Helpers | ||
{ | ||
internal static IEnumerable<object> BuildClassifiedTextElements(ImmutableArray<TaggedText> taggedTexts) | ||
internal static IReadOnlyCollection<object> BuildInteractiveTextElements(ImmutableArray<TaggedText> taggedTexts, Document document, Lazy<IStreamingFindUsagesPresenter> streamingPresenter) | ||
{ | ||
var index = 0; | ||
return BuildInteractiveTextElements(taggedTexts, ref index, document, streamingPresenter); | ||
} | ||
|
||
private static IReadOnlyCollection<object> BuildInteractiveTextElements(ImmutableArray<TaggedText> taggedTexts, ref int index, Document document, Lazy<IStreamingFindUsagesPresenter> streamingPresenter) | ||
{ | ||
// This method produces a sequence of zero or more paragraphs | ||
var paragraphs = new List<object>(); | ||
|
||
// Each paragraph is constructed from one or more lines | ||
var currentParagraph = new List<ClassifiedTextElement>(); | ||
var currentParagraph = new List<object>(); | ||
|
||
// Each line is constructed from one or more inline elements | ||
var currentRuns = new List<ClassifiedTextRun>(); | ||
|
||
foreach (var part in taggedTexts) | ||
while (index < taggedTexts.Length) | ||
{ | ||
var part = taggedTexts[index]; | ||
if (part.Tag == TextTags.ContainerStart) | ||
{ | ||
if (currentRuns.Count > 0) | ||
{ | ||
// This line break means the end of a line within a paragraph. | ||
currentParagraph.Add(new ClassifiedTextElement(currentRuns)); | ||
currentRuns.Clear(); | ||
} | ||
|
||
index++; | ||
var nestedElements = BuildInteractiveTextElements(taggedTexts, ref index, document, streamingPresenter); | ||
if (nestedElements.Count <= 1) | ||
{ | ||
currentParagraph.Add(new ContainerElement( | ||
ContainerElementStyle.Wrapped, | ||
new ClassifiedTextElement(new ClassifiedTextRun(ClassificationTypeNames.Text, part.Text)), | ||
new ContainerElement(ContainerElementStyle.Stacked, nestedElements))); | ||
} | ||
else | ||
{ | ||
currentParagraph.Add(new ContainerElement( | ||
ContainerElementStyle.Wrapped, | ||
new ClassifiedTextElement(new ClassifiedTextRun(ClassificationTypeNames.Text, part.Text)), | ||
new ContainerElement( | ||
ContainerElementStyle.Stacked, | ||
nestedElements.First(), | ||
new ContainerElement( | ||
ContainerElementStyle.Stacked | ContainerElementStyle.VerticalPadding, | ||
nestedElements.Skip(1))))); | ||
} | ||
|
||
index++; | ||
continue; | ||
} | ||
else if (part.Tag == TextTags.ContainerEnd) | ||
{ | ||
// Return the current result and let the caller continue | ||
break; | ||
} | ||
|
||
if (part.Tag == TextTags.ContainerStart | ||
|| part.Tag == TextTags.ContainerEnd) | ||
{ | ||
index++; | ||
continue; | ||
} | ||
|
||
if (part.Tag == TextTags.LineBreak) | ||
{ | ||
if (currentRuns.Count > 0) | ||
|
@@ -53,8 +113,20 @@ internal static IEnumerable<object> BuildClassifiedTextElements(ImmutableArray<T | |
else | ||
{ | ||
// This is tagged text getting added to the current line we are building. | ||
currentRuns.Add(new ClassifiedTextRun(part.Tag.ToClassificationTypeName(), part.Text)); | ||
var style = GetClassifiedTextRunStyle(part.Style); | ||
if (part.NavigationTarget is object) | ||
{ | ||
var target = part.NavigationTarget; | ||
var tooltip = part.NavigationHint; | ||
currentRuns.Add(new ClassifiedTextRun(part.Tag.ToClassificationTypeName(), part.Text, () => NavigateToQuickInfoTarget(target, document, streamingPresenter.Value), tooltip, style)); | ||
} | ||
else | ||
{ | ||
currentRuns.Add(new ClassifiedTextRun(part.Tag.ToClassificationTypeName(), part.Text, style)); | ||
} | ||
} | ||
|
||
index++; | ||
} | ||
sharwell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (currentRuns.Count > 0) | ||
|
@@ -72,7 +144,60 @@ internal static IEnumerable<object> BuildClassifiedTextElements(ImmutableArray<T | |
return paragraphs; | ||
} | ||
|
||
internal static object CreateParagraphFromLines(IReadOnlyList<ClassifiedTextElement> lines) | ||
private static void NavigateToQuickInfoTarget(string navigationTarget, Document document, IStreamingFindUsagesPresenter streamingPresenter) | ||
{ | ||
var navigateToLinkService = document.Project.Solution.Workspace.Services.GetRequiredService<INavigateToLinkService>(); | ||
if (Uri.TryCreate(navigationTarget, UriKind.Absolute, out var absoluteUri)) | ||
{ | ||
navigateToLinkService.TryNavigateToLinkAsync(absoluteUri, CancellationToken.None); | ||
return; | ||
} | ||
|
||
SymbolKeyResolution resolvedSymbolKey; | ||
try | ||
{ | ||
resolvedSymbolKey = SymbolKey.ResolveString(navigationTarget, document.Project.GetCompilationAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None), cancellationToken: CancellationToken.None); | ||
} | ||
catch | ||
{ | ||
// Ignore symbol resolution failures. It likely is just a badly formed URI. | ||
return; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. def not liking this.
Worst case, when someone navigates, we can do a TWD and make it cancellable if something goes off the rails. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Limited by the editor API. @gundermanc how would you handle this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: sync blocking is ok with me. it's more the lack of cancellability. That should hopefully be something we can fix up with a TWD. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the only option is to do whatever the equivalent of a For my own edification, would it be preferable to have actions like this be async? I didn't think it was necessary originally because we're de facto going to have to InvokeAsync() somewhere but I acknowledge things are a bit more constrained for Roslyn since this layer has to work on Mac. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd be fine with this being async (which honestly makes sense to me given that any of thse clicks might cause major work to happen). Or, if sync, at least have it be under a TWD. |
||
} | ||
|
||
if (resolvedSymbolKey.GetAnySymbol() is { } symbol) | ||
{ | ||
GoToDefinitionHelpers.TryGoToDefinition(symbol, document.Project, streamingPresenter, CancellationToken.None); | ||
return; | ||
} | ||
} | ||
|
||
private static ClassifiedTextRunStyle GetClassifiedTextRunStyle(TaggedTextStyle style) | ||
{ | ||
var result = ClassifiedTextRunStyle.Plain; | ||
if ((style & TaggedTextStyle.Emphasis) == TaggedTextStyle.Emphasis) | ||
{ | ||
result |= ClassifiedTextRunStyle.Italic; | ||
} | ||
|
||
if ((style & TaggedTextStyle.Strong) == TaggedTextStyle.Strong) | ||
{ | ||
result |= ClassifiedTextRunStyle.Bold; | ||
} | ||
|
||
if ((style & TaggedTextStyle.Underline) == TaggedTextStyle.Underline) | ||
{ | ||
result |= ClassifiedTextRunStyle.Underline; | ||
} | ||
|
||
if ((style & TaggedTextStyle.Code) == TaggedTextStyle.Code) | ||
{ | ||
result |= ClassifiedTextRunStyle.UseClassificationFont; | ||
sharwell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
return result; | ||
} | ||
|
||
internal static object CreateParagraphFromLines(IReadOnlyList<object> lines) | ||
{ | ||
Contract.ThrowIfFalse(lines.Count > 0); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we consider extracting out everything above (into something like StartContainerElement(...))? I thnk it would make it much easier to understand things if we just had a top level lopo, with the high level decision making, and then have the individual functions that are responsible for each piece of work. intermingling them makes it harder to keep track and understand what's going on for me.