Skip to content

Commit

Permalink
First test version
Browse files Browse the repository at this point in the history
  • Loading branch information
btastic committed Nov 20, 2021
1 parent 385726d commit 7f10662
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 60 deletions.
256 changes: 222 additions & 34 deletions HandlerFinder/FindHandlerCommand.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using EnvDTE;
using EnvDTE80;
using Microsoft;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Threading;
using Document = Microsoft.CodeAnalysis.Document;
using Project = Microsoft.CodeAnalysis.Project;
using Solution = Microsoft.CodeAnalysis.Solution;
using Task = System.Threading.Tasks.Task;

namespace HandlerFinder
Expand All @@ -31,12 +37,12 @@ internal sealed class FindHandlerCommand
/// <summary>
/// Command menu group (command set GUID).
/// </summary>
public static readonly Guid CommandSet = new Guid("222993a5-b14f-4206-9ba3-00cf383c7d99");
public static readonly Guid s_commandSet = new Guid("222993a5-b14f-4206-9ba3-00cf383c7d99");

/// <summary>
/// VS Package that provides this command, not null.
/// </summary>
private readonly AsyncPackage package;
private readonly AsyncPackage _package;

/// <summary>
/// Initializes a new instance of the <see cref="FindHandlerCommand"/> class.
Expand All @@ -46,11 +52,11 @@ internal sealed class FindHandlerCommand
/// <param name="commandService">Command service to add command to, not null.</param>
private FindHandlerCommand(AsyncPackage package, OleMenuCommandService commandService)
{
this.package = package ?? throw new ArgumentNullException(nameof(package));
_package = package ?? throw new ArgumentNullException(nameof(package));
commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));

var menuCommandID = new CommandID(CommandSet, CommandId);
var menuItem = new OleMenuCommand(this.Execute, menuCommandID);
var menuCommandID = new CommandID(s_commandSet, CommandId);
var menuItem = new OleMenuCommand(Execute, menuCommandID);
menuItem.BeforeQueryStatus += MenuItem_BeforeQueryStatus;
commandService.AddCommand(menuItem);
}
Expand All @@ -68,7 +74,7 @@ private void MenuItem_BeforeQueryStatus(object sender, EventArgs e)
command.Visible = false;
ThreadHelper.JoinableTaskFactory.Run(async delegate
{
(var node, var document) = await GetCurrentTokenAndDocumentAsync();
(SyntaxNode node, Document document) = await GetCurrentTokenAndDocumentAsync();
command.Visible = IsSyntaxNodeSupported(node);
});
}
Expand All @@ -86,11 +92,11 @@ public static FindHandlerCommand Instance
/// <summary>
/// Gets the service provider from the owner package.
/// </summary>
private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider
private IAsyncServiceProvider ServiceProvider
{
get
{
return this.package;
return _package;
}
}

Expand All @@ -117,39 +123,217 @@ public static async Task InitializeAsync(AsyncPackage package)
/// <param name="e">Event args.</param>
private void Execute(object sender, EventArgs e)
{
string requestedCommandOrRequest = string.Empty;
(string fileName, int lineToGoTo) = (string.Empty, 0);

ThreadHelper.ThrowIfNotOnUIThread();

ThreadHelper.JoinableTaskFactory.Run(async delegate
{
(var node, var document) = await GetCurrentTokenAndDocumentAsync();
(SyntaxNode node, Document document) = await GetCurrentTokenAndDocumentAsync();

if (IsSyntaxNodeSupported(node))
{
SemanticModel semanticModel = await document.GetSemanticModelAsync();
requestedCommandOrRequest = GetIdentifierNameByNode(node);
}
});

var symbolInfo = semanticModel.GetSymbolInfo(node);
var typeInfo = semanticModel.GetTypeInfo(node);
if (string.IsNullOrWhiteSpace(requestedCommandOrRequest))
{
return;
}

var symbol = semanticModel.GetDeclaredSymbol(node);
var containingNamespace = symbol.ContainingNamespace;
ThreadHelper.JoinableTaskFactory.Run(async delegate
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

System.Windows.Forms.Clipboard.SetText(symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat));
if (requestedCommandOrRequest.ToLowerInvariant().EndsWith("request"))
{
(fileName, lineToGoTo) =
await FindRequestHandlerAsync(requestedCommandOrRequest);
}
else if (requestedCommandOrRequest.ToLowerInvariant().EndsWith("command"))
{
(fileName, lineToGoTo) =
await FindCommandHandlerAsync(requestedCommandOrRequest);
}

});

