From 83a377823513283755ab8570a80ccf0ff6cb203d Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Fri, 6 Sep 2024 10:01:18 +0200 Subject: [PATCH] fix: ls doesn't init after extension loads [IDE-617] (#296) * fix: ls doesn't init after extension loads * fix: delete unused command * chore: update loading message --- .gitleaksignore | 3 ++ .../Language/ILanguageClientManager.cs | 7 ++- .../Language/LanguageClientHelper.cs | 7 ++- .../Language/SnykLanguageClient.cs | 19 +++++++- .../SnykLanguageClientCustomTarget.cs | 2 +- .../Language/SnykLanguageServerEventArgs.cs | 9 ++++ .../Resources/SnykLsInit.cs | 4 ++ .../Settings/SnykGeneralOptionsDialogPage.cs | 4 +- .../SnykGeneralSettingsUserControl.cs | 4 +- .../Snyk.VisualStudio.Extension.2022.csproj | 5 +++ .../SnykVSPackage.cs | 45 ++++++++++++++++--- .../UI/Toolwindow/MessagePanel.xaml.cs | 43 +++++++++--------- .../Toolwindow/SnykToolWindowControl.xaml.cs | 4 +- 13 files changed, 117 insertions(+), 39 deletions(-) create mode 100644 Snyk.VisualStudio.Extension.2022/Language/SnykLanguageServerEventArgs.cs create mode 100644 Snyk.VisualStudio.Extension.2022/Resources/SnykLsInit.cs diff --git a/.gitleaksignore b/.gitleaksignore index 1b8e2312b..a31389a85 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -4,6 +4,9 @@ 9a05faae741fbfab2a0dbcc3df3452dbb031afef:Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.resx:generic-api-key:126 9a05faae741fbfab2a0dbcc3df3452dbb031afef:Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.resx:generic-api-key:129 9a05faae741fbfab2a0dbcc3df3452dbb031afef:Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.resx:generic-api-key:132 +b600b71041ea07c6aae3f1fe451a25680b0db4d7:Snyk.VisualStudio.Extension.2022/Settings/SnykGeneralSettingsUserControl.resx:generic-api-key:126 +b600b71041ea07c6aae3f1fe451a25680b0db4d7:Snyk.VisualStudio.Extension.2022/Settings/SnykGeneralSettingsUserControl.resx:generic-api-key:129 +b600b71041ea07c6aae3f1fe451a25680b0db4d7:Snyk.VisualStudio.Extension.2022/Settings/SnykGeneralSettingsUserControl.resx:generic-api-key:132 7920b3fa9f77f5461cd7648e6414db7d3e1c766b:snyk-visual-studio-plugin/VSPackage.resx:generic-api-key:130 # file no longer exists diff --git a/Snyk.VisualStudio.Extension.2022/Language/ILanguageClientManager.cs b/Snyk.VisualStudio.Extension.2022/Language/ILanguageClientManager.cs index c64cb46af..6f7b753fc 100644 --- a/Snyk.VisualStudio.Extension.2022/Language/ILanguageClientManager.cs +++ b/Snyk.VisualStudio.Extension.2022/Language/ILanguageClientManager.cs @@ -1,7 +1,8 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; +using Microsoft.VisualStudio.Threading; using Snyk.Common; -using Snyk.Common.Service; using StreamJsonRpc; namespace Snyk.VisualStudio.Extension.Language @@ -18,5 +19,7 @@ public interface ILanguageClientManager Task InvokeLogin(CancellationToken cancellationToken); Task InvokeLogout(CancellationToken cancellationToken); Task DidChangeConfigurationAsync(CancellationToken cancellationToken); + event AsyncEventHandler OnLanguageServerReadyAsync; + event AsyncEventHandler OnLanguageClientNotInitializedAsync; } } diff --git a/Snyk.VisualStudio.Extension.2022/Language/LanguageClientHelper.cs b/Snyk.VisualStudio.Extension.2022/Language/LanguageClientHelper.cs index 5a82ce7f0..1e5a92fcb 100644 --- a/Snyk.VisualStudio.Extension.2022/Language/LanguageClientHelper.cs +++ b/Snyk.VisualStudio.Extension.2022/Language/LanguageClientHelper.cs @@ -2,9 +2,14 @@ { public static class LanguageClientHelper { + public static ILanguageClientManager LanguageClientManager() + { + return SnykVSPackage.Instance?.LanguageClientManager; + } + public static bool IsLanguageServerReady() { - return SnykVSPackage.Instance?.LanguageClientManager != null && SnykVSPackage.Instance.LanguageClientManager.IsReady; + return LanguageClientManager() != null && LanguageClientManager().IsReady; } } } \ No newline at end of file diff --git a/Snyk.VisualStudio.Extension.2022/Language/SnykLanguageClient.cs b/Snyk.VisualStudio.Extension.2022/Language/SnykLanguageClient.cs index 1dddeafe6..1983744f1 100644 --- a/Snyk.VisualStudio.Extension.2022/Language/SnykLanguageClient.cs +++ b/Snyk.VisualStudio.Extension.2022/Language/SnykLanguageClient.cs @@ -102,6 +102,8 @@ public object GetInitializationOptions() public event AsyncEventHandler StartAsync; public event AsyncEventHandler StopAsync; + public event AsyncEventHandler OnLanguageServerReadyAsync; + public event AsyncEventHandler OnLanguageClientNotInitializedAsync; public async Task ActivateAsync(CancellationToken token) { @@ -157,6 +159,11 @@ public async Task OnLoadedAsync() public async Task StartServerAsync(bool shouldStart = false) { + if (StartAsync == null) + { + FireOnLanguageClientNotInitializedAsync(); + return; + } if (!IsReady && StartAsync != null && SnykVSPackage.Instance?.Options != null && shouldStart) { if (CustomMessageTarget == null) @@ -165,6 +172,8 @@ public async Task StartServerAsync(bool shouldStart = false) } Logger.Information("Starting Language Server"); await StartAsync.InvokeAsync(this, EventArgs.Empty); + IsReady = true; + FireOnLanguageServerReadyAsyncEvent(); } else { @@ -231,7 +240,6 @@ public async Task AttachForCustomMessageAsync(JsonRpc rpc) Rpc.AllowModificationWhileListening = true; Rpc.ActivityTracingStrategy = null; Rpc.AllowModificationWhileListening = false; - IsReady = true; } protected void OnStopping() { } @@ -354,7 +362,14 @@ public async Task RestartServerAsync() await RestartAsync(true); } - // TODO: Add Logging + public void FireOnLanguageServerReadyAsyncEvent() + { + this.OnLanguageServerReadyAsync?.InvokeAsync(this, new SnykLanguageServerEventArgs{IsReady = true}).FireAndForget(); + } + public void FireOnLanguageClientNotInitializedAsync() + { + this.OnLanguageClientNotInitializedAsync?.InvokeAsync(this, new SnykLanguageServerEventArgs { IsReady = false }).FireAndForget(); + } private async Task InvokeAsync(string request, CancellationToken t) { diff --git a/Snyk.VisualStudio.Extension.2022/Language/SnykLanguageClientCustomTarget.cs b/Snyk.VisualStudio.Extension.2022/Language/SnykLanguageClientCustomTarget.cs index dec67eaa9..b9619342b 100644 --- a/Snyk.VisualStudio.Extension.2022/Language/SnykLanguageClientCustomTarget.cs +++ b/Snyk.VisualStudio.Extension.2022/Language/SnykLanguageClientCustomTarget.cs @@ -132,7 +132,7 @@ public async Task OnHasAuthenticated(JToken arg) if (serviceProvider.Options.AutoScan) { - await this.languageClientManager.InvokeWorkspaceScanAsync(CancellationToken.None); + await this.languageClientManager.InvokeWorkspaceScanAsync(SnykVSPackage.Instance.DisposalToken); } } diff --git a/Snyk.VisualStudio.Extension.2022/Language/SnykLanguageServerEventArgs.cs b/Snyk.VisualStudio.Extension.2022/Language/SnykLanguageServerEventArgs.cs new file mode 100644 index 000000000..b757c3b19 --- /dev/null +++ b/Snyk.VisualStudio.Extension.2022/Language/SnykLanguageServerEventArgs.cs @@ -0,0 +1,9 @@ +using System; + +namespace Snyk.VisualStudio.Extension.Language +{ + public class SnykLanguageServerEventArgs : EventArgs + { + public bool IsReady { get; set; } + } +} diff --git a/Snyk.VisualStudio.Extension.2022/Resources/SnykLsInit.cs b/Snyk.VisualStudio.Extension.2022/Resources/SnykLsInit.cs new file mode 100644 index 000000000..449416ab2 --- /dev/null +++ b/Snyk.VisualStudio.Extension.2022/Resources/SnykLsInit.cs @@ -0,0 +1,4 @@ +/* + Snyk Security is loading... + Please hold on for a moment. +*/ \ No newline at end of file diff --git a/Snyk.VisualStudio.Extension.2022/Settings/SnykGeneralOptionsDialogPage.cs b/Snyk.VisualStudio.Extension.2022/Settings/SnykGeneralOptionsDialogPage.cs index 99f0481e5..6d939acbf 100644 --- a/Snyk.VisualStudio.Extension.2022/Settings/SnykGeneralOptionsDialogPage.cs +++ b/Snyk.VisualStudio.Extension.2022/Settings/SnykGeneralOptionsDialogPage.cs @@ -335,8 +335,8 @@ public bool Authenticate() Logger.Information("Api token is invalid. Attempting to authenticate via snyk auth"); ThreadHelper.JoinableTaskFactory.Run(async ()=> { - await ServiceProvider.Package.LanguageClientManager.InvokeLogout(CancellationToken.None); - var token = await ServiceProvider.Package.LanguageClientManager.InvokeLogin(CancellationToken.None); + await ServiceProvider.Package.LanguageClientManager.InvokeLogout(SnykVSPackage.Instance.DisposalToken); + var token = await ServiceProvider.Package.LanguageClientManager.InvokeLogin(SnykVSPackage.Instance.DisposalToken); ApiToken = CreateAuthenticationToken(token); }); return true; diff --git a/Snyk.VisualStudio.Extension.2022/Settings/SnykGeneralSettingsUserControl.cs b/Snyk.VisualStudio.Extension.2022/Settings/SnykGeneralSettingsUserControl.cs index 07c70e8fd..4276e43b8 100644 --- a/Snyk.VisualStudio.Extension.2022/Settings/SnykGeneralSettingsUserControl.cs +++ b/Snyk.VisualStudio.Extension.2022/Settings/SnykGeneralSettingsUserControl.cs @@ -110,7 +110,7 @@ private void OptionsDialogPageOnSettingsChanged(object sender, SnykSettingsChang await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); this.UpdateViewFromOptionsDialog(); if (LanguageClientHelper.IsLanguageServerReady()) - await ServiceProvider.Package.LanguageClientManager.DidChangeConfigurationAsync(CancellationToken.None); + await ServiceProvider.Package.LanguageClientManager.DidChangeConfigurationAsync(SnykVSPackage.Instance.DisposalToken); }).FireAndForget(); public async Task OnAuthenticationSuccessfulAsync(string apiToken) @@ -356,7 +356,7 @@ private void StartSastEnablementCheckLoop() try { if (!LanguageClientHelper.IsLanguageServerReady()) return; - var sastSettings = await this.ServiceProvider.Package.LanguageClientManager.InvokeGetSastEnabled(CancellationToken.None); + var sastSettings = await this.ServiceProvider.Package.LanguageClientManager.InvokeGetSastEnabled(SnykVSPackage.Instance.DisposalToken); bool snykCodeEnabled = sastSettings != null ? sastSettings.SnykCodeEnabled : false; diff --git a/Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj b/Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj index c849db057..35aa2c959 100644 --- a/Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj +++ b/Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj @@ -88,6 +88,7 @@ + @@ -101,6 +102,10 @@ + + PreserveNewest + true + diff --git a/Snyk.VisualStudio.Extension.2022/SnykVSPackage.cs b/Snyk.VisualStudio.Extension.2022/SnykVSPackage.cs index 3097c2e47..9e1a989e5 100644 --- a/Snyk.VisualStudio.Extension.2022/SnykVSPackage.cs +++ b/Snyk.VisualStudio.Extension.2022/SnykVSPackage.cs @@ -1,10 +1,13 @@ using Snyk.Common.Settings; using System; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; +using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using EnvDTE; using Microsoft; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell; @@ -18,6 +21,7 @@ using Snyk.VisualStudio.Extension.UI.Notifications; using Snyk.VisualStudio.Extension.UI.Toolwindow; using Task = System.Threading.Tasks.Task; +using Thread = EnvDTE.Thread; namespace Snyk.VisualStudio.Extension { @@ -196,8 +200,10 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke await InitializeGeneralOptionsAsync(); - // Initialize analytics - var vsVersion = await GetReadableVsVersionAsync(); + + // Initialize LS + Logger.Information("Initializing Language Server"); + await InitializeLanguageClientAsync(); // Initialize commands Logger.Information("Initialize Commands()"); @@ -214,8 +220,6 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke ToolWindowControl.InitializeEventListeners(this.serviceProvider); ToolWindowControl.Initialize(this.serviceProvider); - await InitializeLanguageClientAsync(); - // Notify package has been initialized IsInitialized = true; initializationTaskCompletionSource.SetResult(true); @@ -245,9 +249,11 @@ private async Task InitializeLanguageClientAsync() var componentModel = GetGlobalService(typeof(SComponentModel)) as IComponentModel; Assumes.Present(componentModel); var languageServerClientManager = componentModel.GetService(); - if(languageServerClientManager != null && !languageServerClientManager.IsReady) + LanguageClientManager = languageServerClientManager; + LanguageClientManager.OnLanguageClientNotInitializedAsync += LanguageClientManagerOnOnLanguageClientNotInitializedAsync; + LanguageClientManager.OnLanguageServerReadyAsync += LanguageClientManagerOnOnLanguageServerReadyAsync; + if (languageServerClientManager != null && !languageServerClientManager.IsReady) { - LanguageClientManager = languageServerClientManager; // If CLI download is necessary, Skip initializing. if (this.serviceProvider.TasksService.ShouldDownloadCli()) { @@ -262,6 +268,33 @@ private async Task InitializeLanguageClientAsync() } } + private async Task LanguageClientManagerOnOnLanguageServerReadyAsync(object sender, SnykLanguageServerEventArgs args) + { + // Sleep for three seconds before closing the temp window + await Task.Delay(3000); + await JoinableTaskFactory.SwitchToMainThreadAsync(); + tempOpenedFileWindow?.Close(vsSaveChanges.vsSaveChangesNo); + } + + private Window tempOpenedFileWindow; + private async Task LanguageClientManagerOnOnLanguageClientNotInitializedAsync(object sender, SnykLanguageServerEventArgs args) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var dte = (DTE)await GetServiceAsync(typeof(DTE)); + if (dte == null) + { + return; + } + + // Get the path to the file within the installed extension directory + var assemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var filePath = Path.Combine(assemblyLocation, "Resources", "SnykLsInit.cs"); + + // Open the file + tempOpenedFileWindow = dte.ItemOperations.OpenFile(filePath, EnvDTE.Constants.vsViewKindTextView); + } + private async Task InitializeGeneralOptionsAsync() { if (GeneralOptionsDialogPage == null) diff --git a/Snyk.VisualStudio.Extension.2022/UI/Toolwindow/MessagePanel.xaml.cs b/Snyk.VisualStudio.Extension.2022/UI/Toolwindow/MessagePanel.xaml.cs index e96173ee9..d689e21bc 100644 --- a/Snyk.VisualStudio.Extension.2022/UI/Toolwindow/MessagePanel.xaml.cs +++ b/Snyk.VisualStudio.Extension.2022/UI/Toolwindow/MessagePanel.xaml.cs @@ -1,19 +1,16 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Drawing; using System.IO; -using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using Community.VisualStudio.Toolkit; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Threading; using Serilog; -using Serilog.Core; using Snyk.Common; +using Snyk.VisualStudio.Extension.Language; using Snyk.VisualStudio.Extension.Service; namespace Snyk.VisualStudio.Extension.UI.Toolwindow @@ -23,16 +20,6 @@ namespace Snyk.VisualStudio.Extension.UI.Toolwindow /// public partial class MessagePanel : UserControl { - - - //[ComVisible(true)] - //public class ScriptManager - //{ - // public void OpenFileInEditor(string filePath, int lineNumber, int columnNumber) - // { - // // DTE stuff - // } - //} private static readonly ILogger Logger = LogManager.ForContext(); private readonly IList panels; @@ -42,11 +29,6 @@ public partial class MessagePanel : UserControl public MessagePanel() { this.InitializeComponent(); - //Testbrowser.ObjectForScripting = new ScriptManager(); - //Testbrowser.ContextMenu.IsEnabled = false; - //Testbrowser.= true; - // To Execute from Javascript window.external.OpenFileInEditor(filePath, int lineNumber, int columnNumber) - //Testbrowser.DataContext = ""; this.panels = new List { @@ -58,6 +40,17 @@ public MessagePanel() this.scanningProjectMessagePanel, }; snykDogLogo.Source = SnykIconProvider.GetImageSourceFromPath(SnykIconProvider.SnykDogLogoIconPath); + var languageClientManager = LanguageClientHelper.LanguageClientManager(); + if (languageClientManager != null) + { + languageClientManager.OnLanguageServerReadyAsync += LanguageClientManagerOnOnLanguageServerReady; + } + } + + private async Task LanguageClientManagerOnOnLanguageServerReady(object sender, SnykLanguageServerEventArgs e) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + testCodeNowButton.IsEnabled = true; } /// @@ -101,7 +94,15 @@ public string Text /// /// Show overview screen message. /// - public void ShowOverviewScreenMessage() => this.ShowPanel(this.overviewPanel); + public void ShowOverviewScreenMessage() + { + if (!LanguageClientHelper.IsLanguageServerReady()) + { + testCodeNowButton.IsEnabled = false; + } + + this.ShowPanel(this.overviewPanel); + } private void RunButton_Click(object sender, RoutedEventArgs e) => ThreadHelper.JoinableTaskFactory.RunAsync(SnykTasksService.Instance.ScanAsync); @@ -120,7 +121,7 @@ private void TestCodeNow_Click(object sender, RoutedEventArgs e) ThreadHelper.JoinableTaskFactory.Run(RunTestCodeNowAsync); } - private async System.Threading.Tasks.Task RunTestCodeNowAsync() + private async Task RunTestCodeNowAsync() { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); this.testCodeNowButton.IsEnabled = false; // Disable the button while authenticating diff --git a/Snyk.VisualStudio.Extension.2022/UI/Toolwindow/SnykToolWindowControl.xaml.cs b/Snyk.VisualStudio.Extension.2022/UI/Toolwindow/SnykToolWindowControl.xaml.cs index 136aaa24b..71764ff9a 100644 --- a/Snyk.VisualStudio.Extension.2022/UI/Toolwindow/SnykToolWindowControl.xaml.cs +++ b/Snyk.VisualStudio.Extension.2022/UI/Toolwindow/SnykToolWindowControl.xaml.cs @@ -480,7 +480,7 @@ private void UpdateTreeNodeItemsState() => ThreadHelper.JoinableTaskFactory.Run( SastSettings sastSettings = null; if (LanguageClientHelper.IsLanguageServerReady()) { - sastSettings = await this.serviceProvider.Package.LanguageClientManager.InvokeGetSastEnabled(CancellationToken.None); + sastSettings = await this.serviceProvider.Package.LanguageClientManager.InvokeGetSastEnabled(SnykVSPackage.Instance.DisposalToken); } this.resultsTree.CodeQualityRootNode.State = this.GetSnykCodeRootNodeState(sastSettings, options.SnykCodeQualityEnabled); @@ -695,7 +695,7 @@ private void ShowWelcomeOrRunScanScreen() { var options = this.serviceProvider.Options; - if (options.ApiToken.IsValid()) + if (options.ApiToken.IsValid() && LanguageClientHelper.IsLanguageServerReady()) { this.context.TransitionTo(RunScanState.Instance); return;