diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs index b7808509..bf78583e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs @@ -123,6 +123,28 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } } + /// + /// Remove the binding queue entry + /// + protected void RemoveBindingContext(string key) + { + lock (this.bindingContextLock) + { + if (this.BindingContextMap.ContainsKey(key)) + { + // disconnect existing connection + var bindingContext = this.BindingContextMap[key]; + if (bindingContext.ServerConnection != null && bindingContext.ServerConnection.IsOpen) + { + bindingContext.ServerConnection.Disconnect(); + } + + // remove key from the map + this.BindingContextMap.Remove(key); + } + } + } + private bool HasPendingQueueItems { get diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs index bd767a7a..a74810d5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs @@ -51,8 +51,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// Use a ConnectionInfo item to create a connected binding context /// - /// - public virtual string AddConnectionContext(ConnectionInfo connInfo) + /// Connection info used to create binding context + /// Overwrite existing context + public virtual string AddConnectionContext(ConnectionInfo connInfo, bool overwrite = false) { if (connInfo == null) { @@ -63,8 +64,15 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices string connectionKey = GetConnectionContextKey(connInfo); if (BindingContextExists(connectionKey)) { - // no need to populate the context again since the context already exists - return connectionKey; + if (overwrite) + { + RemoveBindingContext(connectionKey); + } + else + { + // no need to populate the context again since the context already exists + return connectionKey; + } } IBindingContext bindingContext = this.GetOrCreateBindingContext(connectionKey); diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/RebuildIntelliSenseNotification.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/RebuildIntelliSenseNotification.cs new file mode 100644 index 00000000..f5c52591 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/RebuildIntelliSenseNotification.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts +{ + /// + /// Parameters to be sent back with a rebuild IntelliSense event + /// + public class RebuildIntelliSenseParams + { + /// + /// URI identifying the file that should have its IntelliSense cache rebuilt + /// + public string OwnerUri { get; set; } + } + + /// + /// RebuildIntelliSenseNotification notification mapping entry + /// + public class RebuildIntelliSenseNotification + { + public static readonly + EventType Type = + EventType.Create("textDocument/rebuildIntelliSense"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs index 46a59afb..2229f893 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs @@ -212,6 +212,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices serviceHost.SetRequestHandler(HoverRequest.Type, HandleHoverRequest); serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest); serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest); + serviceHost.SetEventHandler(RebuildIntelliSenseNotification.Type, HandleRebuildIntelliSenseNotification); // Register a no-op shutdown task for validation of the shutdown logic serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) => @@ -442,6 +443,62 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices await Task.FromResult(true); } + /// + /// Handle the rebuild IntelliSense cache notification + /// + public async Task HandleRebuildIntelliSenseNotification( + RebuildIntelliSenseParams configChangeParams, + EventContext eventContext) + { + Logger.Write(LogLevel.Verbose, "HandleRebuildIntelliSenseNotification"); + + // Skip closing this file if the file doesn't exist + var scriptFile = this.CurrentWorkspace.GetFile(configChangeParams.OwnerUri); + if (scriptFile == null) + { + return; + } + + ConnectionInfo connInfo; + LanguageService.ConnectionServiceInstance.TryFindConnection( + scriptFile.ClientFilePath, + out connInfo); + + await Task.Run(() => + { + ScriptParseInfo scriptInfo = GetScriptParseInfo(connInfo.OwnerUri, createIfNotExists: false); + if (scriptInfo != null && scriptInfo.IsConnected && + Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout)) + { + try + { + this.BindingQueue.AddConnectionContext(connInfo, overwrite: true); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error, "Unknown error " + ex.ToString()); + } + finally + { + // Set Metadata Build event to Signal state. + Monitor.Exit(scriptInfo.BuildingMetadataLock); + } + } + + // if not in the preview window and diagnostics are enabled then run diagnostics + if (!IsPreviewWindow(scriptFile) + && WorkspaceService.Instance.CurrentSettings.IsDiagnositicsEnabled) + { + RunScriptDiagnostics( + new ScriptFile[] { scriptFile }, + eventContext); + } + + // Send a notification to signal that autocomplete is ready + ServiceHost.Instance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() {OwnerUri = connInfo.OwnerUri}); + }); + } + /// /// Handle the file configuration change notification /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/Workspace/WorkspaceService.cs b/src/Microsoft.SqlTools.ServiceLayer/Workspace/WorkspaceService.cs index dad14f90..5b5102e9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Workspace/WorkspaceService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Workspace/WorkspaceService.cs @@ -112,6 +112,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace /// List of callbacks to call when a text document is closed /// private List TextDocCloseCallbacks { get; set; } + #endregion @@ -189,7 +190,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace public void RegisterTextDocOpenCallback(TextDocOpenCallback task) { TextDocOpenCallbacks.Add(task); - } + } #endregion @@ -287,7 +288,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace var configUpdateTasks = ConfigChangeCallbacks.Select( t => t(configChangeParams.Settings, CurrentSettings, eventContext)); await Task.WhenAll(configUpdateTasks); - } + } #endregion diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/LanguageServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/LanguageServiceTests.cs index 31747992..2b52b951 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/LanguageServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/LanguageServiceTests.cs @@ -135,5 +135,27 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer SignatureHelp signatureHelp = service.GetSignatureHelp(textDocument, result.ScriptFile); Assert.NotNull(signatureHelp); } + + /// + /// Test overwriting the binding queue context + /// + [Fact] + public void OverwriteBindingContext() + { + var result = TestObjects.InitLiveConnectionInfo(); + + // add a new connection context + var connectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(result.ConnectionInfo, overwrite: true); + Assert.True(LanguageService.Instance.BindingQueue.BindingContextMap.ContainsKey(connectionKey)); + + // cache the server connection + var orgServerConnection = LanguageService.Instance.BindingQueue.BindingContextMap[connectionKey].ServerConnection; + Assert.NotNull(orgServerConnection); + + // add a new connection context + connectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(result.ConnectionInfo, overwrite: true); + Assert.True(LanguageService.Instance.BindingQueue.BindingContextMap.ContainsKey(connectionKey)); + Assert.False(object.ReferenceEquals(LanguageService.Instance.BindingQueue.BindingContextMap[connectionKey].ServerConnection, orgServerConnection)); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/AutocompleteTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/AutocompleteTests.cs index db5014cd..4fcc14fa 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/AutocompleteTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/AutocompleteTests.cs @@ -75,7 +75,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices // setup binding queue mock bindingQueue = new Mock(); - bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny())) + bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny(), It.IsAny())) .Returns(this.testConnectionKey); // inject mock instances into the Language Service diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs index 0b7efbb7..675b3507 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs @@ -82,7 +82,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices // setup binding queue mock bindingQueue = new Mock(); - bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny())) + bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny(), It.IsAny())) .Returns(this.testConnectionKey); // inject mock instances into the Language Service