From 7f10662d9eb82ec810bfd160a73f9339d82ea448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ben=20Konsem=C3=BCller?= Date: Sat, 20 Nov 2021 17:12:03 +0100 Subject: [PATCH] First test version --- HandlerFinder/FindHandlerCommand.cs | 256 +++++++++++++++++--- HandlerFinder/HandlerFinder.csproj | 10 +- HandlerFinder/HandlerFinderPackage.cs | 2 +- HandlerFinder/HandlerFinderPackage.vsct | 10 +- HandlerFinder/source.extension.vsixmanifest | 38 +-- 5 files changed, 256 insertions(+), 60 deletions(-) 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. - - - - amd64 - - - - - - - - - - - + + + HandlerFinder + A basic extension to find the handlers of commands and requests within netgo codebases. + + + + amd64 + + + + + + + + + + +