string message = string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()", this.GetType().FullName);
string title = "FindHandlerCommand";

// Show a message box to prove we were here
VsShellUtilities.ShowMessageBox(
this.package,
message,
title,
OLEMSGICON.OLEMSGICON_INFO,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
if (!string.IsNullOrEmpty(fileName))
{
var dte = Package.GetGlobalService(typeof(_DTE)) as DTE2;
dte.ExecuteCommand("File.OpenFile", fileName);

if (lineToGoTo > 0)
{
((TextSelection)dte.ActiveDocument.Selection).GotoLine(lineToGoTo + 1);
}
}
}

private string GetIdentifierNameByNode(SyntaxNode node)
{
string name = string.Empty;

switch (node)
{
case RecordDeclarationSyntax _:
name = ((BaseTypeDeclarationSyntax)node).Identifier.Text;
break;
case IdentifierNameSyntax _:
name = ((IdentifierNameSyntax)node).Identifier.Text;
break;
}

if (name != "var")
{
return name;
}

return string.Empty;
}

private async Task<Tuple<string, int>> FindCommandHandlerAsync(string requestedCommandOrRequest)
{
string fileNameToOpen = string.Empty;
int lineToGoTo = 0;

var componentModel = (IComponentModel)await ServiceProvider.GetServiceAsync(typeof(SComponentModel));

if (componentModel == null)
{
return new Tuple<string, int>(string.Empty, 0);
}

VisualStudioWorkspace workspace = componentModel.GetService<VisualStudioWorkspace>();

Solution solution = workspace.CurrentSolution;

Project domainProject =
solution.Projects.SingleOrDefault(y => y.Name.ToLowerInvariant().EndsWith("domain"));

IEnumerable<GenericNameSyntax> types =
domainProject
.Documents
.Where(x => x.FilePath.ToLowerInvariant().Contains("commandhandlers"))
.Select(doc =>
new
{
Model = doc.GetSemanticModelAsync().Result,
Declarations = doc.GetSyntaxRootAsync().Result
.DescendantNodes().OfType<GenericNameSyntax>()
}).Where(x => x.Declarations.Any())
.SelectMany(x => x.Declarations)
.Where(x => x.Identifier.Text == "IRequestHandler");

foreach (GenericNameSyntax type in types)
{
foreach (TypeSyntax typeArgument in type.TypeArgumentList.Arguments)
{
var typeArgumentIdentifierSyntax = (IdentifierNameSyntax)typeArgument;
if (typeArgumentIdentifierSyntax.Identifier.Text == requestedCommandOrRequest)
{
fileNameToOpen = type.SyntaxTree.FilePath;

Document document = domainProject.Documents.Single(x => x.FilePath == fileNameToOpen);

SyntaxTree syntaxTree = await document.GetSyntaxTreeAsync();
SyntaxNode semantics = await document.GetSyntaxRootAsync();

IEnumerable<MethodDeclarationSyntax> declaredMethods = semantics.DescendantNodesAndSelf().OfType<MethodDeclarationSyntax>();

foreach (MethodDeclarationSyntax method in declaredMethods)
{
if (((IdentifierNameSyntax)(method.ParameterList.Parameters.First()).Type).Identifier.Text == requestedCommandOrRequest)
{
lineToGoTo = syntaxTree.GetLineSpan(method.Span).StartLinePosition.Line;
break;
}
}

break;
}
}

if (!string.IsNullOrEmpty(fileNameToOpen))
{
break;
}
}

return new Tuple<string, int>(fileNameToOpen, lineToGoTo);
}

