diff --git a/HandlerFinder/FindHandlerCommand.cs b/HandlerFinder/FindHandlerCommand.cs
index 9b0ed82..b3ba892 100644
--- a/HandlerFinder/FindHandlerCommand.cs
+++ b/HandlerFinder/FindHandlerCommand.cs
@@ -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
@@ -31,12 +37,12 @@ internal sealed class FindHandlerCommand
///
/// Command menu group (command set GUID).
///
- 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");
///
/// VS Package that provides this command, not null.
///
- private readonly AsyncPackage package;
+ private readonly AsyncPackage _package;
///
/// Initializes a new instance of the class.
@@ -46,11 +52,11 @@ internal sealed class FindHandlerCommand
/// Command service to add command to, not null.
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);
}
@@ -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);
});
}
@@ -86,11 +92,11 @@ public static FindHandlerCommand Instance
///
/// Gets the service provider from the owner package.
///
- private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider
+ private IAsyncServiceProvider ServiceProvider
{
get
{
- return this.package;
+ return _package;
}
}
@@ -117,39 +123,217 @@ public static async Task InitializeAsync(AsyncPackage package)
/// Event args.
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> 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.Empty, 0);
+ }
+
+ VisualStudioWorkspace workspace = componentModel.GetService();
+
+ Solution solution = workspace.CurrentSolution;
+
+ Project domainProject =
+ solution.Projects.SingleOrDefault(y => y.Name.ToLowerInvariant().EndsWith("domain"));
+
+ IEnumerable types =
+ domainProject
+ .Documents
+ .Where(x => x.FilePath.ToLowerInvariant().Contains("commandhandlers"))
+ .Select(doc =>
+ new
+ {
+ Model = doc.GetSemanticModelAsync().Result,
+ Declarations = doc.GetSyntaxRootAsync().Result
+ .DescendantNodes().OfType()
+ }).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 declaredMethods = semantics.DescendantNodesAndSelf().OfType();
+
+ 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(fileNameToOpen, lineToGoTo);
+ }
+
+ private async Task> 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.Empty, 0);
+ }
+
+ VisualStudioWorkspace workspace = componentModel.GetService();
+
+ Solution solution = workspace.CurrentSolution;
+
+ Project applicationProject =
+ solution.Projects.SingleOrDefault(y => y.Name.ToLowerInvariant().EndsWith("application"));
+
+ IEnumerable types =
+ applicationProject
+ .Documents
+ .Where(x => x.FilePath.ToLowerInvariant().Contains("requesthandlers"))
+ .Select(doc =>
+ new
+ {
+ Model = doc.GetSemanticModelAsync().Result,
+ Declarations = doc.GetSyntaxRootAsync().Result
+ .DescendantNodes().OfType()
+ }).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 declaredMethods =
+ semantics.DescendantNodesAndSelf().OfType();
+
+ 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(fileNameToOpen, lineToGoTo);
}
///
@@ -159,8 +343,12 @@ private void Execute(object sender, EventArgs e)
///
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;
}
///
@@ -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);
}
@@ -187,7 +375,7 @@ private async Task GetTextViewAsync()
Assumes.Present(textManager);
textManager.GetActiveView(1, null, out IVsTextView textView);
- var editorAdaptersFactoryService = await GetEditorAdaptersFactoryServiceAsync();
+ IVsEditorAdaptersFactoryService editorAdaptersFactoryService = await GetEditorAdaptersFactoryServiceAsync();
return editorAdaptersFactoryService.GetWpfTextView(textView);
}
diff --git a/HandlerFinder/HandlerFinder.csproj b/HandlerFinder/HandlerFinder.csproj
index 0033e28..6392f8d 100644
--- a/HandlerFinder/HandlerFinder.csproj
+++ b/HandlerFinder/HandlerFinder.csproj
@@ -66,10 +66,16 @@
- 3.0.0
+ 4.0.1
- 3.0.0
+ 4.0.1
+
+
+ 4.0.1
+
+
+ 4.0.1
diff --git a/HandlerFinder/HandlerFinderPackage.cs b/HandlerFinder/HandlerFinderPackage.cs
index 5290cfd..89e3352 100644
--- a/HandlerFinder/HandlerFinderPackage.cs
+++ b/HandlerFinder/HandlerFinderPackage.cs
@@ -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);
}
diff --git a/HandlerFinder/HandlerFinderPackage.vsct b/HandlerFinder/HandlerFinderPackage.vsct
index 16abc1c..4cb74ab 100644
--- a/HandlerFinder/HandlerFinderPackage.vsct
+++ b/HandlerFinder/HandlerFinderPackage.vsct
@@ -54,10 +54,10 @@
If you do not want an image next to your command, remove the Icon node /> -->
@@ -69,7 +69,7 @@
bitmap strip containing the bitmaps and then there are the numeric ids of the elements used
inside a button definition. An important aspect of this declaration is that the element id
must be the actual index (1-based) of the bitmap inside the bitmap strip. -->
-
+
@@ -84,7 +84,9 @@
-
+
+
+
diff --git a/HandlerFinder/source.extension.vsixmanifest b/HandlerFinder/source.extension.vsixmanifest
index af197fe..0113ba2 100644
--- a/HandlerFinder/source.extension.vsixmanifest
+++ b/HandlerFinder/source.extension.vsixmanifest
@@ -1,22 +1,22 @@
-
-
- HandlerFinder
- Empty VSIX Project.
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ HandlerFinder
+ A basic extension to find the handlers of commands and requests within netgo codebases.
+
+
+
+
+
+
+
+
+
+
+
+
+