Get Roslyn SyntaxToken from Visual Studio Text Selection (caret position)
Asked Answered
C

1

9

I am attempting to bridge between the VSSDK and Roslyn SDK in a Visual Studio extension package and have been having a hard time with this. The ActivePoint.AbsoluteCharOffset given from Visual Studio does not match the element I get from Roslyn when using FindToken(offset). I'm fairly sure this has to do with how each side counts EOL characters based on my current working hack but I'm not 100% that my hack is going to hold up in the long run.

My hack is this line: charOffset += point.Line;

I add the number of lines onto the char offset, this seems to work so I'm guessing I am adding in all the line break characters that are being ignored by activepoint counting.

Helpers

private VisualStudioWorkspace workspace = null;
public RoslynUtilities(VisualStudioWorkspace workspace)
{
    this.workspace = workspace;
}
public Solution Solution { get { return workspace.CurrentSolution; } }
public Document GetDocumentFromPath(string fullPath)
{
    foreach (Project proj in this.Solution.Projects)
    {               
        foreach (Document doc in proj.Documents)
        {
            if (doc.FilePath == fullPath)
                return doc;                   
        }
    }
    return null;
}
public SyntaxTree GetSyntaxTreeFromDocumentPath(string fullPath)
{
    Document doc = GetDocumentFromPath(fullPath);
    if (doc != null)
        return doc.GetSyntaxTreeAsync().Result;
    else
        return null;
}
public SyntaxNode GetNodeByFilePosition(string fullPath, int absoluteChar)
{
    SyntaxTree tree = GetSyntaxTreeFromDocumentPath(fullPath);
    if(tree != null)
    {
        var compUnit = tree.GetCompilationUnitRoot();
        if(compUnit != null)
        {
            return compUnit.FindToken(absoluteChar, true).Parent;
        }
    }
    return null;                        
}
private VisualStudioWorkspace GetRoslynWorkspace()
    {
        var componentModel = (IComponentModel)GetGlobalService(typeof(SComponentModel));
        return componentModel.GetService<VisualStudioWorkspace>();
    }

Main Part

EnvDTE80.DTE2 applicationObject = (EnvDTE80.DTE2)GetService(typeof(SDTE));
EnvDTE.TextSelection ts = applicationObject.ActiveWindow.Selection as EnvDTE.TextSelection;
if (ts == null)
    return;

EnvDTE.VirtualPoint point = ts.ActivePoint;
int charOffset = point.AbsoluteCharOffset;
charOffset += point.Line;//HACK ALERT

Parse.Roslyn.RoslynUtilities roslyn = new Parse.Roslyn.RoslynUtilities(GetRoslynWorkspace());
SyntaxNode node = roslyn.GetNodeByFilePosition(applicationObject.ActiveDocument.FullName, charOffset);
Connelley answered 26/5, 2016 at 19:55 Comment(0)
C
14

I'd highly recommend using Microsoft.VisualStudio.Text.SnapshotPoint from a Microsoft.VisualStudio.Text.Editor.IWpfTextView buffer instead of EnvDTE interfaces to interact with Roslyn.

Main code may look like this:

Microsoft.VisualStudio.Text.Editor.IWpfTextView textView =
    GetTextView();

Microsoft.VisualStudio.Text.SnapshotPoint caretPosition =
    textView.Caret.Position.BufferPosition;

Microsoft.CodeAnalysis.Document document =
    caretPosition.Snapshot.GetOpenDocumentInCurrentContextWithChanges();

Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax invocationExpressionNode = 
    document.GetSyntaxRootAsync().Result.
        FindToken(caretPosition).Parent.AncestorsAndSelf().
        OfType<Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax>().
        FirstOrDefault();

See Create a typed variable from the current method invocation for a complete example.

Coryphaeus answered 27/5, 2016 at 3:17 Comment(3)
Thanks! I switched it over and edited the last line by simply removing everything starting with the .AncestorsAndSelf and it did what I wanted. For whatever reason my old method is always +2 chars over what this one returns but this one is correct.Connelley
To save anyone else the wild goose chase I've just been on: using the GetOpenDocumentInCurrentContextWithChanges extension method in Microsoft.Codeanalysis 2.0 will introduce a (so far as I can see undocumented) dependency on Microsoft.VisualStudio 15.0, resulting in plugins that don't work on Visual Studio 2015. So if you want to use this method and don't want to restrict yourself to using Visual Studio 2017 it seems you'll need to build against Microsoft.CodeAnalysis 1.3.2.Undefined
@DanielMcLaury I haven't found any official documentation on this (not spent a lot of time looking though), but this SO answer talks about VS versions that each version of CodeAnalysis supports. It looks like 1.3.2 works with VS2015 Update 3 and higher: #45679361Taster

© 2022 - 2024 — McMap. All rights reserved.