private async Task<Tuple<string, int>> FindRequestHandlerAsync(string requestedCommandOrRequest)
{
string fileNameToOpen = string.Empty;
int lineToGoTo = 0;

var componentModel = (IComponentModel)await ServiceProvider.GetServiceAsync(typeof(SComponentModel));

if (componentModel == null)
{
return new Tuple<string, int>(string.Empty, 0);
}

VisualStudioWorkspace workspace = componentModel.GetService<VisualStudioWorkspace>();

Solution solution = workspace.CurrentSolution;

Project applicationProject =
solution.Projects.SingleOrDefault(y => y.Name.ToLowerInvariant().EndsWith("application"));

IEnumerable<GenericNameSyntax> types =
applicationProject
.Documents
.Where(x => x.FilePath.ToLowerInvariant().Contains("requesthandlers"))
.Select(doc =>
new
{
Model = doc.GetSemanticModelAsync().Result,
Declarations = doc.GetSyntaxRootAsync().Result
.DescendantNodes().OfType<GenericNameSyntax>()
}).Where(x => x.Declarations.Any())
.SelectMany(x => x.Declarations)
.Where(x => x.Identifier.Text == "IRequestHandler");

foreach (GenericNameSyntax type in types)
{
foreach (TypeSyntax typeArgument in type.TypeArgumentList.Arguments)
{
var typeArgumentIdentifierSyntax = (IdentifierNameSyntax)typeArgument;
if (typeArgumentIdentifierSyntax.Identifier.Text == requestedCommandOrRequest)
{
fileNameToOpen = type.SyntaxTree.FilePath;

Document document = applicationProject.Documents.Single(x => x.FilePath == fileNameToOpen);

SyntaxTree syntaxTree = await document.GetSyntaxTreeAsync();
SyntaxNode semantics = await document.GetSyntaxRootAsync();

IEnumerable<MethodDeclarationSyntax> declaredMethods =
semantics.DescendantNodesAndSelf().OfType<MethodDeclarationSyntax>();

foreach (MethodDeclarationSyntax method in declaredMethods)
{
if (((IdentifierNameSyntax)(method.ParameterList.Parameters.First()).Type).Identifier.Text == requestedCommandOrRequest)
{
lineToGoTo = syntaxTree.GetLineSpan(method.Span).StartLinePosition.Line;
break;
}
}

break;
}
}

if (!string.IsNullOrEmpty(fileNameToOpen))
{
break;
}
}

return new Tuple<string, int>(fileNameToOpen, lineToGoTo);
}

/// <summary>
Expand All @@ -159,8 +343,12 @@ private void Execute(object sender, EventArgs e)
/// <returns></returns>
private bool IsSyntaxNodeSupported(SyntaxNode node)
{
return node != null
&& ((node is MemberDeclarationSyntax));
bool isSupported = node != null
&& ((node is IdentifierNameSyntax) || (node is RecordDeclarationSyntax));

isSupported &= GetIdentifierNameByNode(node) != string.Empty;

return isSupported;
}

/// <summary>
Expand All @@ -172,7 +360,7 @@ private bool IsSyntaxNodeSupported(SyntaxNode node)
IWpfTextView textView = await GetTextViewAsync();
SnapshotPoint caretPosition = textView.Caret.Position.BufferPosition;
Document document = caretPosition.Snapshot.GetOpenDocumentInCurrentContextWithChanges();
var syntaxRoot = await document.GetSyntaxRootAsync();
SyntaxNode syntaxRoot = await document.GetSyntaxRootAsync();
return (syntaxRoot.FindToken(caretPosition).Parent, document);
}

Expand All @@ -187,7 +375,7 @@ private async Task<IWpfTextView> GetTextViewAsync()
Assumes.Present(textManager);
textManager.GetActiveView(1, null, out IVsTextView textView);

var editorAdaptersFactoryService = await GetEditorAdaptersFactoryServiceAsync();
IVsEditorAdaptersFactoryService editorAdaptersFactoryService = await GetEditorAdaptersFactoryServiceAsync();

return editorAdaptersFactoryService.GetWpfTextView(textView);
}
Expand Down
10 changes: 8 additions & 2 deletions HandlerFinder/HandlerFinder.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,16 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp">
<Version>3.0.0</Version>
<Version>4.0.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.EditorFeatures.Text">
<Version>3.0.0</Version>
<Version>4.0.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild">
<Version>4.0.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.LanguageServices">
<Version>4.0.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.0.31902.203" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.0.5233" />
Expand Down
2 changes: 1 addition & 1 deletion HandlerFinder/HandlerFinderPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
{
// When initialized asynchronously, the current thread may be a background thread at this point.
// Do any initialization that requires the UI thread after switching to the UI thread.
await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
await FindHandlerCommand.InitializeAsync(this);
}

Expand Down
Loading

0 comments on commit 7f10662

Please sign in to comment.