Skip to content

Add GoToDefinition for XAML LSP#55296

Merged
mgoertz-msft merged 4 commits intodotnet:mainfrom
LinglingTong:dev/ltong/lsp_definitions
Aug 4, 2021
Merged

Add GoToDefinition for XAML LSP#55296
mgoertz-msft merged 4 commits intodotnet:mainfrom
LinglingTong:dev/ltong/lsp_definitions

Conversation

@LinglingTong
Copy link
Copy Markdown
Contributor

This PR enables GoToDefinition for LSP XAML. The GoToDefinitionHandler uses IXamlGoToDefinitionService, which will be implemented in XAML language service to get the definitions of the given document and position. There are two types of XamlDefinition we are getting from the XAML language service:

  • XamlSourceDefinition has the file path and textSpan (or line and column). This is usually the case when the definition is in a XAML file.
  • XamlSymbolDefinition contains just the symbol. We then use Roslyn to find the locations of the given symbol.

@LinglingTong LinglingTong requested a review from a team as a code owner July 30, 2021 21:07
@ghost ghost added the Area-IDE label Jul 30, 2021
@LinglingTong
Copy link
Copy Markdown
Contributor Author

LinglingTong commented Jul 30, 2021

@mgoertz-msft @dibarbet Could you please help to take a look?


In reply to: 890167800

{
var locations = ArrayBuilder<LSP.Location>.GetInstance();

if (definition is XamlSourceDefinition sourceDefinition)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would recommend breaking up this method a little bit so it's easier to read, e.g. have methods to handle each kind of definition (HandleSourceDefinition, HandleSymbolDefinition


public override async Task<LSP.Location[]> HandleRequestAsync(TextDocumentPositionParams request, RequestContext context, CancellationToken cancellationToken)
{
var locations = ArrayBuilder<LSP.Location>.GetInstance();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use arrabuilder like this - https://sourceroslyn.io/#Microsoft.CodeAnalysis.LanguageServer.Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs,64 and then just to ToArray() on it instead of ToArrayAndFree()


private async Task<LSP.Location[]> GetLocationsAsync(XamlDefinition definition, RequestContext context, CancellationToken cancellationToken)
{
var locations = ArrayBuilder<LSP.Location>.GetInstance();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above on ArrayBuilder usage

}

var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
var xamlGoToDefinitionService = document.Project.LanguageServices.GetService<IXamlGoToDefinitionService>();
Copy link
Copy Markdown
Contributor

@mgoertz-msft mgoertz-msft Jul 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it could move down to before line 65 #Closed


var definitions = await xamlGoToDefinitionService.GetDefinitionsAsync(document, position, cancellationToken).ConfigureAwait(false);
foreach (var definition in definitions)
{
Copy link
Copy Markdown
Contributor

@mgoertz-msft mgoertz-msft Jul 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimize with Parallel.ForEach or Task.WhenAll? #Closed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to use Task.WhenAll, and I also changed the locations to ConcurrentBag type so it is thread safe

// Cannot find the file in solution. This is probably a file lives outside of the solution like generic.xaml
// which lives in the Windows SDK folder. Try getting the SourceText from the file path.
using var fileStream = new FileStream(sourceDefinition.FilePath, FileMode.Open, FileAccess.Read);
var sourceText = SourceText.From(fileStream);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the best way to handle error conditions here? For example if the file doesn't exist or cannot be opened for some reason.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tried out giving it an atbitrary file path and let it throw. Don't see any error diaglog/indicator from the UI. I am thinking about let it throw if anything wrong with opening the document so we can get the exception from Telemetry late, but yeah the downside of it would be the remaining code will not execute either (if there are more definitions). @dibarbet how does csharp handle partial success for go to definition?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't this be a full failure - you have no location to go to? Depending on the kind of failure we either throw or just return no definitions.

If you do not expect to hit an exception, I would leave it as throwing so you can capture telemetry on it. If an exception here is normal and not unexpected, then it should probably be handled. Throwing here is OK, the request queue should handle the exception and report a nfw since it isn't a mutating request.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, David. That's what I am expecting. BTW do you know if there will be a dialog when fail to go to definition for LSP just like what we have in classic VS? Or no indication for failure is the design going forward?
image

internal sealed class XamlSourceDefinition : XamlDefinition
{
private readonly TextSpan? _span;
private readonly int _line, _character;
Copy link
Copy Markdown
Contributor

@mgoertz-msft mgoertz-msft Jul 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_character

_column #Closed

Contract.ThrowIfNull(symbolDefinition.Symbol);
var symbol = symbolDefinition.Symbol;

if (symbol.Locations.First().IsInMetadata && _metadataAsSourceFileService != null)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

symbol.Locations.First()

@dibarbet IS the first location always the definition or is there a better way to get the location for a symbol definition?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we usually use first. that being said this is what we do in non-LSP and we probably need to update our handler to do something similar
https://sourceroslyn.io/#Microsoft.VisualStudio.LanguageServices/Implementation/Workspace/VisualStudioSymbolNavigationService.cs,67

Contract.ThrowIfNull(symbolDefinition.Symbol);
var symbol = symbolDefinition.Symbol;

if (symbol.Locations.First().IsInMetadata && _metadataAsSourceFileService != null)
Copy link
Copy Markdown
Contributor

@mgoertz-msft mgoertz-msft Jul 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&& _metadataAsSourceFileService != null

Not necessary. MEF creation will fail w/o it #Closed

_span = span;
}

public XamlSourceDefinition(string filePath, int line, int character)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

character

column

// Convert the line column to TextSpan
if (_line < text.Lines.Count)
{
var character = Math.Min(_column, text.Lines[_line].Span.Length);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

character

column

{
var task = Task.Run(async () =>
{
foreach(var location in await this.GetLocationsAsync(definition, context, cancellationToken).ConfigureAwait(false))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

foreach(

insert space

}
else
{
throw new InvalidOperationException("Unexpected XamlDefinition Type");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XamlDefinition

nameof(XamlDefinition)

Copy link
Copy Markdown
Contributor

@mgoertz-msft mgoertz-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@mgoertz-msft mgoertz-msft merged commit e011274 into dotnet:main Aug 4, 2021
@ghost ghost added this to the Next milestone Aug 4, 2021
@dibarbet dibarbet modified the milestones: Next, 17.0.P4 Aug 31, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants